API-Versionierung und Abwärtskompatibilität
API-Versionierung und Abwärtskompatibilität
~6 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
Kapitel 79-84 haben vier Verträge fest etabliert: RewardInterface, RewardRepositoryInterface, webapi.xml-Routen und schema.graphqls. Sobald externe Clients - eine mobile App, ein Partnersystem, ein anderes Team - gegen diese Verträge entwickeln, wird jede spätere Änderung an ihnen zu einer Abwärtskompatibilitäts-Frage. Dieses Kapitel bleibt bewusst konzeptionell statt neuen Code zu bauen - es setzt die Regeln, an die sich jede künftige Änderung dieses Moduls halten muss.
Die unbequeme Wahrheit über PHP-Interfaces
Eine neue Klasse um eine Methode zu erweitern, ist rückwärtskompatibel - bestehender Code, der die Klasse nutzt, bemerkt nichts. Bei einem Interface gilt das Gegenteil: RewardInterface um eine neue abstrakte Methode getStockStatus(): string zu ergänzen, zwingt PHP dazu, dass jede Implementierung diese Methode nachrüstet - einschließlich fremder, in diesem Projekt nicht sichtbarer Drittanbieter-Klassen, die RewardInterface selbst implementieren (etwa ein alternatives Model\Data\Reward per eigener Preference). Ein Breaking Change, getarnt als scheinbar harmlose Erweiterung.
Achtung: Genau deshalb erweitert RewardInterface in Kapitel 79 bewusst ExtensibleDataInterface: getExtensionAttributes()/setExtensionAttributes() sind der von Magento selbst vorgesehene, sichere Erweiterungsweg. Ein neues Feld wandert in extension_attributes.xml statt als neue Methode direkt ins Interface - bestehende Implementierungen bleiben unverändert gültig, der Codegenerator baut die RewardExtensionInterface einfach neu.
Wann wirklich ein V2 nötig ist
- Nie nötig für additive REST-Änderungen: ein neues, optionales Response-Feld über Extension Attributes lässt bestehende V1-Clients unberührt - sie ignorieren einfach, was sie nicht kennen.
- Nie nötig, um ein Feld zu entfernen, das niemand mehr nutzt - vorausgesetzt, es lässt sich anhand echter Zugriffslogs bestätigen, was bei einer öffentlichen API selten der Fall ist.
- Wirklich nötig, sobald sich die Bedeutung eines bestehenden Feldes ändert (Beispiel unten), ein Pflichtparameter hinzukommt, oder eine Methode ihren Rückgabetyp inkompatibel ändert.
Ein konkretes, hypothetisches Beispiel für den letzten Fall: Sollte points_per_euro (Kapitel 7) künftig je nach Produktkategorie variieren und PointsSummaryInterface::getPointsBalance() deshalb nicht mehr den reinen Punktestand, sondern eine ganze Aufschlüsselung zurückgeben müssen - eine echte Bedeutungsänderung, kein additives Feld. Der saubere Weg wäre dann ein eigenes Api\PointsManagementV2Interface mit einer neuen getPointsSummaryV2()-Methode, registriert unter einer zweiten webapi.xml-Route:
<route url="/V2/loyalty/points/mine" method="GET">
<service class="Mironsoft\Loyalty\Api\PointsManagementV2Interface" method="getPointsSummary"/>
<resources>
<resource ref="self"/>
</resources>
<data>
<parameter name="customerId" force="true">%customer_id%</parameter>
</data>
</route>
/V1/loyalty/points/mine aus Kapitel 80 bliebe dabei unverändert bestehen - bestehende Clients brechen nicht, neue Clients migrieren freiwillig zu /V2, sobald sie bereit sind. Rein illustrativ: PointsManagementV2Interface wird in diesem Modul nicht tatsächlich gebaut, das Beispiel zeigt ausschließlich das Muster.
GraphQL versioniert sich anders als REST
GraphQL kennt kein /V2-URL-Präfix - ein Schema wird kontinuierlich weiterentwickelt, nicht in Versionen geschnitten. Der offizielle GraphQL-Weg für eine Bedeutungsänderung ist die @deprecated-Direktive: das alte Feld bleibt funktionsfähig, wird aber als veraltet markiert und über Introspection (Kapitel 87) für Tooling sichtbar.
type LoyaltyReward @doc(description: "A single redeemable reward") {
reward_id: Int!
identifier: String!
title: String!
is_active: Boolean! @deprecated(reason: "Use reward_status instead, which distinguishes active/paused/archived")
reward_status: String @doc(description: "One of active, paused, archived")
}
Auch hier rein illustrativ - reward_status existiert in diesem Modul nicht wirklich, is_active aus Kapitel 82 bleibt der tatsächliche Stand dieser Serie. Das Beispiel zeigt nur, wie eine spätere, echte Bedeutungsänderung an LoyaltyReward aussehen würde, ohne bestehende Clients sofort zu brechen.
Eine Checkliste für jede künftige Änderung
- Lässt sich die Änderung als neues, optionales Feld über Extension Attributes (REST) oder additive Schema-Erweiterung (GraphQL) abbilden? Dann keine neue Version nötig.
- Ändert sich die Bedeutung eines bestehenden Feldes, nicht nur seine Präsenz? Dann V2-Route (REST) bzw. @deprecated plus neues Feld (GraphQL).
- Wird eine Methode aus einem bestehenden Api-Interface entfernt oder ihre Signatur geändert? Praktisch niemals ohne neue Version - PHP erzwingt sonst Fehler bei jeder fremden Implementierung.
- Ist die Änderung dokumentiert (Kapitel 87) und im Spec-Dokument dieser Serie nachgetragen, damit spätere Blöcke denselben Vertrag kennen?
Mit klaren Regeln für Stabilität ausgestattet, wendet sich Kapitel 86 einer anderen Bedrohung zu: Was passiert, wenn ein Client die Regeln kennt, aber die Einlöse-Route aus Kapitel 81 einfach zu oft hintereinander aufruft?