Modularisierung von Databricks Notebooks und Code-Wiederverwendung in Workflows

Modularisierung von Databricks Notebooks und Code-Wiederverwendung in Workflows

Modularisierung von Databricks Notebooks und Code-Wiederverwendung in Workflows

13.04.2025
13.04.2025
13.04.2025
Data Lakehouse
Data Lakehouse
Data Lakehouse

Viele Databricks-Projekte starten schnell und unkompliziert: ein paar Notebooks, ein paar Transformationen, alles läuft. Doch je größer das Projekt wird, desto öfter tauchen typische Probleme auf:

  • 💥 Duplikation von Code (z. B. dieselben Funktionen in 5 Notebooks kopiert)

  • 📉 Schlechter Überblick bei wachsender Anzahl von Notebooks

  • 😰 Fehleranfälligkeit bei Änderungen (z. B. kleine Änderungen müssen an mehreren Stellen gemacht werden)

Modularisierung ist der Schlüssel, um diese Herausforderungen in den Griff zu bekommen. Ziel ist es, wiederverwendbare Komponenten zu schaffen – egal ob es sich um Funktionen, Konfigurationslogik oder ganze Pipeline-Abschnitte handelt.

In diesem Artikel zeige ich dir:

  • Wie du in Databricks Code effizient wiederverwenden kannst

  • Welche Modularisierungsansätze du kennen solltest

  • Wie du Workflows baust, die flexibel und wartbar bleiben

Dabei geht’s nicht nur um %run und Repos – sondern auch um Projektstruktur, Parametrisierung und saubere Trennung von Verantwortlichkeiten.

Los geht’s mit der einfachsten Form von Modularisierung in Databricks: dem Notebook-Chaining per %run.


Notebook-Komposition mit %run: Wiederverwendbare Komponenten schaffen

Databricks bietet mit dem %run-Kommando eine einfache Möglichkeit, Code aus einem anderen Notebook zu laden – ähnlich wie ein Modulimport in klassischen Python-Projekten.

Das ist besonders hilfreich, wenn du:

  • wiederkehrende Logik (z. B. Konfiguration, Helper-Funktionen) zentral definieren willst

  • deine Pipeline in einzelne, gut wartbare Bausteine aufteilen möchtest

🧪 Beispiel: Gemeinsame Konfiguration auslagern

Du kannst ein eigenes Notebook z. B. config nennen und dort alle globalen Variablen und Parameter zentral pflegen:

# /Shared/config
env = "prod"
input_path = f"/mnt/data/{env}/input"
output_path = f"/mnt/data/{env}/output"

Dann bindest du es in beliebigen Notebooks ein mit:

# Notebook: /Ingestion/step_1
%run ../Shared/config
df = spark.read.parquet(input_path)
...
df.write.parquet(output_path)

➡️ Vorteil: Wenn sich der Pfad ändert, musst du es nur an einer Stelle anpassen.

🔁 Weitere Anwendungsfälle

  • Helper-Methoden (z. B. Funktionen zum Lesen & Schreiben von Daten)

  • Schema-Definitionen (z. B. Spark StructType Objekte)

  • Custom Logging-Mechaniken

  • Validation oder Cleaning-Logik

⚠️ Was du beachten solltest

  • %run lädt den Code zur Laufzeit in den aktuellen Notebook-Kontext → alle Variablen & Funktionen stehen direkt zur Verfügung

  • Wenn mehrere Notebooks denselben %run aufrufen, solltest du Seiteneffekte vermeiden (z. B. keine Jobs starten oder Daten schreiben beim Laden!)

  • Achte auf Lesbarkeit – zu viel %run-Magie kann die Nachvollziehbarkeit verringern, wenn man nicht klar dokumentiert, was wo passiert

🧠 Tipp aus der Praxis:

Nutze %run primär für konfigurierbare, passive Komponenten – also alles, was Variablen, Funktionen oder Konfiguration liefert – aber keine komplexe Logik ausführt.


Code als Python-Modul: Importieren von DBFS- & Repo-Dateien

Sobald dein Projekt wächst, stößt %run schnell an seine Grenzen. Spätestens wenn du mehrere Umgebungen, Unit Tests oder CI/CD einführst, ist es sinnvoller, klassische Python-Module zu verwenden.

In Databricks hast du dafür zwei Optionen:

🗂️ Option 1: Module im Databricks Repos (empfohlen)

Wenn du mit Databricks Repos arbeitest (also einem geklonten Git-Repository), kannst du dort eine normale Projektstruktur mit Unterordnern und Python-Dateien anlegen:

project-root/

├── jobs/
└── ingest.py

├── shared/
├── config.py
└── utils.py

└── notebooks/
    └── run_job.py

In deinem Notebook kannst du dann ganz normal importieren:

import sys
sys.path.append('/Workspace/Repos/benutzername@firma.de/project-root')
from shared.config import input_path, output_path
from shared.utils import clean_dataframe

✅ Vorteile:

  • Pythonic, testbar, CI/CD-freundlich

  • Unterstützung für IDE-Entwicklung außerhalb von Databricks

  • Super für Teams & langfristige Wartung

📦 Option 2: Module in DBFS (z. B. /dbfs/FileStore/modules)

Wenn du noch kein Repo nutzt, kannst du auch eigene .py-Dateien im DBFS (Databricks File System) speichern und sie dann dynamisch importieren:

# Beispiel: Datei unter /dbfs/FileStore/modules/utils.py
import sys
sys.path.append("/dbfs/FileStore/modules")
from utils import some_function

💡 Achtung: Das funktioniert, ist aber eher ein Workaround. Für produktive Nutzung ist Databricks Repos der klar empfohlene Weg.

🔧 Nützliche Tipps

  • Packe deine Imports am besten in eine Helper-Notebook-Datei wie setup.py oder bootstrap.py und %run sie zentral

  • Achte auf saubere Struktur mit klaren Modulen: z. B. config, io, transform, validate

  • Nutze .py-Dateien auch für UDFs oder Businesslogik, die mehrfach verwendet wird


Workflows mit Parametern & dynamischem Verhalten gestalten

Modularisierung hört nicht beim Code auf – auch deine Workflows und Pipelines sollten so gestaltet sein, dass sie flexibel, wiederverwendbar und gut steuerbar sind.

Databricks Workflows (früher Jobs) bieten genau das: Du kannst Notebooks oder Python-Skripte als Tasks definieren und diese mit Parametern versehen, um das Verhalten zur Laufzeit zu beeinflussen.

🧪 Beispiel: Notebook mit Parametern

Angenommen, du hast ein generisches Ingestion-Notebook, das Daten aus verschiedenen Quellen laden kann – z. B. über einen source-Parameter:

# ingest_data.py

dbutils.widgets.text("source", "default")
source = dbutils.widgets.get("source")

if source == "customers":
    df = spark.read.json("/mnt/data/raw/customers/")
elif source == "orders":
    df = spark.read.json("/mnt/data/raw/orders/")
else:
    raise Exception("Unsupported source")

df.write.mode("overwrite").parquet(f"/mnt/data/processed/{source}/")

➡️ Jetzt kannst du denselben Code für beliebige Datenquellen nutzen – der Parameter steuert die Quelle.

⚙️ Parametrisierte Workflows in der UI

In der Databricks UI kannst du beim Erstellen eines Jobs:

  1. Das Notebook auswählen

  2. Eingabefelder für Parameter definieren (source = "customers")

  3. Diese Parameter bei der Ausführung ändern

Du kannst auch mehrere Tasks mit unterschiedlichen Parametern aus demselben Notebook aufrufen – z. B. für mehrere Tabellen.

🔁 Dynamische Pipelines bauen

Mit Parametrisierung + Verzweigungen (Task Dependencies) kannst du sehr dynamische Pipelines erstellen, z. B.:

  • Ein Task lädt Metadaten und entscheidet, welche Tabellen verarbeitet werden

  • Danach folgen mehrere Tasks mit dynamischem Verhalten, gesteuert über Parameter

💡 Die Parameter kannst du auch mit REST-APIs oder CI/CD-Systemen (z. B. Azure DevOps, GitHub Actions) programmatisch setzen und starten.

🔐 Bonus: Secrets & Umgebungen

Verwende dbutils.secrets.get(...) oder Umgebungsparameter, um sichere und flexible Konfiguration umzusetzen:

env = dbutils.widgets.get("env")
db_password = dbutils.secrets.get(scope="prod", key="db-password")

Damit kannst du denselben Code in Dev, QA und Prod verwenden – ohne manuelle Änderungen.


Best Practices für eine skalierbare Projektstruktur in Databricks

Modularisierung bringt nur dann echten Mehrwert, wenn auch die Projektstruktur sauber aufgesetzt ist. Chaos in der Ordnerstruktur oder wild verstreute Notebooks können deine Wiederverwendbarkeit und Teamarbeit schnell ausbremsen.

Hier ein paar bewährte Ansätze aus der Praxis:

📁 Klare Trennung von Code & Ausführung

Empfohlene Struktur (Beispiel mit Databricks Repos):

project-root/

├── notebooks/
├── ingest_customers.py
├── ingest_orders.py
└── run_pipeline.py

├── shared/
├── config.py
├── io.py
└── transforms.py

├── tests/
└── test_transforms.py

└── workflows/
    └── pipeline_definition.json

🔹 notebooks/ → ausführbare Einstiegspunkte
🔹 shared/ → wiederverwendbare Module
🔹 tests/ → Unit Tests mit pytest
🔹 workflows/ → JSON- oder YAML-Definitionen für CI/CD

👨‍💻 Weitere Empfehlungen

  • Nutze Databricks Repos, nicht DBFS für produktive Projekte

  • Vermeide doppelte Logik in Notebooks – lieber Helper-Funktionen extrahieren

  • Dokumentiere den Zweck jedes Notebooks oder Skripts am Anfang

  • Verwende konsistente Namenskonventionen für Tasks, Module und Pfade

  • Wenn möglich: Unit Tests mit pytest + dbx

🚀 Fazit

Die Modularisierung von Notebooks und die Wiederverwendung von Code in Databricks sind entscheidende Erfolgsfaktoren für skalierbare Data-Workflows. Ob über %run, Python-Module oder parametrische Workflows – mit der richtigen Struktur kannst du:

  • Wartungskosten drastisch senken

  • Flexibilität und Wiederverwendbarkeit erhöhen

  • den Grundstein für automatisierte Tests und CI/CD legen

🔧 Modularisierung ist keine Option – sie ist Voraussetzung für produktionsreife Data Engineering-Prozesse.

Andere Artikel

Sprechen wir Daten!

Sprechen wir Daten!

Sprechen wir Daten!