Dienstag, März 18, 2008

Software-Architektur mit Dependency-Injection

Überblick

Dieser Artikel beschreibt meine Erfahrungen mit Dependency-Injection (DI) in mehrjährigen Großprojekten. Dabei geht es vor allem um die Best-Practices für Entwurf und Architektur.

Wir haben vor allem handprogrammierte DI, Pico-Container und Java-Server-Faces als DI-Container eingesetzt. Der Artikel bezieht sich primär auf Pico-Container. Die „Erkenntnisse“ lassen sich aber auf andere DI-Container übertragen.

In einem Großprojekt haben wir ein System mit vielen Singletons schrittweise auf Pico-Container umgestellt. Wir haben einige größere Refactorings in dem Projekt durchgeführt und bei vielen war der Nutzen letztlich zweifelhaft. Der Umbau auf Pico-Container gehört aber definitiv zu den Umbauten, die sich deutlich gelohnt haben.

Zielsetzungen

Mit DI kann man eine Reihe von Zielen erreichen:

  • Entwurf entkoppeln
  • Abhängigkeiten explizieren
  • Testbarkeit verbessern
  • Singletons und static-Attribute eliminieren

Entwurf entkoppeln

DI führt nicht automatisch zu einer Entkopplung der Systemkomponenten. Man kann sich auch mit DI eigenartige und unwartbare Abhängigkeitsgeflechte zusammenbauen. Ohne DI hingegen ist es in der Praxis fast unmöglich, entkoppelte Systeme zu bauen. In diesem Sinne ist DI eine notwendige, aber keine hinreichende Bedingung für Entkopplung. Wenn man zusätzlich zu DI noch Interfaces und testgetriebene Entwicklung einsetzt, bekommt man aber ohne größere Anstrengungen ein gut entkoppeltes System.

Abhängigkeiten explizieren

DI macht die Abhängigkeiten von Objekten an ihrer Schnittstelle deutlich. Dadurch ist erstmal natürlich nur ein wenig Dokumentation gewonnen. In unseren Projekten hat dieses kleine bisschen Mehr an Dokumentation aber erheblichen Einfluss darauf gehabt, wie gut man Entwürfe verstehen konnte.

Testbarkeit verbessern

Durch die Entkopplung ließen sich die einzelnen Komponenten besser testen. Hier fällt insbesondere die Harmonie von DI mit Mock-Testen[1] positiv auf. Zusammen mit testgetriebener Entwicklung entsteht ein schlagkräftiges Trio.

Nicht zuletzt laufen so entkoppelte Tests viel schneller ab als klassisch erstellte Unit-Tests (in einigen Fällen konnten wir Testlaufzeiten von mehreren Minuten auf unter 10 Sekunden reduzieren).

Singletons und static-Attribute eliminieren

Setzt man DI konsequent ein, braucht man weder Singletons noch irgendeine andere Form statischer Attribute. Dadurch werden Zustandsabhängigkeiten im System reduziert, so dass sich Systemteile besser unabhängig voneinander wieder verwenden und testen lassen.

In unseren Projekten sind durch die Umstellung von Singletons auf DI eine ganze Reihe eigenartiger Phänomene beim Ausführen und beim Testen verschwunden.

Pico-Container im Code

Pico-Container ist ein sehr leichtgewichtiger DI-Container, der programmatisch konfiguriert wird. Daher können programmatisch DI-Container erzeugt und an andere Objekte übergeben werden. Da ist eine Fußangel versteckt: Man sollte Pico-Container minimal-invasiv benutzen. Das bedeutet, dass möglichst wenig Code Kenntnis davon haben soll, dass Pico-Container (oder ein anderer DI-Container) eingesetzt wird. Folglich sollten die Pico-Container nur im Startup erzeugt werden. Weitere Klassen dürfen nicht vom Pico-Container abhängen. In der Schichtung des Systems liegt der Pico-Container also ganz oben.

Pico-Container in Tests

In Unittests hat Pico-Container nichts verloren. Sind die getesteten Klassen so kompliziert miteinander verflochten, dass man sie manuell nicht zusammenstecken kann, ist Refactoring angesagt – und zwar schleunigst.

Welche Klassen registriert man beim DI-Container?

In unseren Projekten haben sich drei Typen von Klassen etabliert, die wir typischerweise beim DI-Container registrieren.

  • Fabriken
  • Services
  • Objekte, die nur einmal existieren dürfen (ehemalige Singletons)

Wofür baut man Fabriken?

Wenn ein Objekt andere Objekte erzeugen will, kann man diese Objekte nicht über DI hineinreichen – man weiß ja noch nicht, wie viele Objekte erzeugt werden. In solchen Fällen reicht man stattdessen Fabriken per DI in das Objekt.

Fabriken werden häufig mit static-Methoden implementiert. Wenn man die Fabriken beim DI-Container registriert, wäre das jedoch einigermaßen witzlos. Also gilt: Fabriken sollten keine static-Methoden enthalten – static-Attribute sowieso nicht.

In vielen Projekten habe ich beobachtet, dass viel zu selten Fabriken benutzt werden! Fabriken (ohne static-Methoden) helfen bei DI und erleichtern das Testen mit Mocks ungemein[2]. Im Zweifel würde ich lieber eine Fabrik zuviel spendieren.

Abschluss

Unsere Erfahrungen mit Pico-Container haben wir in erster Linie in Rich-Clients gesammelt. Wir haben Pico-Container serverseitig nicht verwendet. Es sollte jedoch möglich sein, weil der Pico-Container sehr leichtgewichtig ist. Der Overhead für die Erzeugung bei jedem Request oder EJB-Aufruf sollte in den meisten Anwendungen zu verkraften sein. Wenn der Overhead zu groß wird, kann man den Pico-Container mind. bei Webanwendungen auch in der Session speichern.

Inzwischen kommen viele Server-Infrastrukturen aber bereits mit eigenen DI-Containern (Spring, JSF, EJB 3), so dass man serverseitig i.d.R. wahrscheinlich nicht in die Verlegenheit kommt, Pico-Container einzusetzen.

Referenzen



[1] z.B. mit Easy Mock

[2] Beim Testen gilt: Jedes Testproblem kann durch eine weitere Indirektion gelöst werden.

Post bewerten

2 Kommentare:

Stefan Roock hat gesagt…

Ich habe von Ullrich Schäfer einen Kommentar bekommen:

Hallo,
[...]
Ich bin letztens über das Guice Framework zur DI gestoßen. Es wurde
bei Google entwickelt und mache einen guten Eindruck. Besonders mag
ich, dass man sich nicht mit XML herumschlagen muss, sonder auf Java
Annotationen zurückgreifen kann.

http://code.google.com/p/google-guice/

Vielleicht ist dies ja auch interessant für Ihre Leser. Speziell das,
auf der Seite verlinkte Video bietet einen guten Einblick und ein paar
nette Beispiele für DI.

Beste Grüße
Ullrich Schäfer

Ullrich hat gesagt…

Link zum klicken http://code.google.com/p/google-guice/

(Zum Test, da Kommentieren vorher nicht möglich)