This blog post is also available in English
TL;DR
- SPA-first-APIs geben interne Details der Domäne oft an integrierende Systeme weiter.
- Wer Frontend-Endpunkte veröffentlicht, geht ein implizites Stabilitätsversprechen ein, das er sicherlich brechen wird.
- Selbst rein additive Änderungen tun weh, wenn Konsument:innen von Änderungen am Modell wissen müssen.
- Trenne UI-APIs und halte sie privat, getrennt von einer öffentlichen Integrations-API, um die Kopplung gering zu halten.
Ein einfaches Domänenmodell in Aktion
Werfen wir zunächst einen Blick auf ein einfaches Domänenmodell in Aktion. Stell dir vor, du erstellst ein Modell, mit dem ein Ladenbesitzer seine Öffnungszeiten eingeben kann. Unsere Domänenexpert:innen denken über ihre Öffnungszeiten vielleicht in drei Konzepten: Es gibt reguläre Öffnungszeiten, Feiertage und Sonderöffnungszeiten. Reguläre Öffnungszeiten (Regular Periods) werden zum Beispiel so definiert: „Im Sommer öffnen wir von Montag bis Freitag von 9:00 bis 12:00 und von 13:00 bis 20:00 Uhr sowie Samstags von 9:00 bis 12:00 Uhr.“ Dann gibt es Feiertage, an denen alle Läden schließen müssen, etwa den Tag der Arbeit (1. Mai in Deutschland). Schließlich kann es einmalige Sonderöffnungszeiten (Overrides) geben, bei denen die regulären Öffnungszeiten für einen Tag außer Kraft gesetzt werden (z. B. früher schließen wegen Personalmangels).
Unsere Anwendung bildet diese Konzepte, in denen unsere Domänenexpert:innen denken und ermöglicht es so, Öffnungszeiten durch das Anlegen, Ändern und Löschen von „Regular Periods”, „Overrides” und „Holidays” zu pflegen, wie in der UI unten zu sehen:
Unsere UI (in diesem Fall eine React-Anwendung) nutzt den Endpunkt /api/opening-hours, der nach Start- und Enddatum filtert. Das Antwortformat dieses Endpunkts sieht so aus:
{
"regular_periods": [
{
"name": "Summer Schedule 2026",
"starts_on": "2026-03-01",
"ends_on": "2026-08-31",
"weekdays": {
"monday": [
{
"opens_at": "09:00",
"closes_at": "12:00"
},
{
"opens_at": "13:00",
"closes_at": "20:00"
}
],
"tuesday": [
{
"opens_at": "09:00",
"closes_at": "12:00"
},
{
"opens_at": "13:00",
"closes_at": "20:00"
}
],
"wednesday": [
{
"opens_at": "09:00",
"closes_at": "12:00"
},
{
"opens_at": "13:00",
"closes_at": "20:00"
}
],
"thursday": [
{
"opens_at": "09:00",
"closes_at": "12:00"
},
{
"opens_at": "13:00",
"closes_at": "20:00"
}
],
"friday": [
{
"opens_at": "09:00",
"closes_at": "12:00"
},
{
"opens_at": "13:00",
"closes_at": "20:00"
}
],
"saturday": [
{
"opens_at": "09:00",
"closes_at": "12:00"
}
],
"sunday": []
},
"id": "8b8633b8-c027-4af6-8e6d-a22edf4faecb"
}
],
"holidays": [
{
"date": "2026-05-01",
"name": "Labour Day",
"id": "0bc5c05a-b8c2-5583-aed4-4f58d00bd050"
}
],
"day_overrides": [
{
"date": "2026-04-30",
"timespans": [
{
"opens_at": "09:00",
"closes_at": "11:00"
}
],
"note": "Prepare for Labour Day",
"id": "fe95781e-f83e-4f27-a404-da43fc537ff6"
}
]
}Unsere UI weiß, wie diese Einträge sich gegenseitig aufheben oder überschreiben, und rendert den Kalender entsprechend, indem sie z. B. bei Overrides die Regular Hours ausblendet. Die UI muss dafür wissen, wie das Domänenmodell aufgebaut ist. Zudem brauchst du Endpunkte, um jedes dieser unterschiedlichen Domänenkonzepte gegebenenfalls zu aktualisieren und zu löschen. Am Ende könnte die API, die du für dein Frontend entworfen hast, etwa so aussehen:
So weit, so gut.
Wenn externe Systeme integriert werden müssen
Stell dir nun vor, ein anderes Team benötigt diese Öffnungszeiten. Kein Problem! Wir haben ja bereits all diese API-Endpunkte, und fastapi hat uns freundlicherweise bereits diese OpenAPI-Spezifikation erstellt. Wir reichen sie einfach weiter, kein Aufwand für uns! Nicht so schnell. Die API deines Frontends weiterzugeben ist eine Entscheidung, die du vielleicht noch bereust.
Indem du diese Endpunkte an ein anderes Team weitergibst, sagst du: „Auf diese Endpunkte ist Verlass, sie ändern sich nicht.“ Doch früher oder später ändern sie sich eben doch. Nahezu jedes Feature deiner Anwendung geht schließlich mit einer Änderung der UI und der dazugehörigen API einher. Und wenn es so weit ist, musst du die Integration mit allen Systemen testen, die einen dieser Endpunkte möglicherweise genutzt haben, denn es gibt keine einfache Möglichkeit festzustellen, ob einer der Endpunkte von einer anderen Anwendung verwendet wird und diese durch deine Änderung kaputtgegangen ist.
Man könnte versuchen, der API nur noch optionale Felder und Endpunkte hinzuzufügen, um das Versprechen bestehender Endpunkte nicht zu brechen. Doch auch diese „additiven Änderungen” können sich auf subtilere Weise als „breaking change” erweisen. Erinnerst du dich an unsere drei verschiedenen Arten von Öffnungszeiten in der API (Regular Intervals, Overrides und Holidays)? Was passiert, wenn wir eine vierte hinzufügen? Da wir unsere Clients mit den Konzepten unseres Modells belastet haben, muss jede:r Konsument:in unserer API neue Konzepte und auch neue Vorrangregeln lernen, auch wenn er eigentlich nur an den effektiven Öffnungszeiten interessiert ist.
Das Problem ist, dass unsere API zu viel vom Domain Model zeigt. Die API, die wir für unsere SPA erstellt haben, legt Implementierungsdetails offen, die unsere API-Konsument:innen gar nicht interessieren sollten. Sie hatte nie die Systemintegration als Entwurfsziel, sondern wurde mit Blick auf andere Konsument:innen und deren Bedürfnissen entworfen.
Die API unserer UI muss von den verschiedenen Arten von Öffnungszeiten wissen. Andere Teams interessieren sich höchstwahrscheinlich nur für die „effektiven“ Öffnungszeiten, sie wollen einfach wissen, wann ein Laden öffnet oder schließt. Das ist eine andere Abstraktionsebene. Und die API unserer UI bietet diese nicht.
Deshalb sollten wir die APIs besser trennen. Wir erstellen eine (private) API für unsere UI und eine (öffentliche) API für die Systemintegration. Jetzt wirst du vielleicht einwenden: Das ist doch Duplikation! Und Duplikation ist immer böse! In diesem Fall würde ich argumentieren, dass uns die Duplikation etwas viel Wichtigeres verkauft: lose Kopplung. Wir können unsere API für die SPA jetzt ändern (was wir höchstwahrscheinlich täglich tun), ohne alle anderen Systeme erneut testen zu müssen. So sähe eine Trennung der Schnittstellen (interface segregation) aus:
In unserer OpenAPI-Doku haben wir jetzt ebenfalls zwei Sätze von Endpunkten, die öffentlichen:
und die privaten:
Durch zwei verschiedene Sätze von APIs können wir erkennen:
- Welche Endpunkte wir gefahrlos ändern können, ohne einen neuen Systemintegrationstest durchzuführen
- Welche Implementierungsdetails vor anderen Teams verborgen sind und was wir ändern können, ohne die Kompatibilität zu brechen
Außerdem können wir unterschiedliche Bedürfnisse effektiver bedienen: Vielleicht möchte einer unserer Konsument:innen Änderungen an unseren Öffnungszeiten „pollen”, weil er eine lokale Kopie vorhält, um von der Verfügbarkeit unseres Systems unabhängig zu sein. Etwas, wofür unser aktuelles API-Design vielleicht nicht ohne Weiteres geeignet ist.