Eine API ohne Versionierung wird zum Problem, sobald sich Anforderungen ändern. Clients brechen, Integrationen scheitern, und Breaking Changes lassen sich nicht mehr rückgängig machen. Rails bietet drei Strategien, um das zu vermeiden.
Rails 6 API-Tutorial: Namespacing und Versionierung
Welche Versionierungsstrategie passt?
URL-Namespace-Versionierung
Die Version steht direkt in der URL: /api/v1/users, /api/v2/posts.
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :users
end
namespace :v2 do
resources :users
end
end
end
Vorteile: Klar, einfach zu implementieren, gut debugbar. Nachteile: Längere URLs, mögliche Code-Duplikation zwischen Versionen.
Header-basierte Versionierung
Die Version wird über einen HTTP-Header angegeben. Die URL bleibt sauber.
# lib/api_constraints.rb
class ApiConstraints
def initialize(options)
@version = options[:version]
@default = options[:default]
end
def matches?(req)
@default || req.headers['Accept'].include?("application/vnd.example.v#{@version}")
end
end
Vorteile: Saubere URLs, weniger Code-Duplikation. Nachteile: Schwieriger zu testen, weniger sichtbar in Logs.
Subdomain-Versionierung
Versionen laufen auf eigenen Subdomains: v1.api.example.com, v2.api.example.com.
# config/routes.rb
scope constraints: { subdomain: "v1.api" }, module: :api do
scope module: :v1 do
resources :posts
end
end
Vorteile: Starke Trennung, unabhängige Skalierung pro Version. Nachteile: Komplexes DNS-Setup, höherer Infrastrukturaufwand.
Vergleich der Strategien
| Strategie | Vorteile | Nachteile | Am besten für |
|---|---|---|---|
| URL-Namespace | Klar, einfach | Längere URLs, Code-Duplikation | Die meisten Rails-APIs |
| Header-basiert | Saubere URLs | Schwieriger zu testen | APIs mit vielen Versionen |
| Subdomain | Starke Isolation | Komplexes DNS | Grundlegend verschiedene API-Versionen |
Für die meisten Projekte ist URL-Namespace-Versionierung die beste Wahl.
Wie setzt man versionierte Routen auf?
Gemeinsame Ressourcen mit Concerns
# config/routes.rb
Rails.application.routes.draw do
concern :api do
resources :users, :courses, :assignments
end
namespace :v1 do
concerns :api
end
namespace :v2 do
concerns :api
end
end
Controller pro Version organisieren
# app/controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
# v1-spezifische Logik
end
end
end
Jede Version bekommt ein eigenes Verzeichnis unter app/controllers/api/.
Wie werden alte API-Versionen sauber abgekündigt?
Die N-2-Regel
Unterstützen Sie die aktuelle Version und die zwei vorherigen. Das gibt Clients genug Zeit für die Migration.
Deprecation-Header setzen
# app/controllers/v1/users_controller.rb
class V1::UsersController < ApplicationController
before_action :add_deprecation_headers
private
def add_deprecation_headers
response.headers['Deprecation'] = 'true'
response.headers['Sunset'] = 'Wed, 31 Dec 2025 23:59:59 GMT'
response.headers['X-DEPRECATION-WARN'] = 'API v1 wird am 31. Dezember 2025 abgeschaltet. Bitte migrieren Sie auf v2.'
end
end
Checkliste für die Abkündigung
- Deprecation mindestens 6 Monate im Voraus ankündigen
- Sunset-Header in jede Antwort der alten Version einbauen
- Nutzungsstatistiken der alten Version überwachen
- Migrationsleitfaden bereitstellen
- Ablaufdatum kommunizieren und einhalten
Praktische Umsetzung: Der USEO-Ansatz
In unseren Rails-Projekten versionieren wir APIs nach folgenden Prinzipien:
- Basis-Controller pro Version: Wir erstellen einen
Api::V1::BaseController, von dem alle v1-Controller erben. Gemeinsame Logik (Auth, Error-Handling) lebt dort. Das vermeidet Duplikation und hält Versions-Upgrades übersichtlich. - Serializer statt Controller-Logik: Die eigentliche Versionsunterschiede liegen meist im Response-Format, nicht in der Businesslogik. Deshalb nutzen wir Serializer (z.B.
ActiveModelSerializersoderBlueprinter) pro Version, während die Service-Schicht versionsfrei bleibt. - Automatisiertes Deprecation-Monitoring: Wir loggen API-Calls pro Version und alertieren, wenn eine deprecated Version noch signifikant genutzt wird. So können wir Clients proaktiv kontaktieren.
- Contract Testing: Jede API-Version hat Request/Response-Specs, die als Vertrag dienen. Bei Änderungen an der v2 prüfen wir automatisch, ob v1-Contracts noch erfüllt werden.
Dieser Ansatz hat sich besonders bei APIs bewährt, die von mehreren externen Partnern genutzt werden.
FAQs
Wann sollte eine neue API-Version erstellt werden?
Nur bei Breaking Changes: Entfernung von Feldern, Änderung von Datentypen, Umbenennung von Endpunkten. Additive Änderungen (neue Felder, neue Endpunkte) erfordern keine neue Version.
Wie vermeidet man Code-Duplikation zwischen API-Versionen?
Gemeinsame Logik in Service Objects und Basis-Controller auslagern. Nur die Serializer und versionsspezifische Validierungen unterscheiden sich zwischen Versionen.