Abhängigkeiten in Legacy-Systemen verwaltbar machen

Seite 4: Drittanbietercode

Inhaltsverzeichnis

Eine weitere Form der Abhängigkeit, der man bei der Softwareentwicklung begegnet, ist die zu Code von Drittanbietern. Das beginnt bereits beim verwendeten Framework. Mittlerweile weisen allerdings viele Bibliotheken eine gute Qualität auf, sodass es sich lohnt zu überlegen, ob man etwa einen Mailclient oder andere Standardkomponenten tatsächlich selbst entwickeln möchte.

Zum Glück sind die Zeiten vorbei, in denen Code direkt auf Produktionssystemen bearbeitet oder einzelne Dateien per FTP auf den Server geladen wurden. Annähernd jedes Projekt ist heutzutage über ein Versionskontrollsystem wie Git verwaltet. Oft ist aber beispielsweise das Framework mit eingecheckt, was dessen Aktualisierung erschwert. Zum Glück gibt es Tools, die den Vorgang erleichtern.

Im Bereich der Backend-Sprachen bietet sich Bundler für Ruby oder Maven für Java an, wobei Maven gleichzeitig noch als Build-Tool fungieren kann. Fürs Frontend sind im JavaScript-Umfeld Bower und npm beliebt. Um dem PHP-Beispiel treu zu bleiben, kommt im Folgenden Composer zum Einsatz.

Es empfiehlt sich, den Dependency Manager global auf der Entwicklungsmaschine zu installieren und nicht mit ins Repository einzuchecken. Zum Einrichten liegt in Composer eine JSON-Datei vor. Eine ganz einfache composer.json, welche die Anforderungen des vorherigen Abschnitts erfüllt, könnte wie folgt aussehen:

{
"name": "joergbasedow/legacy-project",
"require": {
"symfony/dependency-injection": "2.*",
"symfony/config": "2.*",
"yiisoft/yii": "1.1.15"
}
}

In ihr ist die Version des Yii-Frameworks explizit unter Verwendung semantischer Versionierung vorgegeben. Für die Symfony-Komponenten ist hingegen nur die Hauptversion festgelegt. Composer stellt in dem Zusammenhang eine mächtige Syntax zur Konfiguration bereit.

Nachdem die JSON-Datei angelegt ist, sorgt der Befehl composer install dafür, dass die Software Bibliotheksdateien herunterlädt und im Verzeichnis vendors ablegt. Letztes sollte in der .gitignore-Datei vermerkt sein, um zu verhindern, dass es mit eingecheckt wird. Composer hat in vendors außerdem eine autoload.php abgelegt, die einmalig beispielsweise in der Bootstrap-Datei einzubinden ist. Sie konfiguriert das PHP-Autoloading, sodass das System die Namespaces automatisch den passenden Verzeichnissen zuordnet.

In Legacy-Systemen sind oft Funktionen zu finden, die besser mit einer Drittanbieter-Bibliothek realisiert wären. Beim Entwickeln des Codes setzen Programmierer oft nur das Nötigste um, was unter dem Gesichtspunkt des Ballasts auch vernünftig erscheinen mag. Soll der Funktionsumfang der Software ausgebaut werden, ist allerdings auch der Code zu erweitern, was unter Umständen zeitintensiv sein kann. Kommt stattdessen die Bibliothek eines passenden Drittanbieters zum Einsatz, die von einer größeren Community gepflegt wird, bekommt man die benötigten Features oft geschenkt. Außerdem kommen eventuell sicherheitskritische Fehler in ihr dadurch schneller ans Licht, dass viele Entwickler die Bibliothek nutzen und den Code kontinuierlich weiterentwickeln. Einmal im Composer eingebunden, lässt sie sich regelmäßig und kontrolliert aktualisieren.

Außer composer.json legt Composer eine composer.lock-Datei an, in der festgehalten ist, welche Versionen der Drittanbieter-Bibliotheken genau installiert sind. Sie ist spätestens dann der Versionsverwaltung zu übergeben, wenn das System in Produktion geht. Dadurch ist gewährleistet, dass das System beim Ausführen von composer install immer genau die gleichen Versionen installiert. Um auf neue Releases zu aktualisieren, gibt es den Befehl composer update. Die entsprechende Funktion prüft für alle Bibliotheken, ob es neue Versionen gibt, die zu den Angaben in der JSON-Datei passen. Da man oft gezielt einzelne Libraries aktualisieren möchte, lassen sich diese mit angeben (z.B. composer update symfony/dependency-injection).

Die geladenen Bibliotheken haben oft eigene Abhängigkeiten, die in der jeweils mitgelieferten composer.json-Datei definiert sind. Es werden also durchaus mehr Bibliotheken installiert als in der eigenen Konfigurationsdatei stehen. Vor dem Herunterladen analysiert Composer die composer.json-Dateien aller expliziten und impliziten Abhängigkeiten. Falls sich ergibt, dass Komponenten in unterschiedlichen Versionen einzurichten sind (z.B. swiftmailer 5.4.* und 5.0.*), kann der Dependency Manager die Bibliotheken nicht installieren und gibt eine Warnung aus. Der Konflikt lässt sich beheben, indem die Entwickler entweder die explizite Abhängigkeit anpassen oder entsprechend die, die implizit den Konflikt verursacht.

Der Vorteil ist, dass von jeder Bibliothek genau eine Version installiert ist. Projektmitarbeiter wissen daher immer genau, welche jeweils im Code benutzt wird. Im Gegensatz dazu installiert der JavaScript-Paketverwalter npm unterschiedliche Versionen einer Bibliothek, die er jeweils als Unterverzeichnis in das Verzeichnis der Bibliothek packt, die sie benötigt. Das führt zu beliebig tiefen Verzeichnishierarchien. Dadurch ist etwa bei der statischen Codeanalyse die aktuell verwendete Variante nicht sofort ersichtlich.

Außer den Abhängigkeiten zu Fremdbibliotheken lassen sich auch Systemanforderungen in composer.json festhalten. Der folgende Codeauszug setzt eine bestimmte PHP-Version und einige -Erweiterungen voraus. Wenn sie nicht auf dem System installiert sind, gibt Composer beim Update eine Warnung aus:

{
"name": "joergbasedow/legacy-project",
"require": {
"php": ">=5.6.0",
"ext-gd": "*",
"ext-curl": "*",
"ext-redis": "*",
"swiftmailer/swiftmailer": "5.4.*",
"symfony/dependency-injection": "2.*",
"symfony/config": "2.*",
"yiisoft/yii": "1.1.15"
}
}

Um nicht parallel zum Composer-Autoloading noch ein eigenes implementieren zu müssen, kann man Regeln für das automatische Laden in der Konfigurationsdatei ergänzen:

{
"name": "joergbasedow/legacy-project",
"require": {
"php": ">=5.6.0",
"ext-gd": "*",
"ext-curl": "*",
"ext-redis": "*",
"swiftmailer/swiftmailer": "5.4.*",
"symfony/dependency-injection": "2.*",
"symfony/config": "2.*",
"yiisoft/yii": "1.1.15"
},
"autoload": {
"psr-4": {
"Library\\": "protected/lib",
"Module\\": "protected/modules"
},
"classmap": [
"protected/models/behaviors",
"protected/commands"
]
}
}

Hierbei lassen sich sowohl Namespaces für PSR-4-Autoloading als auch Verzeichnisse einrichten [h]. Composer durchsucht sie nach Klassen, für die er anschließend eine Zuordnung ([Namespace\]Klassenname => Datei) erstellt.