Vortrag in der Symfony2 User Group Köln vom 16.07.2014.
Welche Rolle spielt das Paket-Management für den Software-Entwicklungsprozess? Welche Dinge sollte man als Paket-Maintainer bewusst entscheiden und planen?
In diesem Talk haben wir die Erfahrungen, Probleme und gelernten Lektionen aus knapp drei Jahren mit Composer vorgestellt.
4. wfLib
Systemweite (eigene) Bibliothek im include_path
Bugs/BC breaks
machen viel auf
einmal kaputt
Was einmal drin ist,
können wir nur noch
schwer ändern.
PEAR_DB vs.
Zend_DB
Branches in Sync?
5. Composer to the rescue!
Schrittweiser Umbau in Pakete
Composer-Repo selbstgeschrieben in 11/2011
Es geht schnell und
leicht, für irgendwas ein
Paket anzulegen.
Dank .lock-File können wir gut
weiterentwickeln.
10. Release Reuse Equivalence
Principle
The granule of reuse is the granule of
release. Only components that are
released through a tracking system can
be effectively reused. This granule is the
package.
Robert C. Martin: Granularity (1997)
http://www.objectmentor.com/resources/articles/granularity.pdf
26. Common Reuse Principle
The classes in a package are reused
together. If you reuse one of the classes
in a package, you reuse them all.
Robert C. Martin: Granularity (1997)
http://www.objectmentor.com/resources/articles/granularity.pdf
27.
28.
29. Common Closure Principle
The classes in a package should be
closed together against the same kinds
of changes. A change that affects a
package affects all classes in that
package.
Robert C. Martin: Granularity (1997)
http://www.objectmentor.com/resources/articles/granularity.pdf
30.
31. Stable Dependencies Principle
The dependencies between packages in
a design should be in the direction of the
stability of the packages. A package
should only depend upon packages that
are more stable than it is.
Robert C. Martin: Stability(1997)
http://www.objectmentor.com/resources/articles/stability.pdf
39. Depending
„Depending is an online tool to monitor
your PHP project dependencies. It shows
your project dependencies status, right
away after you're commiting new code
into your Github repository.“
– depending.in
Wir helfen unseren Kunden dabei, individuelle Anwendungen zu entwickeln und langfristig erfolgreich zu betreiben.
Das sind derzeit über 50 Kunden, teilweise mit eigenen Entwicklungsabteilungen, teils ganz ohne.
Dabei stellen sich für uns natürlich einige praktische Probleme.
Im Dezember durfte ich hier schon einen Vortrag dazu halten, wie sich Altanwendungen schrittweise auf Symfony2 migrieren lassen.
Heute geht es um die Frage, wie wir eigenen Code langfristig wiederverwenden können.
Branches:
Nichts vergessen?
Kein Cherry Picking möglich, wenn DB-Spezifisch
API hängt davon ab, welche Version man nutzt
Wofür packen wir Code in Pakete?
Abstraktionsmechanismus, um Systeme auf größerer Ebene zerlegen und verstehen zu können
Letztlich das Ziel der OOP
Copy != Reuse, weil:
Selbst für fixes verantwortlich
Sei es nur, fixes von upstream zu kopieren
Divergierende Änderungen? -> Endgültig eigener Code
Am Anfang billig, langfristig (maintenance) teuer
Nur Pakete, die einem kontrollierten Release-Prozess unterliegen, lassen sich sinnvoll wiederverwenden
Wir können sehen, wenn neue Versionen verfügbar sind und diese in unser Produkt integrieren, wenn wir es für angemessen halten (oder für den Moment bei der alten Version bleiben)
Damit das geht, müssen die einzelnen Versionen „offiziell“ sein und Versionsnummern o. ä. haben
Release auf Ebene von Klassen nicht praktikabel -> Paket-Ebene.
Wir müssen uns auf die Schnittstelle des Pakets verlassen können, nicht die Implementierung
Wenn wir für jede neue Version die Implementierung betrachten müssen (oder sich die Schnittstellen ändern), werden wir nicht regelmäßig aktualisieren können -> kein echter Reuse.
Generell: Alles, was meine Klienten wissen bzw. worauf sie sich verlassen
Nicht relevant: Wie das Paket implementiert ist, d. h. welche anderen Pakete es evtl. „hinter den Kulissen“ nutzt
Natürlich nur, wenn das nicht „nach außen“ leaked
Also?
„klassische“ API (Methodensignaturen, Klassennamen, ...)
Alles? Nur @api? Protected-Methoden?
Symfony-Controller: Namen der Parameter
Bundles: Konfigurationsparameter, -defaults
HTML-Struktur?
Datenbank-Schema? Auch, wenn es Migrations gibt?
Lizenzmodell?
Sonstige Systemanforderungen oder Abhängigkeiten? Kann ein Upgrade oder eine Installation in Verbindung mit anderen Paketen ggf. unmöglich machen...
Wir müssen uns auf die Schnittstelle des Pakets verlassen können, nicht die Implementierung
Wenn wir für jede neue Version die Implementierung betrachten müssen (oder sich die Schnittstellen ändern), werden wir nicht regelmäßig aktualisieren können -> kein echter Reuse.
Generell: Alles, was meine Klienten wissen bzw. worauf sie sich verlassen
Nicht relevant: Wie das Paket implementiert ist, d. h. welche anderen Pakete es evtl. „hinter den Kulissen“ nutzt
Natürlich nur, wenn das nicht „nach außen“ leaked
Also?
„klassische“ API (Methodensignaturen, Klassennamen, ...)
Alles? Nur @api? Protected-Methoden?
Symfony-Controller: Namen der Parameter
Bundles: Konfigurationsparameter, -defaults
HTML-Struktur?
Datenbank-Schema? Auch, wenn es Migrations gibt?
Lizenzmodell?
Sonstige Systemanforderungen oder Abhängigkeiten? Kann ein Upgrade oder eine Installation in Verbindung mit anderen Paketen ggf. unmöglich machen...
Ziel: Potenziellen Re-usern einen Überblick darüber verschaffen,
Welche neuen Möglichkeiten/Bugfixes etc. sie bekommen
Auf welche neuen Voraussetzungen sie sich einstellen müssen
Welchen Änderungen/Anpassungen kommen mit einer neuen Version auf sie zu?
Auf welche Erweiterungen wird der Maintainer Rücksicht nehmen, was sollte man besser nicht tun?
Composer.json: z. B. PHP-Extensions
Entscheidung Lohnt sich ein Upgrade und wann machen wir es am besten?
Lektion gelernt: Es spielt keine Rolle, ob das ein open source oder internes Paket ist, die Schmerzen bei Mißachtung sind die gleichen
Wenn Versionsnummern willkürlich vergeben werden, muss ich für jede einzelne Version prüfen, ob/wie ich sie benutzen kann
Sehr enge Vorgabe in composer.json, kleiner Lösungsraum für Solver
Problem bei transitiven Deps: Wenn eine Version mal „zu offen“ war, geht Composer u. U. auf diese zurück, um dann eine inkompatible aktuelle Version der Dep zu installieren
Lest das Dokument, ist einfach und schnell gelesen
Erster Gedanke: „Was ist daran so neu/toll“?
Nicht selbstverständlich. Also bei fremden Paketen prüfen, ob es eingehalten wird. Bei eigenen ausdrücklich dokumentieren, falls ja.
Inkrement setzt nachfolgende Teile auf 0 zurück
X = 0 „unstable“
Beispiele:
Neue Klassen/Teilsysteme
Neue Konfigurationseinstellungen
...
Gerade bei internen Pakten/Projekten: Es ist praktisch nie die Zeit/das Geld da, um ein laufendes Projekt „mal eben so“ auf eine neue Major zu heben, also macht man es nicht
Damit behalten wir potenziell sehr lange Klienten auf alten Branches
Damit wächst die Bereitschaft, sogar noch neue Features für alte Majors zu liefern
Features u. U. nicht so einfach auf die neue Major zu übertragen -> passiert nicht -> Problem verschärft sich
Beispiele:
Neue Klassen/Teilsysteme
Neue Konfigurationseinstellungen
...
Gerade bei internen Pakten/Projekten: Es ist praktisch nie die Zeit/das Geld da, um ein laufendes Projekt „mal eben so“ auf eine neue Major zu heben, also macht man es nicht
Damit behalten wir potenziell sehr lange Klienten auf alten Branches
Damit wächst die Bereitschaft, sogar noch neue Features für alte Majors zu liefern
Features u. U. nicht so einfach auf die neue Major zu übertragen -> passiert nicht -> Problem verschärft sich
-
Problem: Jede noch offene Version ist ein Branch
Wo bringen wir Bugfixes an? Je mehr Branches, desto aufwändiger zu entscheiden
Evtl. Bugfixes nicht einfach vorwärts zu portieren
Jeden Branch muss man für ein Release einzeln betrachten, testen etc.
Für wen mache ich das?
Kenne ich meine Klienten? (Deptrack...?)
Falls nein: Release Document o. ä. macht deutlich, was man erwarten darf.
Empfehlung: Nur *Bugfix* auf begrenzter Anzahl alter Branches; neue Features immer nur auf dem *neuesten* Branch; Major Releases um jeden Preis vermeiden?
Je schneller/öfter, desto mehr Branches potenziell (längerfristig) zu warten
Wie lange muss ich mit @dev-Deps leben? Kann ich mich drauf verlassen, zum go-live eine „stable“-Version zu haben?
Webfactory: Teils immer „sofort“; teils bei Entwicklung mit @dev arbeiten und dann zum Release taggen.
Herausfinden, was wirklich funktioniert
Reuse-Kandidat?
API stabil?
Wir wollen von so wenig wie möglich, davon aber so maximal wie möglich abhängen
Zusammen gehört:
Enge Kopplung
Reuse (oft) zusammen
Unhandlich (muss mehrere Deps verwalten)
Schwierige Updates, wenn cross-package Version constraints
Sind die Teile isoliert sinnvoll nutzbar? -> Aufteilen.
Ungenutzte Klasse
Evtl. neue Version (=Arbeit), die mich gar nicht interessiert
Kann Release Cycles entkoppeln
Kann Major-breaks begrenzen
Aufteilung: Schlecht im Nachhinein machbar
Wir wollen von so wenig wie möglich, davon aber so maximal wie möglich abhängen
Zusammen gehört:
Enge Kopplung
Reuse (oft) zusammen
Strategische Überlegung: Was ändert sich zusammen?
Release-Aufwand begrenzen, der voraussichtlich durch eine Änderung verursacht wird
Cross-Package-Upgrades/Deps vermeiden