HowTo: Dependency Injection & Service Locator

image Vor gut zwei Wochen hat Alexander Groß bei der .NET Usergroup Dresden das Thema Dependency Injection in den Raum geworfen und es auch sehr anschaulich an Beispielen verdeutlicht. Er zeigte den IoC Container Castle Windsor und warum ein ServiceLocator keine so gute Idee ist. Mit dem Blogpost versuche ich mal diese scheinbare Raketenwissenschaft etwas aufzuschlüsseln.

Dependency Injection? Service Locator? WTF?

Wenn man über das Thema sich etwas beließt, dann kommt man früher oder später zu den riesigen Erklärungen von Martin Fowler (ServiceLocator vs. DI). Das ist bestimmt toll beschrieben und ich hab das schon x-mal angefangen, aber bis zu dem .NET Usergroup-Treffen war es mir noch nicht ganz bewusst wie das praktisch denn nun aussieht. Darum versuch ich es mal ganz einfach.

Worum geht es hier eigentlich?

Jede Software hat irgendwelche “Abhängigkeiten”. Sei es eine Datenbank, sei es das Filesystem auf dem zugegriffen wird oder sei es ein interner Dienst oder Service der genutzt wird. Damit man das komplizierte Gebilde später auch vielleicht irgendwie testen kann, empfiehlt es sich auf eine Schnittstelle zu programmieren.
Kleines Szenario: Wir haben ein Formular wo sich User anmelden können. Nach dem anmelden soll eine Mail verschickt werden. Die Daten validieren und verarbeiten macht der “UserService“. Dieser benutzt zum Mails versenden etwas vom Typ “IMailService“.
Jetzt kommt aber genau der interessante Punkt: Woher soll unser UserService wissen, woher er eine Instanz vom Typ “IMailService” bekommen soll?

Variante 1: Service Locator

Achtung: Jetzt folgt Code, wo der eine oder andere mit dem Kopf schüttelt -  der Code ist heute noch so im Einsatz ;)

Die erste Idee wie man solche Abhängigkeiten “kontrollieren” kann wäre wenn man eine Art “Gott-Klasse”/”Setup” hat, in dem man die Komponenten registriert. Mit dieser “Gott-Klasse” könnten wir uns auf Befehl jeden Typen erstellen:

    public class InstanceFactory : IInstanceFactory
    {
        public T GetInstanceOf<T>()
        {
            if (typeof(T) == typeof(IMailService))
            {
                return (T)(object)new EmailService();
            }
        }
    }

An der Stelle könnte man natürlich auch auf eine XML Datei oder irgendwas anderes zugreifen.

Angewendet würde diese Klasse im UserService dann so:

IInstanceFactory factory = new InstanceFactory();
IMailService srv = factory.GetInstanceOf<IMailService>();

Diese “Gott-Klasse” könnte natürlich auch eine statische Klasse sein, aber um den UserService testbar zu halten, können wir über den Konstruktor im UnitTest ein andere InstanceFactory reingeben:

        public UserService()
            : this(new InstanceFactory())
        {
        }

         public UserService(IInstanceFactory factory)
        {
            this._factory = factory;
        }

Im Normalfall wird also unsere Implementation von oben genommen und ansonsten könnten wir auch eine TestInstanceFactory reingeben. Geht soweit auch, ist aber eigentlich nicht so gut wie ich feststellen musste.

Wo liegt beim Service Locator das Problem?

Bei dieser Variante weiß man nicht genau, welche Abhängigkeiten eine Klasse hat. Wenn man die Abhängigkeiten im Konstruktor sieht, dann weiß man, dass z.B. der UserService eine Instanz vom Typ IMailService benötigt. Durch diese “Gott-Klasse” kann man plötzlich kreuz und quer irgendwelche Services aufrufen. Das macht das debugging und testen schwieriger.

Die bessere Methode: Inversion of Control Container

Hier gilt (wie immer) : Solides Halbwissen ist vorhanden – wenn ich hier Quatsch erzähle, dann berichtigt mich ruhig.

Um nicht zu weit auszuschweifen: Um die Abhängigkeiten auf den ersten Blick zu erkennen, ist es meiner Meinung nach gut, wenn diese über den Konstruktor definiert werden. Eine andere Art wäre dies über Properties zu machen. Das ist aber IMHO nicht so einleuchtend wie im Konstruktor.

Rund um das Inversion of Control Thema gibt es bereits eine Vielzahl von Frameworks, die einem dabei helfen:

Auf den ersten Blick ähneln sich die Konzepte vom Service Locator und diesen Frameworks. Bei Den IoC Container legt man immer eine Art “Konfiguration” nach dem Schema: “Wenn du nach Instanz von Typ X gefragt wirst, dann gibt ihm eine Instanz vom Typ X.” an. Allerdings sind die benannten Frameworks hier weitaus cleverer als mein Code von oben.

Wir spinnen das Szenario von oben weiter: Der UserService hat auch ein Interface IUserService und wird im UserController verwendet. Hier mal ein Beispiel mit Castle.Windsor:

   WindsorContainer container = new WindsorContainer(new XmlInterpreter());
   IUserService srv = container.Resolve<IUserService>();

Die “Resolve” Methode ist im Prinzip ähnlich wie das “GetInstanceOf” Methode. Allerdings hat unser UserService wiederrum eine Abhängigkeit auf den EmailService. Das wird allerdings alles vom Framework geregelt und alle Abhängigkeiten werden sauber aufgelöst.

Der Vorteil an der Methode ist, dass man im Unittest einfach so die Klassen benutzen kann und sofort die Abhängigkeiten durch den Konstruktor sieht. Das macht die Sache wesentlich durchschaubarer.

Ich erspare mir hier mal ein konkretes Beispiel, da es im Netz sehr viele gute HowTos zu den einzelnen Frameworks gibt.

Diese Frameworks können natürlich noch wesentlich mehr als pure Instanzen zurückgegeben, aber das würde jetzt zu weit führen. Ich hoffe ich konnte erstmal ein wenig Licht ins Dunkel bringen. Falls ich in diesem Post irgendwelche Sachen aber komplett falsch verstanden habe oder einfach Begrifflichkeiten verwechselt habe, dann korrigiert mich bitte :)

5 Kommentare bisher »

  1. Alexander Groß sagt

    am 16. März 2010 @ 09:37

    Hallo Robert,

    eine sehr schöne Zusammenfassung!

    Alex

  2. Rainer Hilmer sagt

    am 16. März 2010 @ 11:10

    Hallo Robert,
    schöne Einführung in das Thema. Was hier vielleicht nicht so ganz deutlich geworden ist: Ein weiterer Vorteil von DI und IOC ist, neben der Testbarkeit, die einfache Möglichkeit, Komponenten auszutauschen (Komponentendesign). So ist es z.B. ein Leichtes Serialisierungskomponenten auszutauschen (XML gegen binary or whatever). Wichtig dabei ist nur daß diese Komponenten eine gemeinsame Kommunikationsschnittstelle (Interface) implementieren. Mit diesem Verfahren lassen auch Presentation Layer austauschen. Mit der richtigen Architektur kann man die gleiche Applikation mit einem Web-UI verbinden oder z.B. mit Windows Forms.
    Gruß
    Rainer

  3. Ron sagt

    am 8. Juni 2010 @ 16:06

    Hallo Robert,

    es gibt bekanntlich keine dumme Fragen, also riskier´ ich´s mal…

    Die Klasse UserService: IUserService stellt ja einen Konstruktor für Constructor Injection bereit:
    UserService(IInstanceFactory factory) { … }

    Mit IOC-Containern wie Castle Windsor oder LightCore kann man nun über die Konfigurationsdateien “anmelden”, dass der Konstruktor der Klasse UserService (oder bzw. die Klasse als solche) eine Abhängigkeit von IInstanceFactory besitzt. Soweit richtig?

    Ich hätte mir aber folgendes von einem IOC-Container gewünscht: Damit ich mich beim Schreiben der Konfiguration für den IOC-Container eben NICHT MEHR darum kümmern muss, welche Abhängigkeiten die Klasse Kunde per Constructor Injection nennt, soll der IOC-Container selbst erkennen (bspw. über Reflection), dass UserService eine IInstanceFactory benötigt, selbständig prüfen, ob es schon eine Instanz von ILogger gibt, und wenn nicht, selbige aufgrund der Konfiguration eigenständig instanzieren und dann erst den Konstruktor von UserService aufrufen.

    Anders ausgedrückt: Soweit ich das Konzept von LightCore oder CastleWindsor richtig verstanden habe, muss ich mich ja immer noch selbst darum kümmern, dass die Abhängigkeit der Klasse X vom Typ Y (weil als Konstruktorargument erwartet) irgendwo (z.B. in der Konfiguration) “angemeldet” wird.
    Erhofft hätte ich mir eine entsprechende Intelligenz des IOC-Containers; habe ich hier etwas übersehen oder ist das so?

    Herzlichen Dank für Deine Meinung,
    Gruß
    Ron

  4. Robert Mühsig sagt

    am 10. Juni 2010 @ 15:18

    Hallo Ron,
    das hast du genau richtig erkannt. Die IInstanceFactory geht eher in die Richtung “Worst-Practices” weil es quasi ein ServiceLocator ist.
    Dort ist das Problem halt, dass man a) nie genau weiß, was die Klasse überhaupt für Abhängigkeiten hat und b) dass man keine wirkliche Kontrolle hat, weil die Klasse ganz wild Services aufrufen kann.

    Mein unteres Beispiel mit WindsorCastle und dem Aufruf von “container.Resolve();” würde automatisch die Abhängigkeiten aufschlüsseln und hätte die Intelligenz die du ansprichst.

    Dabei sucht WindsorCastle automatisch nach dem Konstruktor, welcher die meisten Abhängigkeiten entgegennimmt und schlüsselt diese dann immer weiter auf.

  5. HowTo: Alle Implementationen vom Interface X über Castle Windsor per DI auflösen | Code-Inside Blog sagt

    am 27. Juni 2010 @ 23:24

    [...] kleinen Einstieg hab ich hier bereits gegeben. In meinem Beispiel nutze ich Castle Windsor, genauso gut hätte ich wahrscheinlich [...]

Komentar RSS · TrackBack URI

Hinterlasse einen Kommentar

Name: (erforderlich)

eMail: (erforderlich)

Website:

Kommentar: