URI, Header oder Content-Negotiation: welche Strategie wirklich trägt
Ohne durchdachte API-Versionierung wird jeder Breaking Change zum Krisenmeeting mit externen Integratoren. In PHP stehen drei etablierte Ansätze zur Auswahl: Versionsnummer in der URI, ein eigener Versions-Header oder Versionierung über den Accept-Header per Content-Negotiation. Welche API-Versionierung passt, hängt von Kundenstruktur, Release-Rhythmus und der Zahl parallel unterstützter Versionen ab.
Inhaltsverzeichnis
- 1. Warum API-Versionierung überhaupt nötig ist
- 2. URI-Versionierung: /v1/, /v2/ in PHP umsetzen
- 3. Header-Versionierung: eigener Custom-Header
- 4. Content-Negotiation: Versionierung über Accept-Header
- 5. Semantic Versioning für API-Verträge
- 6. Was überhaupt ein Breaking Change ist
- 7. Deprecation-Workflow: alte Versionen sauber abkündigen
- 8. Mehrere Versionen in einer PHP-Codebasis pflegen
- 9. API-Versionierung im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum API-Versionierung überhaupt nötig ist
Eine API, die von externen Systemen konsumiert wird, kann nicht wie eine interne Bibliothek jederzeit umgebaut werden. Sobald ein Feld umbenannt, ein Endpunkt entfernt oder ein Response-Format geändert wird, bricht jeder Client, der sich auf die alte Struktur verlässt. API-Versionierung löst genau dieses Problem: Sie erlaubt es, eine neue Version parallel zur alten anzubieten, sodass bestehende Integrationen weiterlaufen, während neue Clients bereits die verbesserte Version nutzen.
Ohne API-Versionierung bleiben nur zwei unattraktive Optionen: entweder friert man die API dauerhaft ein, was Weiterentwicklung verhindert, oder man ändert sie und riskiert, dass Partnerintegrationen ohne Vorwarnung ausfallen. Beide Optionen sind in produktiven PHP-Systemen mit externen Konsumenten nicht akzeptabel. Eine durchdachte API-Versionierung schafft den nötigen Spielraum, um Innovation und Stabilität gleichzeitig zu ermöglichen, statt zwischen beidem wählen zu müssen.
2. URI-Versionierung: /v1/, /v2/ in PHP umsetzen
Die verbreitetste Form der API-Versionierung ist die Versionsnummer direkt in der URL, etwa /v1/orders und /v2/orders. Der große Vorteil: Die Version ist auf den ersten Blick sichtbar, lässt sich in Logs, Browser-Adressleisten und API-Dokumentationen ohne zusätzliche Tools nachvollziehen, und funktioniert mit jedem HTTP-Client ohne Sonderbehandlung von Headern. Aus Sicht des Supports ist das ein erheblicher Vorteil, weil ein Fehlerbericht mit URL bereits die genutzte Version enthält.
Der Nachteil dieser API-Versionierung ist, dass sie strenggenommen gegen das REST-Prinzip verstößt, dass dieselbe Ressource unter derselben URI erreichbar sein sollte. /v1/orders/5 und /v2/orders/5 repräsentieren dieselbe fachliche Ressource unter zwei verschiedenen Adressen. In der Praxis überwiegt der pragmatische Nutzen diesen theoretischen Einwand meist deutlich, weshalb URI-Versionierung trotz der Kritik die am häufigsten eingesetzte Form der API-Versionierung bleibt.
<?php
declare(strict_types=1);
// URI-based API versioning: dispatch by version prefix
final class VersionedRouter
{
/** @var array<string, ControllerInterface> */
private array $versions = [];
public function registerVersion(string $version, ControllerInterface $controller): void
{
$this->versions[$version] = $controller;
}
public function dispatch(string $path, string $method, array $payload): mixed
{
if (!preg_match('#^/(v\d+)/(.+)$#', $path, $matches)) {
http_response_code(400);
return ['error' => 'Missing API version in path'];
}
[, $version, $resource] = $matches;
if (!isset($this->versions[$version])) {
http_response_code(404);
return ['error' => "API version {$version} is not supported"];
}
return $this->versions[$version]->handle($resource, $method, $payload);
}
}
$router = new VersionedRouter();
$router->registerVersion('v1', new OrderControllerV1());
$router->registerVersion('v2', new OrderControllerV2()); // adds pagination cursor
3. Header-Versionierung: eigener Custom-Header
Statt die Version in die URL zu schreiben, kann sie auch über einen eigenen Header übertragen werden, etwa Api-Version: 2. Diese Form der API-Versionierung hält die URL sauber und stabil und behandelt die Version als Metadatum des Requests statt als Teil der Ressourcenadresse, was dem REST-Grundgedanken näherkommt. Für interne Microservices, bei denen alle Clients unter eigener Kontrolle stehen, ist das eine gute Wahl, weil sich Header-Handling zentral in einer HTTP-Client-Bibliothek konfigurieren lässt.
Der Nachteil zeigt sich vor allem bei öffentlichen APIs mit vielen externen Konsumenten: Ein fehlender oder falsch gesetzter Header ist für Entwickler weniger offensichtlich als eine falsche URL, und viele einfache HTTP-Test-Tools, Browser-Adressleisten oder Postman-Collections ohne gepflegte Header-Presets führen schnell zu falsch versionierten Requests. Für interne API-Versionierung zwischen kontrollierten Teams ist der Header-Ansatz dennoch eine der saubersten Lösungen.
<?php
declare(strict_types=1);
// Header-based API versioning: version as request metadata
final class HeaderVersionResolver
{
public function __construct(
private readonly string $defaultVersion = '1',
private readonly array $supportedVersions = ['1', '2'],
) {}
public function resolve(?string $headerValue): string
{
$version = $headerValue ?? $this->defaultVersion;
if (!in_array($version, $this->supportedVersions, true)) {
http_response_code(400);
header('Content-Type: application/json');
echo json_encode([
'error' => "Api-Version '{$version}' is not supported",
'supported_versions' => $this->supportedVersions,
]);
exit;
}
return $version;
}
}
$resolver = new HeaderVersionResolver();
$version = $resolver->resolve($_SERVER['HTTP_API_VERSION'] ?? null);
$controller = match ($version) {
'1' => new OrderControllerV1(),
'2' => new OrderControllerV2(),
};
4. Content-Negotiation: Versionierung über Accept-Header
Die dritte gängige Strategie der API-Versionierung nutzt Media-Type-Parameter innerhalb des Accept-Headers, etwa Accept: application/vnd.mironsoft.v2+json. Dieser Ansatz gilt vielen als die theoretisch sauberste API-Versionierung, weil er HTTP-Content-Negotiation nutzt, ein Mechanismus, der genau für solche Aushandlungen vorgesehen ist, statt die Versionierung künstlich in URL oder Custom-Header zu pressen.
In der Praxis stößt diese Form der API-Versionierung auf Akzeptanzprobleme: Der Header ist unhandlicher zu schreiben, viele API-Gateways und Caches behandeln Vendor-Media-Types nicht durchgängig korrekt, und die Entdeckbarkeit für neue Entwickler ist geringer als bei einer sichtbaren Versionsnummer in der URL. Für APIs mit strengen Hypermedia- und REST-Reifegradanforderungen bleibt Content-Negotiation dennoch die Variante, die dem REST-Ideal am nächsten kommt, auch wenn sie in der Breite seltener eingesetzt wird als URI-Versionierung.
<?php
declare(strict_types=1);
// Content-negotiation based versioning via vendor media type
final class VendorMediaTypeVersionResolver
{
public function resolve(string $acceptHeader): string
{
if (preg_match('#application/vnd\.mironsoft\.v(\d+)\+json#', $acceptHeader, $matches)) {
return $matches[1];
}
// Fall back to the latest stable version if no vendor type is given
return '2';
}
}
$resolver = new VendorMediaTypeVersionResolver();
$version = $resolver->resolve($_SERVER['HTTP_ACCEPT'] ?? '');
header("Content-Type: application/vnd.mironsoft.v{$version}+json");
5. Semantic Versioning für API-Verträge
Semantic Versioning, also die Struktur MAJOR.MINOR.PATCH, stammt ursprünglich aus dem Bibliotheks- und Paketmanagement, lässt sich aber sinnvoll auf API-Versionierung übertragen. Eine MAJOR-Erhöhung signalisiert Breaking Changes, eine MINOR-Erhöhung neue, abwärtskompatible Felder oder Endpunkte, und ein PATCH markiert reine Fehlerbehebungen ohne Verhaltensänderung. In der Praxis wird bei öffentlichen HTTP-APIs meist nur die MAJOR-Version in URL oder Header sichtbar gemacht, weil MINOR- und PATCH-Änderungen per Definition abwärtskompatibel sein müssen.
Diese Konvention schafft eine gemeinsame Sprache zwischen Team und Konsumenten: Wenn die API-Versionierung strikt nach Semantic Versioning kommuniziert wird, wissen Integratoren beim Blick auf ein Changelog sofort, ob ein Update risikofrei eingespielt werden kann oder eine Anpassung des eigenen Codes erfordert. Ohne diese Disziplin verschwimmen Breaking Changes und harmlose Erweiterungen in derselben Versionsnummer, was Vertrauen in die API-Versionierung insgesamt untergräbt.
6. Was überhaupt ein Breaking Change ist
Nicht jede Änderung an einer API rechtfertigt eine neue Hauptversion, aber die Grenze wird in der Praxis oft falsch gezogen. Ein neues optionales Feld in der Response ist kein Breaking Change, weil gut geschriebene Clients unbekannte Felder ignorieren. Das Entfernen eines bestehenden Feldes, das Ändern seines Typs oder das strengere Machen einer zuvor optionalen Validierungsregel sind hingegen klassische Breaking Changes, die eine neue Version in der API-Versionierung erfordern.
Ein besonders unterschätzter Fall: Das Ändern der Reihenfolge von Array-Elementen wirkt harmlos, kann aber Clients brechen, die sich auf Indexpositionen statt auf benannte Schlüssel verlassen. Ebenso zählt das Verschärfen von Rate Limits oder das Einführen einer zuvor optionalen Pflichtfeld-Validierung zu den Breaking Changes, auch wenn keine einzige Zeile der Response-Struktur verändert wurde. Eine saubere API-Versionierung braucht daher eine dokumentierte, für das gesamte Team verbindliche Definition, was als Breaking Change zählt.
7. Deprecation-Workflow: alte Versionen sauber abkündigen
API-Versionierung ohne Deprecation-Strategie führt dazu, dass alte Versionen unbegrenzt weiterlaufen müssen, weil niemand weiß, wer sie noch nutzt. Der erste Schritt ist Sichtbarkeit: Ein Deprecation-Header nach RFC 8594 informiert Clients maschinenlesbar darüber, dass die genutzte Version abgekündigt ist, ergänzt um einen Sunset-Header mit dem geplanten Abschaltdatum. Diese Header lassen sich zusätzlich in jeder Response mitschicken, ohne den eigentlichen Response-Body zu verändern.
Der zweite Schritt ist Monitoring: Jede alte API-Version sollte geloggt werden, inklusive Client-Identifikation, um vor der endgültigen Abschaltung gezielt die verbleibenden Nutzer zu kontaktieren. Eine übliche Frist liegt zwischen sechs und zwölf Monaten zwischen Ankündigung und Abschaltung, abhängig davon, wie kritisch die API für externe Partner ist. Eine API-Versionierung, die diesen Prozess von Anfang an mitdenkt, vermeidet die Situation, alte Versionen aus Angst vor Ausfällen faktisch für immer am Leben halten zu müssen.
<?php
declare(strict_types=1);
// Deprecation headers per RFC 8594, attached without touching the body
final class DeprecationMiddleware
{
/** @param array<string, array{sunset: string}> $deprecated */
public function __construct(private readonly array $deprecated) {}
public function apply(string $version): void
{
if (!isset($this->deprecated[$version])) {
return;
}
header('Deprecation: true');
header("Sunset: {$this->deprecated[$version]['sunset']}");
header('Link: <https://mironsoft.de/docs/api/v2>; rel="successor-version"');
}
}
$middleware = new DeprecationMiddleware([
'1' => ['sunset' => 'Sat, 01 Nov 2026 00:00:00 GMT'],
]);
$middleware->apply($version);
8. Mehrere Versionen in einer PHP-Codebasis pflegen
Die technische Herausforderung bei API-Versionierung liegt selten in der Erkennung der Version, sondern in der Pflege mehrerer paralleler Verhaltensweisen in derselben Codebasis. Ein bewährtes Muster ist, jede Version einen eigenen Controller haben zu lassen, der aber dieselbe Domänenschicht darunter nutzt. Nur die Transformation von Domänenobjekt zu API-Response unterscheidet sich zwischen den Versionen, während Geschäftslogik, Validierung und Persistenz version-unabhängig bleiben.
Ein häufiger Fehler ist, Versionsverzweigungen mit if-Bedingungen quer durch die Geschäftslogik zu streuen, etwa if ($version === 'v1') { ... } in der Mitte einer Service-Methode. Das macht die API-Versionierung schnell unwartbar, weil jede neue Version die Zahl der Verzweigungen in bestehendem Code erhöht. Sauberer ist ein Presenter- oder Serializer-Layer pro Version, der die Domänenlogik unverändert lässt und ausschließlich die Ausgabeform anpasst.
9. API-Versionierung im direkten Vergleich
Die folgende Tabelle stellt die drei Strategien der API-Versionierung mit ihren jeweiligen Stärken und Schwächen gegenüber, als schnelle Entscheidungshilfe für das eigene Projekt.
| Strategie | Sichtbarkeit | REST-Konformität | Empfehlung |
|---|---|---|---|
| URI-Versionierung | Sehr hoch | Umstritten | Öffentliche APIs mit vielen externen Konsumenten |
| Custom-Header | Mittel | Hoch | Interne Microservices unter eigener Kontrolle |
| Accept-Header / Vendor-Type | Gering | Sehr hoch | APIs mit strengen Hypermedia-Anforderungen |
| Kein Deprecation-Header | Keine Vorwarnung | – | Immer vermeiden, unabhängig von der Strategie |
Die Tabelle zeigt: Es gibt keine universell richtige API-Versionierung, sondern eine Abwägung zwischen Sichtbarkeit für externe Entwickler und theoretischer REST-Konformität. Was in jedem Fall Pflicht bleibt, unabhängig von der gewählten Strategie, ist ein sauberer Deprecation-Prozess mit Vorlaufzeit.
Mironsoft
PHP-Backend-Entwicklung und API-Architektur
API-Versionierung, die Breaking Changes nicht zur Krise macht?
Wir entwerfen eine API-Versionierung, die zu eurer Kundenstruktur passt, inklusive Deprecation-Prozess und sauberer Trennung zwischen Domänenlogik und versionsspezifischer Ausgabe.
Strategie-Wahl
URI, Header oder Content-Negotiation passend zur Konsumenten-Struktur auswählen
Deprecation-Prozess
RFC-8594-Header, Monitoring und geplante Abschaltfristen aufsetzen
Codebasis-Refactoring
Presenter-Layer statt Versionsverzweigungen in der Geschäftslogik
10. Zusammenfassung
API-Versionierung ist kein einmaliges technisches Detail, sondern eine langfristige Vertrauensfrage gegenüber jedem, der die API konsumiert. URI-Versionierung bietet die höchste Sichtbarkeit, Header-Versionierung eine sauberere Trennung von Ressource und Metadatum, und Content-Negotiation die theoretisch reinste REST-Lösung. Für die meisten Teams mit externen Konsumenten ist URI-Versionierung der pragmatischste Startpunkt, ergänzt um Semantic Versioning zur klaren Kommunikation von Breaking Changes.
Der entscheidende Erfolgsfaktor liegt weniger in der gewählten Strategie als im begleitenden Prozess: eine klare Definition, was als Breaking Change zählt, ein Deprecation-Workflow mit RFC-8594-Headern und ausreichender Vorlaufzeit, und eine Codebasis, die Versionsunterschiede in einer eigenen Presenter-Schicht kapselt statt sie in der Geschäftslogik zu verteilen. Wer diese drei Bausteine konsequent umsetzt, macht API-Versionierung zu einem planbaren Prozess statt zu einer Serie überraschender Support-Tickets.
API-Versionierung in PHP — Das Wichtigste auf einen Blick
URI vs. Header
URI-Versionierung ist am sichtbarsten, Header-Versionierung sauberer im REST-Sinn. Beides ist in PHP mit wenig Code umsetzbar.
Semantic Versioning
MAJOR für Breaking Changes, MINOR für abwärtskompatible Erweiterungen, PATCH für Fehlerbehebungen ohne Verhaltensänderung.
Deprecation
RFC-8594-Header Deprecation und Sunset geben Konsumenten planbare Vorlaufzeit vor der Abschaltung.
Codebasis
Versionsunterschiede in einer Presenter-Schicht kapseln, Geschäftslogik bleibt version-unabhängig.