Zum Inhalt springen
    Zurück zum Blog
    Shopsoftware
    Community
    News
    Tools & Funktionen
    Integration
    Technologien

    Dependency Injection für Projektentwickler in OXID

    20.03.2020
    4 min Lesezeit
    Von Michael Koltan
    Dependency Injection für Projektentwickler in OXID

    Teil 1: Die Grundlagen

    Es gibt eine ganze Reihe von Techniken und Heuristiken um test- und wartbare Software zu schreiben. Doch letztendlich laufen alle auf eine Grundregel hinaus: Eine so weit wie möglich gehende Entkopplung aller Softwarebausteine.

    Die Logik dahinter ist einfach: Wenn die einzelnen Bausteine autark sind, können sie auch isoliert getestet werden. Und damit kann, wenn die Tests vernünftig geschrieben sind, für jeden einzelnen Baustein sichergestellt werden, dass er die an ihn gestellten Anforderungen erfüllt.

    Aber natürlich müssen diese einzelnen Bausteine irgendwann zu einem Ganzen integriert werden. Um hier nicht wieder neue Abhängigkeiten zu schaffen und die mühsam erreichte Entkopplung der einzelnen Bausteine zu verlieren, greift man zur Technik derinversion of control. Diese wird auch Hollywood-Prinzip genannt, gemäß der Standardansage: "Don't call us, we call you". Übersetzt in die Welt der Softwareentwicklung: Wenn einer unserer Softwarebausteine die Funktionalität eines anderen Bausteins benötigt, dann darf er diesen Baustein nicht selbst instantiieren. Vielmehr bekommt er ihn von außen injiziert.

    Um zu verstehen, warum das essentiell ist, nehmen wir ein ganz einfaches Beispiel:

    Wenn wir jetzt die Methode someFunction() testen wollen, dann müssen wir wissen, was genau die Klasse BausteinII tut, um das Ergebnis zu testen, denn BausteinII ist fest verdrahtet mit der Klasse, die wir hier testen wollen. Was sollen wir also im folgenden Testcode an die Stelle von ? setzen? Ohne Kenntnis von BausteinII wissen wir das nicht.

    Und selbst wenn wir einen Wert ermittelt haben, kann uns das trotzdem Probleme machen. Was ist, wenn BausteinII unterschiedliche Ergebnisse zurückliefert, beispielsweise in Abhängigkeit von der Mondphase (oder realistischer: der Zeitzone oder eine Konfigurationsoption)? Dann kann sich der Rückgabewert ändern und unser Test schlägt in manchen Fällen fehl, in manchen nicht.

    Das sind alles Fragen, die wir uns überhaupt nicht stellen wollen, wenn wir BausteinI testen, denn eigentlich wollen wir uns nicht damit auseinandersetzen, was BausteinII tut. Das ist die Aufgabe derjenigen, die diese Komponente entwickeln.

    Deshalb ist es besser, die Kontrolle umzukehren. BausteinI soll überhaupt nichts über BausteinII wissen - außer dass letzterer die Methode doSomething() zur Verfügung stellt. Mit anderen Worten: BausteinII sollte eininterfaceimplementieren und nur über dieses ansprechbar sein:

    Damit können wir nun den Konstruktor von BausteinI umschreiben und das Abhängigkeitsverhältnis umkehren:

    Jetzt können wir für BausteinI einen Test schreiben, ohne uns im Geringsten um BausteinII irgendeinen Gedanken zu machen:

    Wir testen jetzt nur noch, dass der korrekte Parameter an BausteinII übergeben wird, also das, was wir in BausteinI als eine Art Geschäftslogik implementiert haben. Was BausteinII damit macht, ist uns völlig egal, weswegen wir ihn durch ein mock ersetzt haben, das einen Fehler produziert, wenn im Verlauf des Tests nicht genau einmal die Methode doSomething() mit dem Parameter 60 aufgerufen wird.

    Und damit haben wir einen echtenunit testfür unseren BausteinI geschrieben, der die Geschäftslogik von BausteinII völlig außen vor lässt (okay, wenn man der ganz reinen Lehre folgt, auch nicht ganz, weil wir immer noch Kenntnis von Implementationsdetails voraussetzen, nämlich eben diesen Aufruf von BausteinII; aber in der Praxis ist das nicht immer zu vermeiden).

    Um also wart- und testbaren Code zu schreiben, ist das Prinzip derinversion of controlunverzichtbar. Und wie unser voll ausgeführtes Beispiel zeigt, ist für die Implementierung dieses Prinzips auch keinframeworkoder ähnliches nötig.Inversion of controlgibt uns eine Technik an die Hand, wie wir Abhängigkeiten innerhalb unseres Codes aufheben können.

    Zur Laufzeit des Programmes müssen natürlich die einzelnen Bausteine konkret verdrahtet werden. Das kann man, zumindest bei Kleinprojekten, problemlos selbst im Code machen.

    Doch ab einer gewissen Komplexität ist es sinnvoller, einframeworkzu verwenden, das die einzelnen Komponenten zusammenstöpselt. Dabei sollte es im Prinzip egal sein, welchesframeworkman verwendet: Der Code sollte sich dadurch nicht ändern.

    OXID hat sich dazu entschieden, denSymfony DI containerin sein E-Commerceframeworkzu integrieren. Die Gründe dafür waren:

  1. Es handelt sich um ein etabliertesframework, das kontinuierlich gewartet wird.
  2. Dercontainerbietet ein sehr gutescachingund damit eine guteperformance.
  3. Er unterstützt zusätzliche nützliche Funktionalitäten, vor allemevents.
  4. Bei seiner Einführung in der Version 6.0 war derSymfony DI containernur innerhalb eines speziellennamespacesinnerhalb des OXIDframeworksnutzbar und damit für Projektentwickler eigentlich uninteressant. Jetzt haben wir die Nutzung desDI containersauch für Modul- und Projektentwickler freigegeben. Die folgenden Blog-Beiträge werden darauf eingehen, wie derSymfony DI containerin Modulen und für die Projektentwicklung genutzt werden kann (und sollte).

    Dies ist der erste Artikel einer dreiteiligen Serie.Im nächsten Teil wird es darum gehen, wie man innerhalb eines OXID Moduls die Funktionalität des DI containers nutzen kann.

    Und im dritten Teil dann, welche Möglichkeiten der DI container bietet, um Shop-Funktionalitäten zu erweitern oder zu ersetzen, ohne auf oxNew() zurückgreifen zu müssen.