Dependency Injection

… oder kurz: DI

Informatik – 3. Semester

Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Warum DI?

Software- und Web-Engineering I · TH Lübeck
Recap
  • Blick zurück: HTML/CSS/JS/TS – was könnt ihr schon einsetzen?
  • Semesterziel: MVP einer Webanwendung mit HTML, CSS, JS/TS und Spring Boot im Backend
  • Heute legen wir das Fundament für sauberes Backend-Design
HTML5 CSS3 JavaScript TypeScript Spring
Logos: upload.wikimedia.org
Software- und Web-Engineering I · TH Lübeck
Warum Dependency Injection?
  • Spontane Implementierungen führen oft zu enger Kopplung
  • Testen wird schwer, wenn Services sich gegenseitig bauen
  • Teamarbeit leidet, weil jeder andere Initialisierungspfade nutzt
  • DI adressiert genau diese Probleme und schafft klare Verträge
Software- und Web-Engineering I · TH Lübeck
Schichten & Abhängigkeiten
Dependency Injection Design Pattern UML
Quelle: https://upload.wikimedia.org/wikipedia/commons/1/10/W3sDesign_Dependency_Injection_Design_Pattern_UML.jpg
Software- und Web-Engineering I · TH Lübeck
Umfrage
  • Habt ihr bereits DI in Projekten eingesetzt? (z.B. Angular, Nest, Spring)
  • Wer hatte schon Probleme mit Mocking/Testing wegen harter Kopplung?
  • Welche Sprache setzt ihr aktuell in Backends ein?
Software- und Web-Engineering I · TH Lübeck
Warum das Thema jetzt?
  • Eure Projekt-Backends entstehen mit Spring Boot, und Spring lebt von DI
  • Gemeinsame Patterns erleichtern Abstimmung zwischen Frontend/Backend-Teams
  • Frühzeitige DI-Disziplin verhindert spätere Refactor-Aufwände im Projekt
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Motivation und Problemstellung

Software- und Web-Engineering I · TH Lübeck
Einfach mal losprogrammieren
  • Typische spontane Lösung: alles in einer Datei / Klasse
  • Klassen erzeugen ihre Abhängigkeiten direkt mit new
  • Globale Singletons / statische Helper überall verteilt
  • Funktioniert am Anfang – wird mit wachsender Codebasis schnell unübersichtlich
Nicht zu empfehlen!
Software- und Web-Engineering I · TH Lübeck
Beispiel ohne DI (TypeScript)
class EmailService {
  sendWelcomeMail(address: string) {
    console.log(`Sende Mail an ${address}`);
  }
}

class UserRegistration {
  private emailService = new EmailService();

  register(email: string) {
    // ... User in DB speichern
    this.emailService.sendWelcomeMail(email);
  }
}
  • UserRegistration entscheidet selbst, welche Implementierung von EmailService genutzt wird
  • Austausch (z.B. Fake-/Testservice) ist nur über Codeänderung möglich
Software- und Web-Engineering I · TH Lübeck
Probleme bei enger Kopplung
  • Wartbarkeit: Jede neue Variante (z.B. anderer Mailanbieter) erfordert Änderungen an vielen Stellen
  • Testbarkeit: Unit-Tests brauchen „echte“ Abhängigkeiten oder aufwändige Workarounds
  • Wiederverwendung: Klassen sind schwer in anderen Kontexten nutzbar
Versteckte Abhängigkeiten: Konstruktion steckt im Code, nicht in der Schnittstelle
Software- und Web-Engineering I · TH Lübeck
Kurze Denkfrage
  • Wie würdet ihr UserRegistration testen, ohne wirklich E-Mails zu versenden?
  • Wo müsstet ihr aktuell Änderungen vornehmen, um einen Fake-Service zu benutzen?
  • Welche Risiken seht ihr, wenn „echte“ Infrastruktur immer mitläuft (Mail, DB)?
Software- und Web-Engineering I · TH Lübeck
Testschmerz sichtbar machen
// Pseudocode für einen Test ohne DI
it("soll Willkommen-Mail versenden", () => {
  const registration = new UserRegistration();
  registration.register("test@example.com");
  // Wie prüfen wir, ob eine Mail verschickt wurde?
  // Logfile parsen? DB abfragen? externen Service mocken?
});
  • Test kennt keine saubere Möglichkeit, Verhalten zu beobachten
  • Oft werden globale Flags, statische Mocks oder komplizierte Test-Setups gebaut
  • Ergebnis: Tests sind brüchig und werden „nervig“ → weniger Tests
Software- und Web-Engineering I · TH Lübeck
Motivation für DI
  • Abhängigkeiten explizit machen statt versteckt erzeugen
  • Erlaubt Austauschbarkeit (z.B. Fake vs. echter Service) ohne Codeänderungen an der Fachlogik
  • Unterstützt saubere Schichtenarchitektur und klarere Verantwortlichkeiten
  • Grundlage für gute Testbarkeit, Wartbarkeit und Teamarbeit im Projekt
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Dependency, IoC, DIP, …

Software- und Web-Engineering I · TH Lübeck
Begriff „Dependency“
  • Eine Dependency ist alles, was eine Klasse/Funktion zum Arbeiten braucht
  • Beispiele: Logger, Datenbankzugriff, E-Mail-Service, Konfiguration
  • Wichtig: Fachlogik soll nicht wissen, wie die Dependency gebaut wird
Class A uses some methods of Class B: it's a dependency
Quelle: https://cdn-media-1.freecodecamp.org/images/1*0P-1JhnUaZeobDUAajIbhA.jpeg
Software- und Web-Engineering I · TH Lübeck
Inversion of Control (IoC)
Klassisch
  • Eine Klasse kontrolliert selbst, welche konkreten Abhängigkeiten sie erstellt
IoC
  • Die Kontrolle wird „umgedreht“ – etwas anderes liefert ihr die Abhängigkeiten
  • DI ist eine konkrete Art, IoC umzusetzen
Statt „ich hole mir, was ich brauche“ → „ich bekomme, was ich brauche“
Software- und Web-Engineering I · TH Lübeck
Dependency Inversion Principle
„High-level modules should not depend on low-level modules. Both should depend on abstractions.“
„Abstractions should not depend on details. Details should depend on abstractions.“
  • Fachlogik hängt von Interfaces/Typen ab, nicht von konkreten Implementierungen
  • DI-Container helfen, diese Abstraktionen zur Laufzeit mit konkreten Objekten zu verknüpfen
Software- und Web-Engineering I · TH Lübeck
Beispiel: Abhängigkeit auf Abstraktion
interface EmailPort {
  send(address: string, body: string): void;
}

class UserRegistrationService {
  constructor(private emailPort: EmailPort) {}

  register(email: string) {
    // ... User in DB speichern
    this.emailPort.send(email, "Willkommen!");
  }
}
  • UserRegistrationService kennt nur das Interface EmailPort, nicht die konkrete Implementierung
  • Später können wir z.B. SmtpEmailService oder FakeEmailService injizieren
Software- und Web-Engineering I · TH Lübeck
Wie Abhängigkeiten auflösen?
Service Locator
  • Klasse fragt zentralen „Locator“ nach Abhängigkeiten (ServiceLocator.get('EmailService'))
  • Problem: versteckte Abhängigkeiten, schwerer zu testen
Dependency Injection
  • Abhängigkeiten werden explizit über Konstruktor/Methoden übergeben
  • Vorteil: Signatur zeigt klar, was zum Arbeiten benötigt wird
Welchen Usecase könnte ein Service Locator haben?
Software- und Web-Engineering I · TH Lübeck
Verständnis-Check
  • Welche Vorteile hat es, wenn eine Klasse nur von Interfaces (Abstraktionen) abhängt?
  • Woran erkenne ich im Code, ob etwas eher Service Locator oder DI ist?
  • Wo habt ihr in eigenen Projekten bereits IoC unbewusst eingesetzt?
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Konstruktor-, Setter-, Interface-Injection

Software- und Web-Engineering I · TH Lübeck
Überblick: Varianten der Injection
Constructor Injection
Setter Injection
Interface (oder Method) Injection
  • Alle verfolgen dasselbe Ziel: Abhängigkeiten von außen zuführen
  • Unterschied: Wann und wie wird die Abhängigkeit gesetzt?
Software- und Web-Engineering I · TH Lübeck
Konstruktor Injection (empfohlen)
class UserRegistrationService {
  constructor(private emailPort: EmailPort) {}

  register(email: string) {
    // ... User speichern
    this.emailPort.send(email, "Willkommen!");
  }
}
  • Abhängigkeiten sind im Konstruktor sofort sichtbar
  • Objekt ist nach Konstruktion vollständig benutzbar („fully initialized“)
  • Gut für Pflicht-Abhängigkeiten
Software- und Web-Engineering I · TH Lübeck
Setter Injection
class UserRegistrationService {
  private emailPort!: EmailPort;

  setEmailPort(emailPort: EmailPort) {
    this.emailPort = emailPort;
  }

  register(email: string) {
    // ... User speichern
    this.emailPort.send(email, "Willkommen!");
  }
}
  • Abhängigkeit kann nachträglich gesetzt/gewechselt werden
  • Geeignet für optionale oder zur Laufzeit wechselbare Dependencies
  • Risiko: Objekt kann in ungültigem Zustand sein, wenn Setter nicht aufgerufen wird
Software- und Web-Engineering I · TH Lübeck
Interface / Method Injection
interface EmailAware {
  injectEmailPort(emailPort: EmailPort): void;
}

class UserRegistrationService implements EmailAware {
  private emailPort!: EmailPort;

  injectEmailPort(emailPort: EmailPort) {
    this.emailPort = emailPort;
  }
}
  • Klasse definiert explizit eine „Injection-Methode“ über ein Interface
  • Wird in manchen Frameworks genutzt, um bestimmte Hooks zu markieren
  • Im reinen Anwendungscode eher selten nötig
Software- und Web-Engineering I · TH Lübeck
Vor- und Nachteile im Vergleich
Vorteile Nachteile
Konstruktor Injection Klarheit, Immutabilität, Testbarkeit mehr Parameter bei vielen Dependencies
Setter Injection Flexibilität Gefahr unvollständiger Objekte
Interface Injection explizite Markierung zusätzliche Komplexität, Framework-Lastigkeit
In vielen Projekten: Constructor Injection als Standard, Setter/Interface nur bei speziellen Fällen
Software- und Web-Engineering I · TH Lübeck
Gedankenexperiment
  • Welche Injection-Form würdet ihr für einen Logger wählen? Warum?
  • Und für einen FeatureToggleService, dessen Implementierung zur Laufzeit wechseln können soll?
2 Min
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Lebenszyklen und Scopes

Software- und Web-Engineering I · TH Lübeck
Warum Lebenszyklen wichtig sind
  • Gleiche Klasse kann in verschiedenen Kontexten unterschiedliche Lebensdauer haben
    • Konfiguration (eher einmalig)
    • HTTP-Request (kurzlebig)
    • Datenbankverbindung (wiederverwendet)
  • Falscher Scope führt zu Bugs
    • geteilte Zustände
    • Memory-Leaks
    • Performance-Probleme
Software- und Web-Engineering I · TH Lübeck
Typische Scopes (allgemein)
Transient Neue Instanz bei jeder Anforderung (Injection)
Singleton Eine Instanz pro Anwendung / Container
Request Scoped Eine Instanz pro HTTP-Request
Weitere mögliche Scopes: Session, Thread, Job, Modul etc. (frameworkabhängig)
Software- und Web-Engineering I · TH Lübeck
Singleton im Beispiel
class ConfigService {
  constructor(private readonly config: Record<string, string>) {}

  get(key: string) {
    return this.config[key];
  }
}

// Idee: ConfigService einmal erstellen und überall wiederverwenden
  • Sinnvoll für zustandsarme, teure oder globale Ressourcen (z.B. Konfiguration, Logger)
  • Achtung bei mutablem Zustand: geteilte Daten über Threads/Requests hinweg
Software- und Web-Engineering I · TH Lübeck
Transient & Request-Scoped
Transient
  • Jedes Mal eine neue Instanz → gut für kurzlebige, zustandsbehaftete Objekte
  • Beispiel: Builder-Objekte, temporäre Berechnungen
Request Scoped
  • Eine Instanz pro HTTP-Request → teilt sich Zustand innerhalb der Request-Verarbeitung
  • Beispiel: CurrentUserContext, RequestMetrics
Software- und Web-Engineering I · TH Lübeck
Gedankenexperiment
  • Welche Scope-Wahl (Singleton / Request / Transient / Anderes) erscheint sinnvoll für:
    • DatabaseConnectionPool
    • ShoppingCart in einem klassischen Webshop
    • FeatureFlagCache, das periodisch aktualisiert wird
2 Min
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Allgemeine Definition

Software- und Web-Engineering I · TH Lübeck
Was macht ein DI-Container?
  • Verwaltet Registrierungen von „welcher Typ liefert welche Abhängigkeit?“
  • Erstellt Objekte (inkl. ihrer Dependencies) nach definierten Regeln
  • Beachtet Scopes/Lebenszyklen (Singleton, Request, ...)
  • Löst Abhängigkeiten auf, ohne dass Aufrufer new schreiben müssen
Software- und Web-Engineering I · TH Lübeck
Grundideen eines Containers
  • Registrierung: „Wenn jemand EmailPort braucht, nimm SmtpEmailService“
  • Auflösung: „Gib mir eine Instanz von UserRegistrationService“
  • Konfiguration: Wie sind Scopes, Parameter, Umgebungen definiert?
Oft zusätzlich: Module/Packages, die zusammen registriert werden
Software- und Web-Engineering I · TH Lübeck
Einfacher Container (pseudomäßig)
type Token<T> = string; // Vereinfachung

class Container {
  private singletons = new Map<Token<any>, any>();
  private factories = new Map<Token<any>, () => any>();

  registerSingleton<T>(token: Token<T>, factory: () => T) {
    this.factories.set(token, factory);
  }

  resolve<T>(token: Token<T>): T {
    if (!this.singletons.has(token)) {
      const instance = this.factories.get(token)!();
      this.singletons.set(token, instance);
    }
    return this.singletons.get(token) as T;
  }
}
Software- und Web-Engineering I · TH Lübeck
Container mit Abhängigkeiten
const container = new Container();

container.registerSingleton<EmailPort>(
  "EmailPort",
  () => new SmtpEmailService(),
);
container.registerSingleton<UserRegistrationService>(
  "UserRegistrationService",
  () => new UserRegistrationService(container.resolve("EmailPort")),
);

const registrationService = container.resolve<UserRegistrationService>(
  "UserRegistrationService",
);
  • Container kennt, wie UserRegistrationService gebaut wird
  • Aufrufer brauchen nur noch resolve – keine new-Ketten mehr im Anwendungscode
Software- und Web-Engineering I · TH Lübeck
Module & Konfigurationsquellen
  • Module bündeln thematisch zusammengehörige Registrierungen (z.B. „User“, „Billing“, „Auth“)
  • Konfigurationsquellen:
    • Umgebungsvariablen (NODE_ENV, DB-URL, ...)
    • Konfigurationsdateien (JSON, YAML, .env)
    • Feature Flags
  • Container/Framework liest diese Quellen und entscheidet, welche Implementierungen registriert werden
Software- und Web-Engineering I · TH Lübeck
DI-Container
  • Wo seht ihr Vorteile eines zentralen DI-Containers gegenüber „einfach überall new“?
  • Welche Risiken entstehen, wenn der Container zu „magisch“ wird?
  • Wie könnte man Module für euer Semesterprojekt zuschneiden (z.B. User, Board, Notifications)?
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Best Practices und Anti-Patterns

Software- und Web-Engineering I · TH Lübeck
Best Practices
  • Abhängigkeiten explizit machen (Konstruktorparameter, Interfaces)
  • Constructor Injection als Standard wählen
  • Wenige, klar geschnittene Services statt „God Objects/Services“
  • DI nutzen, um Testbarkeit zu verbessern, nicht zu verstecken
Software- und Web-Engineering I · TH Lübeck
Anti-Pattern: God Service
  • Ein Service kennt „alles“: User, Bestellungen, Mails, Logging, ...
  • Symptome:
    • Riesige Klasse, viele hundert Zeilen
    • Viele Dependencies im Konstruktor
    • Änderungen an einer Fachlichkeit brechen andere
Besser: Nach Bounded Contexts / Modulen schneiden und aufteilen
Software- und Web-Engineering I · TH Lübeck
Anti-Pattern: Over-Injection
  • Zu viele Dependencies in einer Klasse (z.B. > 4 – 5 Services)
  • Hinweis auf fehlende Verantwortlichkeits-Trennung
  • Folge: Schwer zu testen, hoher kognitiver Load
  • Gegenmittel: Klasse in kleinere Komponenten zerlegen, Schnittstellen nachschärfen
Software- und Web-Engineering I · TH Lübeck
Anti-Pattern: Versteckte globale Zustände
  • Statische Singletons / globale Variablen statt sauberer DI
  • Erschwert Tests (Zustand bleibt zwischen Tests erhalten)
  • Erhöht Kopplung und macht Reihenfolge/Timing relevant
  • Empfehlung: Globals vermeiden, explizite Injection bevorzugen
Software- und Web-Engineering I · TH Lübeck
Zyklische Abhängigkeiten
Probleme
  • Container kann Graph nicht mehr sauber auflösen
  • Fachlich oft ein Hinweis auf schlechte Schnittstellen
Lösung
  • Gemeinsame Logik in neuen Service auslagern (z.B. UserOrderFacade)
  • Events oder Ports/Adapter nutzen
Beispiel: UserService braucht OrderService, und OrderService braucht UserService
Software- und Web-Engineering I · TH Lübeck
Null Object & Default-Implementierungen
  • Bei optionalen Dependencies: Null Object Pattern statt null/undefined prüfen
  • Beispiel: NoopMetricsService, der nichts tut, aber Interface erfüllt
  • Vorteil: Weniger if (metrics)-Abfragen, klarere Logik
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Mini-Übungen mit Diskussionen

Software- und Web-Engineering I · TH Lübeck
Abhängigkeiten identifizieren
class OrderController {
  constructor(private readonly db: DbClient) {}

  async createOrder(req: any, res: any) {
    const userId = req.headers["x-user-id"];
    const body = req.body;
    const logger = new ConsoleLogger();

    logger.info("Creating order for", userId);
    const orderId = await this.db.insert("orders", { userId, body });
    res.json({ orderId });
  }
}
  • Markiert alle Abhängigkeiten (explizit und implizit)
  • Überlegt, welche davon sinnvoll per DI injiziert werden sollten
2 Min
Software- und Web-Engineering I · TH Lübeck
Kleines Refactoring
  • ConsoleLogger soll per DI injiziert werden
    • Interface Logger definieren
    • OrderController über Konstruktor mit Logger versorgen
    • ConsoleLogger als konkrete Implementierung im Container/Setup registrieren
  • Wie würdet ihr einen FakeLogger für Tests einhängen?
2 Min
Software- und Web-Engineering I · TH Lübeck
Scope-Zuordnung
  • Ordnet gemeinsam zu
    • ConfigService → eher Singleton / Transient / Request?
    • RequestContext (aktuell eingeloggter User) → ?
    • DiscountCalculator ohne eigenen Zustand → ?
1 Min
Software- und Web-Engineering I · TH Lübeck
Quiz!
  • Was ist der Unterschied zwischen IoC und DI?
  • Nenne zwei Vorteile von Constructor Injection gegenüber Setter Injection
  • Warum sind globale Singletons oft problematisch für Tests?
  • Woran erkennst du im Code, dass ein Service eventuell ein „God Service“ geworden ist?
  • Welche Teile eures Semesterprojekts profitieren am stärksten von sauberem DI-Design?
  • Wo seht ihr aktuell potenzielle Problemstellen (z.B. enge Kopplung, globale States)?
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Framework-Spotlight: Spring Boot

Software- und Web-Engineering I · TH Lübeck
Spring Boot: Kurzer Überblick
  • Java-/JVM-Framework basierend auf dem Spring-Ökosystem
  • Starker Fokus auf Konventionen, Auto-Konfiguration und DI
  • Nutzt einen leistungsfähigen DI-Container (Spring Context)
  • Auch hier: Nur ein erster Blick auf DI – Details folgen in eigener Spring-Vorlesung
Software- und Web-Engineering I · TH Lübeck
Komponenten-Scan & Stereotypen
  • Spring findet Beans über Annotationen und Package-Scan
  • Typische Annotationen:
    • @Component – generische Komponente
    • @Service – Service-Schicht
    • @Repository – Datenzugriffsschicht
    • @Controller / @RestController – Web-Schicht
  • Container registriert diese Beans und macht sie injizierbar
Software- und Web-Engineering I · TH Lübeck
Spring: Service + Controller
// UserService.java
import org.springframework.stereotype.Service;

@Service
public class UserService {
  public List<String> findAll() {
    return List.of("alice", "bob");
  }
}
// UserController.java
import org.springframework.web.bind
  .annotation.GetMapping;
import org.springframework.web.bind
  .annotation.RestController;

@RestController
public class UserController {

  private final UserService userService;

  // Constructor Injection
  public UserController(UserService userService) {
    this.userService = userService;
  }

  @GetMapping("/users")
  public List<String> findAll() {
    return userService.findAll();
  }
}
Software- und Web-Engineering I · TH Lübeck
Java-Konfiguration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MailConfig {

  @Bean
  public MailClient mailClient() {
    return new SmtpMailClient("smtp.example.com");
  }
}
  • @Configuration-Klasse definiert Beans explizit
  • @Bean-Methoden geben Objekte zurück, die im Container registriert werden
  • Erinnert an unsere Container.register(...)-Beispiele, nur deklarativ
Software- und Web-Engineering I · TH Lübeck
Profiles & Umgebungen
  • Spring Profiles erlauben verschiedene Bean-Konfigurationen für unterschiedliche Umgebungen
    • z.B. dev, test, prod
  • Beispiel:
    • In dev: FakeMailClient
    • In prod: SmtpMailClient
  • Aktivierung über Properties/Umgebungsvariablen
  • Passt direkt zu unserem Thema „Austauschbare Implementierungen per DI“
Software- und Web-Engineering I · TH Lübeck
Verbindung zu unseren DI-Konzepten
  • Ebenfalls Constructor Injection als Best Practice
  • Spring Context = DI-Container mit Registrierung, Auflösung, Scopes
  • Komponenten-Scan und @Configuration-Klassen übernehmen die Container-Konfiguration
Detaillierte Themen (Scopes, Lifecycle, Testing mit Spring) folgen in der Spring-Vorlesung
Software- und Web-Engineering I · TH Lübeck
1 Warm-up & Kontext
2 Motivation & Problemstellung
3 Begriffe & Grundlagen
4 Formen der Injection
5 Lebenszyklen & Scopes
6 DI-Container
7 Best Practices & Anti-Patterns
8 Mini-Übungen
9 Spotlight: Spring Boot
10 Ausblick & Takeaways

Was in den nächsten Vorlesungen folgt

Software- und Web-Engineering I · TH Lübeck
Zusammenfassung
  • DI löst Probleme von enger Kopplung, Testbarkeit und Wartbarkeit
  • Kerngedanken: IoC, Dependency Inversion, explizite Abhängigkeiten
  • Wichtige Bausteine: Injection-Formen, Scopes, DI-Container
  • Spring Boot setzt diese Konzepte in der Praxis um, Angular oder NestJS ebenso
Software- und Web-Engineering I · TH Lübeck
Blick auf die nächsten Vorlesungen
Als nächstes: REST und Web-APIs
  • Wie Front-End und Back-End über HTTP und JSON miteinander reden
  • Ressourcen, Methoden, Statuscodes, ganz ohne Framework
Danach: Spring Boot, Teil 1 und 2
  • Spring Context, Konfiguration, Repositories, REST-Controller
  • Testing mit Spring und DI
Software- und Web-Engineering I · TH Lübeck

- Einstieg: Thema nennen, Kuerzel DI einfuehren - Nicht auf dem Titel verweilen

- Sektionsauftakt: Warm-up und Kontext - Roadmap kurz zeigen: 10 Stationen, heute alles ein roter Faden

- Kurz vorstellen, Bezug zum Projekt herstellen - Nachfragen, welche Backends/Frameworks im Projekt genutzt werden - Ziel der Folie: alle im Thema abholen und Semesterziel verankern

- Mit einem Beispiel aus der Praxis starten (z.B. "Wir haben am Ende alles in einem Service gehabt...") - Herausarbeiten lassen, dass Kopplung, Testbarkeit und Teamarbeit leiden - Noch keine Loesung im Detail verraten

- Diagramm nutzen, um Bauchgefuehl zu erzeugen - Injector baut ServiceA1/ServiceB1 und injiziert sie in den Client

- Schnell, interaktiv halten - Ergebnisse laut wiederholen ("Viele von euch..."), um spaeter Bezug zu nehmen - Kein langes Ausdiskutieren, nur Stimmungsbild

- Explizit Bezug zu den beiden Spring-Boot-Vorlesungen herstellen - DI ist ein Querschnittsthema, das in vielen Technologien wiederkehrt - Motivieren: "Wenn ihr das heute gut versteht, spart ihr euch spaeter viel Frust."

- Typische Erfahrungen der Studierenden einsammeln ("Wer hat sowas schon gemacht?") - Diese Vorgehensweise ist nachvollziehbar, kippt aber ab einer bestimmten Groesse

- Typische Erfahrungen der Studierenden einsammeln ("Wer hat sowas schon gemacht?") - Diese Vorgehensweise ist nachvollziehbar, kippt aber ab einer bestimmten Groesse

- Code einmal laut "lesen", auf Zeile mit new EmailService() zeigen - Frage: "Was passiert, wenn wir eine andere Implementierung brauchen?" - Noch keine Loesung an dieser Stelle, nur Problemgefuehl verstaerken

- Stichpunkte mit Beispielen aus dem Code verknuepfen (Testbarkeit, Wartbarkeit, Wiederverwendung) - Studierende kurz ergaenzen lassen, ob ihnen weitere Probleme einfallen

- 1-2 Minuten Silent Thinking oder Austausch in Zweiergruppen - Erwartung: Tests ohne DI werden sehr umstaendlich - Anschliessend 1-2 Antworten einholen und auf die naechste Folie ueberleiten

- Pseudocode als Aufhaenger: gemeinsam fragen "Wie wuerden wir das jetzt testen?" - Typische Antworten (Log pruefen, Fake-Server) notieren und als fragil markieren - Dann als Motivation fuer saubere DI nutzen

- Bruecke schlagen: "Wir wollen Abhaengigkeiten sichtbar und austauschbar machen." - Betonung auf Testbarkeit, Teamarbeit und klare Vertraege - Diese Punkte immer wieder als roter Faden aufgreifen

- Jetzt Begriffe sauber legen: Dependency, IoC, DIP - Begriffe sind das Fundament fuer die Framework-Teile spaeter

- Begriff sehr einfach halten, viele Beispiele nennen (Logger, Repo, Config) - Studierende bitten, spontan weitere Dependencies aus ihren Projekten zu nennen

- Mit Alltagsmetapher arbeiten (z.B. Restaurant: Kochen selbst vs. bekocht werden) - DI ist eine konkrete Art, diese Umkehr der Kontrolle umzusetzen - Nicht zu tief in Theoriediskussion abgleiten

- Kurz auf SOLID verweisen, nicht jedes Detail erklaeren: - Single Responsibility (Einzelverantwortung), Open/Closed (Offen/Geschlossen) - Liskov Substitution, Interface Segregation, Dependency Inversion - Fokus: High-Level haengt von Abstraktionen ab, nicht von Implementierungen - Mit einfachem Beispiel (Interface vs. konkrete Klasse) verankern

- Code Schritt fuer Schritt erklaeren - Nachfragen: "Wo stecken hier die Abstraktionen? Wo waeren konkrete Implementierungen denkbar?" - Erwartung: EmailPort ermoeglicht die Austauschbarkeit

- Kurz nachfragen, ob jemand bereits einen Service Locator gesehen hat (z.B. alte Frameworks) - DI macht Abhaengigkeiten sichtbar in Signaturen, Service Locator versteckt sie - Keine religioese Debatte, nur Problemstellen benennen

- Fragen nacheinander stellen, jeweils kurz 1-2 Antworten einholen - Falls wenig Resonanz: Antworten selbst skizzieren und betonen, warum Abstraktionen hilfreich sind

- Vorteil "fertig initialisiertes Objekt" betonen - Frage in die Runde: "Was spricht gegen viele Konstruktorparameter?" - Erwartete Antwort: zu viele Dependencies als Design-Signal

- Drei Formen klar nebeneinanderstellen, alle haben dasselbe Grundziel - Ankuendigen: Constructor Injection wird der Standard, die anderen bleiben als Werkzeuge im Kopf

- Vorteil "fertig initialisiertes Objekt" betonen - Frage in die Runde: "Was spricht gegen viele Konstruktorparameter?" - Erwartete Antwort: zu viele Dependencies als Design-Signal

- Implementierung laesst sich zur Laufzeit wechseln - Gleichzeitig Risiko unvollstaendig initialisierter Objekte - Beispiel fragen: "Wo koennte das sinnvoll sein?" (z.B. optionaler Logger)

- Kommt eher in Frameworks vor als im eigenen Domaenenmodell - Nicht zu sehr vertiefen, nur als Ergaenzung zeigen - Im Alltag selten explizit noetig

- Tabelle als Gegenueberstellung nutzen - Studierende aktiv fragen, welche Variante sie spontan bevorzugen und warum - Constructor Injection als Default-Option klar empfehlen

- Aufgabe stellen und 2 Minuten fuer Austausch geben - Loesungserwartung: Logger meist Constructor oder ggf. Setter - FeatureToggleService je nach Dynamik eher Setter/wechselbare Implementierung - Ergebnisse kurz im Plenum sammeln

- Singleton ist nicht automatisch schlecht, es kommt auf den Zustand an - Frage: "Was passiert, wenn config zur Laufzeit geaendert wird?" Risiken von Mutable State anreissen

- Mit einem Bug-Beispiel einsteigen ("Logger merkt sich zu viel", "User-Kontext wird geteilt") - Lebensdauer ist genauso wichtig wie die Wahl der Abhaengigkeiten

- Begriffe Singleton, Transient, Request sauber definieren - Auf bekannte Frameworks beziehen (Angular, Nest, Spring) - Kurz nachfragen, wo die Studierenden diese Begriffe schon gesehen haben

- Singleton ist nicht automatisch schlecht, es kommt auf den Zustand an - Frage: "Was passiert, wenn config zur Laufzeit geaendert wird?" Risiken von Mutable State anreissen

- Unterschiede mit kleinen Beispielen illustrieren (RequestContext vs. Berechnungsobjekt) - Viele Frameworks bringen diese Scopes bereits mit, DI hilft beim bewussten Einsatz

- Loesungserwartung diskutieren: - DatabaseConnectionPool: Singleton/aehnlicher globaler Scope - ShoppingCart: Session/Request/Benutzer-spezifisch - FeatureFlagCache: Singleton mit periodischer Aktualisierung - Antworten der Studierenden ernst nehmen, Unterschiede begruenden

- Zeigen, wie der Container selbst beim Bauen Abhaengigkeiten aufloest - Frage: "Wo wird entschieden, welche konkrete Implementierung genutzt wird?" - Erwartung: in der Registrierung, nicht in der Fachlogik

- Begriff "Container" entmystifizieren: im Kern nur ein smarter Objekt-Bauer - Beispiele aus bekannten Frameworks nennen (NestJS, Spring, Angular) - Ziel: Angst vor "Magie" nehmen

- Registrierung vs. Aufloesung klar machen - Frage: "Wuerdet ihr lieber manuell ueberall new schreiben oder zentral konfigurieren?" - Vorteile fuer Konfiguration sammeln

- Code nicht Zeile fuer Zeile implementieren lassen, die Idee erklaeren: - Map von Tokens zu Factories, Lazy-Bau von Singletons - Echte Frameworks haben mehr Features, bauen aber auf diesem Prinzip auf

- Zeigen, wie der Container selbst beim Bauen Abhaengigkeiten aufloest - Frage: "Wo wird entschieden, welche konkrete Implementierung genutzt wird?" - Erwartung: in der Registrierung, nicht in der Fachlogik

- Das Konzept auf ihr Projekt mappen: Welche Module waeren sinnvoll (User, Board, Notifications ...)? - Kurz Ideen sammeln - Konfigurationsquellen mit realen Beispielen verbinden (ENV-Variablen, .env-Datei)

- Offene Diskussion zulassen, aber zeitlich im Blick behalten - Betonen: Container darf nicht "magisch alles koennen", sondern soll bewusst eingesetzt und verstanden werden

- Von den Konzepten zu Leitplanken fuer das eigene Projekt

- Leitplanken als Checkliste fuer das Projekt vorstellen - Vorschlagen, diese Punkte in Code-Reviews explizit zu verwenden ("Hat die Klasse klare Dependencies?")

- Beispiele aus typischen Projekten nennen (z.B. UserService macht alles) - Studierende fragen, ob sie solche Klassen kennen - Loesungsidee: nach Bounded Contexts und Verantwortlichkeiten schneiden

- Als Geruchsindikator darstellen: viele Dependencies, Klassendesign pruefen - Bei mehr als 4-5 Dependencies kritisch werden und pruefen, ob aufgeteilt werden kann

- Auf statische Singletons und globale Variablen eingehen - Typische Testprobleme schildern (Zustand bleibt zwischen Tests) - Alternative: explizite Injection und klarer Lebenszyklus

- Ein einfaches Beispiel skizzieren (User/Order) - Zyklen sind technisch (Container-Fehler) und fachlich ein Warnsignal - Loesung ueber neue Fassade oder Events

- Null Object Pattern kurz erklaeren: Default-Implementierung statt ueberall if-Checks - Beispiel NoopLogger/NoopMetrics - DI ist ideal, um solche Defaults zentral einzuhaengen

- Jetzt selbst anwenden: drei kurze Uebungen plus Quiz

- Erwartete Loesung: Dependencies u.a. DbClient, ConsoleLogger, req, res - Diskussion: was sollte per DI kommen (DbClient, Logger), was bleibt Framework-Infrastruktur (req/res) - Ergebnis sichtbar notieren - Nach der Arbeitsphase gemeinsam markieren: new ConsoleLogger() im Code ist ein Hinweis auf fehlende DI

- Loesungsskizze muendlich entwickeln: - Interface Logger mit info() - Konstruktor nimmt Logger entgegen - Container registriert ConsoleLogger - Optional fragen, wie ein FakeLogger im Test aussieht (z.B. Array von Messages sammeln)

- Erwartete Zuordnung: - ConfigService: meist Singleton - RequestContext: Request/Session - DiscountCalculator ohne Zustand: oft Transient oder stateless Service - Diskussionsunterschiede zwischen Teams zulassen und begruenden lassen

- Fragen nacheinander stellen, Studierende antworten lassen - Falls unsicher: Stichworte am Whiteboard sammeln (IoC vs DI, Vorteile Constructor Injection, Probleme globaler Singletons, Symptome God Service)

- Zweiter Framework-Blick: dieselben Prinzipien auf der JVM

- Nur Ueberblick, kein Volltutorial; die Details kommen in Spring Boot Teil 1 und 2 - Studierende mit Java-Erfahrung kurz berichten lassen, wo sie Spring/Spring Boot schon gesehen haben

- Anhand der Annotationen erklaeren, wie der Container weiss, was er registrieren soll - Parallele zu Annotationen als Registrierung und zum eigenen Container ziehen - Nicht alle Annotationen im Detail erklaeren, nur Rollen skizzieren

- Constructor Injection im Java-Code hervorheben - Frage: "Was wuerde passieren, wenn wir hier zusaetzlich @Autowired am Feld nutzen?" - Gelegenheit, kurz auf Field Injection vs Constructor Injection hinzuweisen

- Zeigen, dass hier explizit Beans registriert werden, aehnlich zu unseren Container-Registrierungen - Beispiel "FakeMailClient vs SmtpMailClient" andeuten und als Uebergang zu Profiles nutzen

- Konzepte an praktischen Szenarien erklaeren (dev/test/prod) - Studierende fragen, wie sie aktuell Test- und Prod-Konfiguration unterscheiden - Bruecke schlagen zu austauschbaren Implementierungen via DI

- Wieder betonen: dieselben Prinzipien, andere Sprache/Framework - Verstaendnis der DI-Kernideen hilft, sich in verschiedenen Stacks schnell zurechtzufinden

- Zum Abschluss: zusammenfassen und den Bogen zu den naechsten Vorlesungen schlagen

- Kernaussagen noch einmal klar und langsam formulieren - Studierende bitten, in eigenen Worten zu wiederholen, was DI fuer sie bedeutet - Offene Fragen notieren

- Explizit ankuendigen, was praktisch erwartet wird (Live-Coding, Workshops) - Gelegenheit geben, Wuensche zu Themen zu aeussern (z.B. Testing, spezifische Framework-Fragen)