Dependency Injection
Entwurfsmuster zur Entkopplung von Komponenten durch externe Bereitstellung ihrer Abhängigkeiten, fördert Testbarkeit und Modularität.
Klassifikation
- KomplexitätMittel
- AuswirkungTechnisch
- EntscheidungstypArchitektur
- OrganisationsreifeFortgeschritten
Technischer Kontext
Prinzipien & Ziele
Use Cases & Szenarien
Kompromisse
- Übermäßiger Einsatz führt zu schwer nachvollziehbaren Laufzeitkonfigurationen
- Falsche Lebenszyklusbindung kann zu Ressourcenlecks führen
- Hidden dependencies, wenn Abhängigkeiten nicht klar dokumentiert sind
- Bevorzuge Konstruktorinjektion für erforderliche Abhängigkeiten
- Dokumentiere Scopes und Lebenszyklen klar
- Vermeide Service Locator Pattern als Ersatz
I/O & Ressourcen
- Abstrahierte Schnittstellen und Verträge
- Auswahl eines Injektors oder Containers
- Konventions- oder Konfigurationsvorgaben
- Gekapselte, testbare Komponenten
- Zentrale Konfigurationspunkte für Implementierungsbindung
- Dokumentierte Abhängigkeitsgraphen
Beschreibung
Dependency Injection ist ein Entwurfsmuster zur Entkopplung von Komponenten, bei dem Abhängigkeiten von außen bereitgestellt werden. Dadurch steigen Testbarkeit, Modularität und Wiederverwendbarkeit, weil Objekterstellung und -konfiguration vom Verbrauchscode getrennt sind. Einsatzgrenzen, Lebenszyklen und Komplexität sollten bei der Gestaltung beachtet werden.
✔Vorteile
- Erhöhte Testbarkeit durch einfache Austauschbarkeit von Abhängigkeiten
- Geringere Kopplung und klarere Modulgrenzen
- Bessere Wiederverwendbarkeit und Austauschbarkeit von Implementierungen
✖Limitationen
- Erhöhte Komplexität durch zusätzliche Indirektion
- Lebenszyklusverwaltung kann anspruchsvoll werden
- Nicht jede Abhängigkeit eignet sich zur Injektion (z. B. einfache Datenträgerzugriffe)
Trade-offs
Metriken
- Anteil injizierter Abhängigkeiten
Prozentualer Anteil der Abhängigkeiten, die über DI statt direkt erstellt werden.
- Testabdeckung isolierter Komponenten
Maß für Unit-Test-Abdeckung von Komponenten, die durch DI isoliert getestet werden.
- Konfigurationsfehler pro Release
Anzahl von Laufzeitfehlern, die aus falschen DI-Bindings resultieren.
Beispiele & Implementierungen
Constructor Injection in einem Service
Ein Service erhält Repository- und Logger-Abhängigkeiten über den Konstruktor, was Unit-Tests mit Mocks erlaubt.
Spring-Profile für Umgebungsvarianten
Verschiedene Beans werden je nach aktivem Profile geladen, um Test- und Produktionsimplementierungen zu trennen.
Policy-basierte Injektion für Feature-Toggles
Implementierungen werden anhand von Feature-Flags oder Policies gebunden, um experimentelle Funktionalität gezielt zu steuern.
Implementierungsschritte
Schnittstellen definieren und Abhängigkeiten explizit machen
Ein Injektionsverfahren (z. B. Konstruktorinjektion) wählen
Container/Framework integrieren und Bindings konfigurieren
Tests und Migrationspfad schrittweise einführen
⚠️ Technische Schulden & Engpässe
Tech Debt
- Veraltete Bindings, die nicht mehr getestet werden
- Wuchernde Konfigurationsdateien mit inkonsistenten Bindings
- Unklare Ownership der Bindings zwischen Teams
Bekannte Engpässe
Beispiele für Missbrauch
- Injektion primitiver Werte statt Konfigurationsobjekten
- Injection von zu vielen Verantwortlichkeiten in einen Konstruktor
- Dynamische Bindings in Produktion ohne Testing
Typische Fallen
- Unklare Scope-Definitionen führen zu unerwarteten Instanzlebensdauern
- Zu späte Fehlererkennung durch runtime-basierte Bindings
- Versteckte Nebeneffekte bei injizierten Singleton-Abhängigkeiten
Erforderliche Fähigkeiten
Drivers (Architectural Drivers)
Constraints
- • Vorhandene Legacy-APIs ohne Schnittstellen erschweren Injektion
- • Einschränkungen durch Laufzeitumgebung (z. B. eingeschränkte Reflection)
- • Organisatorische Konventionen für Abhängigkeitsverwaltung müssen etabliert sein