Knifflige Produktaktivierung Die Definition von "Release Candidate"

Highway to Hell

Published on Thursday, December 8, 2016 5:00:00 PM UTC in Programming & Tools

Vor einigen Jahren noch waren Entwickler nicht nur vertraut mit dem Begriff DLL Hell, es liefen ihnen meist auch kalte Schauer den Rücken hinunter, wenn sie an die ein oder andere Situation zurückdachten, in denen sie sich dieser "Hölle" ausgeliefert sahen[1][1]
Die kalten Schauer traten bei mir persönlich derart häufig auf, dass einige meiner Bandscheiben bereits Symptome von Erfrierung zeigten.
. Der Begriff bezeichnet vereinfacht dargestellt die Situation, dass verschiedene, zueinander inkompatible Versionen ein und derselben Bibliothek (DLL) Konflikte verursachten, die nur schwer aufzulösen waren. Heutzutage ist das Phänomen dank schier unbegrenzt zur Verfügung stehender Ressourcen und auch Technologien wie .NET, die aktiv an Lösungen für die Problematik gearbeitet haben, weitgehend in Vergessenheit geraten und berührt kaum noch das tägliche Arbeiten. Außer natürlich man arbeitet mit JavaScript.

Bei der Paketverwaltung mit NPM existiert ein ähnliches Phänomen, das sogar in der offiziellen Dokumentation als Dependency Hell bezeichnet wird. Tatsächlich handelt es sich keineswegs um eine akademische Diskussion; die involvierten Probleme sind sehr real, und dazu muss man nicht einmal - wie oft vermutet - mit mehreren Personen an derselben Code-Basis arbeiten. Die konkrete Situation sah etwa so aus:

  • Ein selbsterstelltes NPM-Paket A benötigt den TypeScript-Compiler in Version 2.x. Es handelt sich allerdings nur um eine indirekte Dependency, die über ein Paket X mit ins Projekt gezogen wird.
  • Ein zweites selbsterstelltes NPM-Paket B benötigt dieselbe TypeScript-Compiler-Version. Es nutzt ebenfalls Paket X. Leider wird in Paket B auch ein weiteres Paket Y genutzt, das eine ältere Version des TypeSCript-Compilers referenziert.

Nach Murphy's Law geschieht das Unvermeidliche: Paket A verwendet Version 2.x der Abhängigkeit, während Paket B mit Version 1.x versorgt wird. Danke, Merkel NPM! Tatsächlich ist der Effekt natürlich von der Reihenfolge der Installation abhängig, ganz wie auf der NPM-Seite beschrieben: Non-determinism of NPM 3. Auf einem anderen Rechner kann sich u.U. ein anderes Ergebnis ergeben, weil dort die Abhängigkeiten alphabetisch aus der zugehörigen package.json installiert werden. Kurz gesagt: das "Ökosystem" sorgt mal wieder für Spaß bei der Fehlersuche.

Was tun?

Im konkreten Beispiel wäre es sicherlich glücklicher gewesen, die Abhängigkeit direkt zu definieren, wodurch das Problem behoben worden wäre. Bei der schieren Masse an Paketen, die selbst ein triviales Projekt heutzutage allerdings referenziert, ist es allerdings kaum realistisch, konkrete Versionen für alle Pakete im gesamten Abhängigkeitsgraphen anzugegben. Zudem wäre es natürlich wünschenswert, dass derartige Fehler auf konsistente, nachvollziehbare Art und Weise auftreten.

Eine annehmbare Lösung wäre daher zumindest, dass die transienten Abhängigkeiten unabhängig von der Situation in immer derselben Version installiert werden. So kann sichergestellt werden, dass einmal gefundene, funktionierende Paket-Zusammenstellungen zuverlässig bei allen Beteiligten gleichermaßen reproduziert werden und nicht beim nächsten Auschecken/Aktualisieren zum Zufallsprodukt verkommen. NPM allerdings kann das nicht bieten.

Yarn

Seit einiger Zeit verfolge ich die Entwicklung des NPM-Ersatzes Yarn, der von Facebook aus (teilweise) ähnlichen Gründen entwickelt wurde. Mit ihm kommt man unmittelbar in den Genuß deterministischen Verhaltens beim Installieren von Abhängigkeiten. Durch den Einsatz eines lock-Files (ähnlich zum Mechanismus für NuGet-Packages) ist sichergestellt, dass Reihenfolgen oder die Paketwiederherstellung auf anderen Maschinen vorhersagbar dieselben Konstellationen produzieren. Das Schöne an Yarn ist, dass es quasi als Drop-in-Replacement für NPM zu betrachten ist, da es dieselben Definitionen für Abhängigkeiten in der package.json verwendet und seine CLI-Kommandos einen fließenden, kompatiblen Übergang von NPM erlauben (vgl. auch den Migration Guide).

Ganz neben bei erhält man noch ein wenig Performancegewinn im Vergleich zu NPM, und ein paar eingebaute Leckerbissen wie etwa das Kommando "yarn outdated":

yarn outdated v0.17.10
Package      Current Wanted Latest Package Type
concurrently 2.2.0   2.2.0  3.1.0  devDependencies
gulp-minify  0.0.11  0.0.11 0.0.14 devDependencies
gulp-tslint  4.3.5   4.3.5  7.0.1  devDependencies
tslint       3.15.1  3.15.1 4.0.2  devDependencies
yargs        4.8.1   4.8.1  6.5.0  devDependencies
Done in 9.19s.

Auch dies zweifelsohne momentan noch eine Schwäche von NPM.

Ob Yarn nur Symptombekämpfung in diesem Abhängigkeits-Dschungel darstellt oder eine dauerhafte Lösung für derartige Probleme darstellt, bleibt abzuwarten. Momentan sind auch meine Erfahrungen zu Inkompatibilitäten o.ä. Problemen gering. Tatsächlich aber bringt Yarn auf Anhieb eine spürbare Verbesserung und erlaubt natürlich jederzeit sehr einfach den Schritt zurück zu NPM, weshalb ich jedem mit ein, zwei Stunden Zeit ans Herz lege, den Versuch damit zumindest mal zu wagen.

Tags: Dependeny Management · NPM