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

StrategieVorteileNachteileAm besten für
URL-NamespaceKlar, einfachLängere URLs, Code-DuplikationDie meisten Rails-APIs
Header-basiertSaubere URLsSchwieriger zu testenAPIs mit vielen Versionen
SubdomainStarke IsolationKomplexes DNSGrundlegend 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:

  1. 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.
  2. Serializer statt Controller-Logik: Die eigentliche Versionsunterschiede liegen meist im Response-Format, nicht in der Businesslogik. Deshalb nutzen wir Serializer (z.B. ActiveModelSerializers oder Blueprinter) pro Version, während die Service-Schicht versionsfrei bleibt.
  3. 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.
  4. 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.

Verwandte Artikel