Ein naheliegender Ansatz wäre, das Portal selbst als SCS zu implementieren. Das scheitert aber an der Natur von Portalen: Sie müssen Daten aus verschiedenen fachlichen Domänen aggregieren und dabei deren jeweilige Mini-Business-Logiken berücksichtigen.
Das Problem der Datenreplikation:
- Produktlogik: Preiskalkulationen, Verfügbarkeiten, Rabattregeln
- Segmentierungslogik: Nutzerkategorien, Berechtigungen, Personalisierung
- Content-Logik: A/B-Tests, zeitgesteuerte Kampagnen, Zielgruppen-Targeting
Ein Portal-SCS müsste Teile diese Logiken replizieren oder vereinfachen. Das führt zu Duplizierung von Logik, aber vorallem zu Horizontalisierung statt Vertikalisierung - genau das Gegenteil dessen, was SCS erreichen sollen.
Das Weiterentwicklungsproblem: Sobald sich die Business-Logik in den Ursprungs-Services ändert, muss das Portal-SCS nachgezogen werden. Damit wird die Vertikalisiserungsidee untergraben, Teams sind nicht mehr autonom, Deployments werden abhängig und die durch Vertikalisierung gewonnene agile Entwicklungsmöglichkeit wird beeinträchtigt.
Bei Mobile besonders kritisch: Native Mobile Apps laufen bereits separat und haben eigene Entwicklungszyklen. Ein Portal-SCS wäre hier nur eine zusätzliche Middleware-Schicht, die Komplexität hinzufügt, ohne Mehrwert zu schaffen.
Daher der bewusste Trade-off: Abhängigkeit zur Laufzeit statt während der Entwicklung. Das Portal-Gateway orchestriert zur Laufzeit, anstatt zur Entwicklungszeit zu replizieren.
Die Ausgangssituation
Vor kurzem stand ich vor einem typischen Architektur-Problem: Ein Portal sollte entstehen, das sowohl als Web-Anwendung als auch nativ auf Mobilgeräten funktioniert. Die Anforderungen klangen zunächst überschaubar:
- Zentrales CMS definiert Layout und stellt segmentbasierte personalisierte Inhalte bereit
- Segmentierungsservice ordnet Nutzer Zielgruppen zu (z.B. “premium,tech”)
- Produktservice liefert individuelle Produktdaten
- Weitere Datenquellen für zusätzliche Inhalte
- Personalisierung erfolgt über Segment-zu-Content-Zuordnung im CMS
Das Ganze sollte natürlich skalierbar, wartbar und performant werden. Die Herausforderung: Verschiedene Teams entwickeln unabhängig an ihren SCS, URLs und API-Endpunkte ändern sich, neue Services kommen dazu. Hardcoded Links zwischen Services würden die Teams wieder aneinander koppeln - genau das Gegenteil von dem, was wir mit SCS erreichen wollen.
Daher optimierten wir als erstes bezüglich Entwicklungsaufwand und Performance und arbeiteten mit dynamischen Linkrelationen: Services teilen mit, welche anderen Services sie benötigen, ohne deren URLs hart zu kodieren. HATEOAS macht diese Verknüpfungen zur Laufzeit auflösbar und hält die Services entkoppelt.
Client-Side Includes für Web: Der bewährte Ansatz
Für Web-Anwendungen gibt es bereits etablierte Lösungen. Eine davon ist Client-Side Includes mit HTMX. Anstatt JSON-APIs mit dynamischen Link-Verweisen auszuliefern, kann der Layout-Service direkt HTML generieren und die Einzelteile über HTMX nachladen.
Der HTMX-Ansatz
<!-- Layout-Service liefert HTML mit Platzhaltern -->
<!-- Die konkreten URLs kommen aus dem Service-Discovery -->
<div class="portal">
<section class="banner">
<h1>Willkommen im Portal</h1>
</section>
<!-- Segmentierte Inhalte werden dynamisch nachgeladen -->
<div hx-get="/api/segmented-content"
hx-trigger="load">
<div class="loading">Lade personalisierte Inhalte...</div>
</div>
<!-- Produktdaten parallel laden -->
<div hx-get="/api/products/featured"
hx-trigger="load"
hx-target="this">
<div class="loading">Lade Produkte...</div>
</div>
</div>
Dieser Ansatz hat viele Vorteile: Jedes Team kann sich voll auf HTML konzentrieren, das Markup kann weitgehend unabhängig von anderen angepasst werden - vorausgesetzt man hält sich an gewisse Vorgaben wie beispielsweise eine gemeinsame Pattern Library.
Die Lösung ist außerdem CDN-freundlich konzipiert: Das HTML-Grundgerüst lässt sich gut cachen, während personalisierte Inhalte per HTMX nachgeladen werden. Das führt zu einem interessanten Kostenvorteil: Das statische HTML-Grundgerüst kann am CDN-Edge gecacht werden und verursacht praktisch keine Kosten, während die personalisierten HTMX-Fragmente separat - aber ebenfalls CDN-optimiert - nachgeladen werden.
Verwandte Konzepte: Von SSI zu modernen Frontend-Microservices
Der HTMX-Ansatz erinnert konzeptuell an Server-Side Includes (SSI) oder Edge-Side Includes (ESI), löst die Fragmente aber clientseitig auf. Auch Zalando’s Tailor verfolgt einen ähnlichen Ansatz mit Server-Side Template Streaming für Frontend-Microservices.
Alternative: Web Components - Statt HTMX könnten die Services auch mittels Web-Components oder Custom-Elements integriert werden. Das hat den Vorteil, dass der Client keine zusätzliche Library benötigt. Dafür muss man die entsprechende Entwicklung und Wartung selbst übernehmen.
Das Portal-Gateway-Pattern steht in dieser Tradition der Service-Komposition, adaptiert die Konzepte aber für Multi-Platform-Anforderungen: JSON-Aggregation für Mobile, HTML-Fragmente für Web.
Das Mobile Problem
Der bewährte Web-Ansatz löst leider das mobile Problem nicht - native Apps können schlecht mit HTML-Fragmenten arbeiten. Hier bräuchte man wieder separate JSON-APIs oder müsste auf Web-Views zurückgreifen, was die Performance und User Experience beeinträchtigt.
Der HATEOAS-Traum vs. Mobile Realität
HATEOAS (Hypermedia as the Engine of Application State) ist ein REST-Architekturprinzip, bei dem Services ihre API-Struktur durch Links in den Responses selbst beschreiben. Ein typischer HATEOAS-Ansatz würde so funktionieren: Ein einziger API-Call zum CMS liefert ein Layout-Dokument mit Links zu allen benötigten Datenquellen, diese werden dynamisch abgerufen und das Portal wird zusammengesetzt. Elegant, flexibel, REST-konform.
Die mobile Realität sieht anders aus. Moderne Mobile Apps erwarten:
- Wenige HTTP-Requests für bessere Performance und zuverlässigere Kommunikation in manchmal unzuverlässigen mobilen Netzwerken
- Vorhersagbare API-Contracts für einfachere Entwicklung und längerfristige Abwärtskompatibilität gegenüber alten App-Installationen
- Offline-Fähigkeit durch lokales Caching
- Schnelle Ladezeiten für gute User Experience
Fünf bis zehn parallele HTTP-Requests pro Portal-Aufruf? Das macht keine Mobile-Entwickler:in glücklich. Dynamische Link-Navigation? Schwer zu cachen und zu debuggen.
Der pragmatische Ausweg: Portal-Gateway
Wie in vielen Architektursituationen liegt die Lösung im Kompromiss. Anstatt den Kampf zwischen HATEOAS-Eleganz und mobiler Realität zu führen, habe ich versucht beide Welten zu kombinieren.
Die Portal-Gateway-Lösung
Also bin ich einen Schritt zurückgegangen: Was wäre, wenn ein zwischengeschalteter Service die ganze HATEOAS-Komplexität übernimmt und der mobilen App nur noch eine einzige, vollständig aufgelöste Antwort liefert?
Das Portal-Gateway (ähnlich dem bekannten Backend-for-Frontend-Pattern) löst mehrere Probleme auf einmal:
- Mobile Apps bekommen eine einzige, optimierte API
- Backend-Services bleiben unverändert - weiterhin HATEOAS-konform und entkoppelt
- Komplexe Orchestrierung wird vom Client ins Backend verlagert
- Intelligente Caching-Strategien können service-übergreifend implementiert werden
Ein praktisches Beispiel
Die mobile App ruft auf:
GET /api/mobile/portal/homepage HTTP/1.1
Host: api.example.com
Authorization: Bearer <auth-token>
Und bekommt eine vollständige Antwort mit allen Daten zurück - Banner-Bereich, personalisierte Inhalte, Produktdaten, zusätzliche Inhalte - alles aufgelöst und aggregiert:
{
"portal": {
"layout": {
"id": "homepage-v2",
"version": "2.1.0",
"lastUpdated": "2025-08-25T10:30:00Z"
},
"elements": [
{
"id": "main-banner",
"type": "static-banner",
"position": 1,
"content": {
"title": "Willkommen zurück!",
"subtitle": "Entdecke unsere Neuheiten",
"imageUrl": "/images/hero-summer.jpg",
"ctaText": "Jetzt entdecken",
"ctaUrl": "/collections/new"
}
},
{
"id": "personalized-offers",
"type": "segmented-content",
"position": 2,
"content": {
"title": "Nur für Premium-Mitglieder",
"message": "Sarah, deine exklusiven 25% sind bereit!",
"offers": [
{
"title": "Smartwatch Pro",
"discount": "25%",
"originalPrice": 299.99,
"finalPrice": 224.99,
"validUntil": "2025-09-01"
}
],
"backgroundColor": "#1a1a2e"
}
},
{
"id": "featured-products",
"type": "product-grid",
"position": 3,
"content": {
"title": "Trending jetzt",
"products": [
{
"id": "prod-123",
"name": "Wireless Earbuds Elite",
"price": 149.99,
"imageUrl": "/images/earbuds-elite.jpg",
"rating": 4.8,
"inStock": true
},
{
"id": "prod-456",
"name": "Fitness Tracker Band",
"price": 89.99,
"imageUrl": "/images/fitness-band.jpg",
"rating": 4.6,
"inStock": true
}
],
"viewMoreUrl": "/products/trending"
}
}
]
},
"metadata": {
"requestId": "req-mobile-67890",
"userId": "user-sarah-456",
"timestamp": "2025-08-25T14:22:15Z",
"performance": {
"totalLatency": 245,
"cacheHits": 2,
"cacheMisses": 1,
"backendCalls": 3
}
}
}
Das Portal-Gateway extrahiert die Nutzer-ID aus dem Authentication-Token und leitet den Authorization-Header an Backend-Services weiter, die die Nutzer-ID für die Personalisierung verwenden.
Intern folgt das Portal-Gateway allen HATEOAS-Links, führt parallele und sequenzielle Datenabfragen aus, behandelt Abhängigkeiten zwischen Services und cached intelligent. Die mobile App muss davon nichts wissen.
Die Portal-Gateway-Architektur im Detail
Das Portal-Gateway implementiert die HATEOAS-Link-Auflösung serverseitig und bietet dabei intelligente Caching-Strategien.
HATEOAS-Link-Auflösung und Caching
Der Auflösungsprozess: Für eine eingehende Anfrage holt das Portal-Gateway ein Layout-Dokument, das HATEOAS-Links enthält. Diese werden durch nachfolgende HTTP-Anfragen aufgelöst - möglichst parallel für minimale Latenz - bis alle Links durch tatsächliche Daten ersetzt sind.
Schemalose JSON-Manipulation:
Das Portal-Gateway arbeitet bewusst schemalos - es versteht nur die Link-Struktur (_links
), nicht aber die eigentlichen Datenstrukturen. Backend-Services können ihre JSON-Schemas frei weiterentwickeln, ohne dass das Portal-Gateway angepasst werden muss. Links werden einfach durch die Response-Daten ersetzt, ohne Validierung oder Transformation der Inhalte.
Service-definierte Cache-Scopes:
# CMS Layout Service Response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=300, s-maxage=300
X-Cache-Scope: global
# Segmentation Service Response
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=3600, s-maxage=86400
X-Cache-Scope: user
# Product Service Response - kein Portal-Gateway-Caching gewünscht
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600
# Kein s-maxage = Portal-Gateway cached nicht, ruft immer frisch ab
Das Portal-Gateway interpretiert s-maxage
als Caching-Berechtigung und X-Cache-Scope
für den Gültigkeitsbereich (global/user). Services ohne s-maxage
werden nicht gecacht.
Backend-Services definieren ihre Caching-Strategien selbst über HTTP-Headers:
Komplexe Dependencies mit URI-Templates
Bei Segmentation-Services entsteht ein typisches Problem: Man benötigt nicht die Segmentierungs-Information selbst, sondern die daraus resultierenden Content-Bausteine. URI-Templates lösen dies elegant:
{
"layout": {
"elements": [
{
"id": "personalized-banner",
"type": "segmented-content",
"_links": {
"content": {
"href": "/api/content/personalized/{segments}",
"templated": true,
"depends_on": ["segmentation"]
}
}
}
]
},
"_links": {
"segmentation": {
"href": "/api/segmentation",
"templated": false
}
}
}
Der Auflösungsprozess:
- Layout enthält URI-Templates mit Dependencies:
"depends_on": ["segmentation"]
- Segmentierung wird über Auth-Header aufgelöst:
/api/segmentation
mitAuthorization: Bearer <token>
→{"segments": ["premium", "tech"]}
- Templates werden mit Segmenten gefüllt: CMS liefert unter
/api/content/personalized/premium,tech
die für diese Segmente passenden Inhalte - Optional: Fragmente für verschiedene Content-Bereiche:
#banner
,#sidebar
Dies reduziert HTTP-Requests und macht Abhängigkeiten explizit.
Getrennte Architekturen: Web und Mobile
Während der Arbeit überlegte ich, ob es nicht sinnvoller wäre, sowohl Web als auch Mobile mit dem Portal-Gateway auszuliefern. Aber da beide Zielgruppen komplett unterschiedliche Optimierungsziele haben, würde das wohl mehr Probleme schaffen als lösen. Also verwenden beide erstmal unterschiedliche Ansätze:
Web: HTMX + direkte SCS-Calls
Für Web-Anwendungen bleibt der HTMX-Ansatz optimal:
- Server-seitiges HTML für Grundstruktur
- Lazy Loading für personalisierte Fragmente
- CDN-Caching für statische Inhalte
- Kein SEO-Nachteil, da Portale ohnehin personalisiert sind
Mobile: Portal-Gateway für aggregierte JSON
Mobile Apps profitieren vom Portal-Gateway-Pattern:
- Einzelner API-Call statt mehrerer Requests
- Strukturierte JSON-Daten für native Rendering
- Offline-Caching der kompletten Portal-Response
- Aufwärtskompatibilität durch ignorierte JSON-Properties
Architektur-Diagramm
Vorteile der Trennung:
- Optimale Performance: Jeder Client nutzt seinen besten Ansatz
- Weniger Komplexität: Kein “Dual-Mode” Service nötig
- Einfacheres Debugging: Weniger Abstraktionsschichten
- Shared Backend: Gleiche SCS für beide Clients
SCS-Fundament und Trade-offs
Immer wenn man auf so eine größere architektonische Änderung stößt, ist ein Vergleich und eine Analyse der Trade-offs sinnvoll. Deswegen gebe ich hier nochmal ein paar Themen wieder, die bei der Abwägung eine Rolle gespielt haben:
Warum SCS + Portal-Gateway funktioniert
Die Architektur funktioniert besonders gut in einer SCS-Landschaft:
-
Team-Autonomie: Jeder SCS entwickelt unabhängig, definiert eigene Caching-Strategien über
X-Cache-Scope
- Skalierbarkeit: Backend-Services skalieren autark, Portal-Gateway orchestriert intelligent
- Flexibilität: HATEOAS-Links bleiben in SCS, keine zentralen API-Standards
Zentrale Trade-offs
Natürlich bringt die Portal-Gateway-Architektur auch Nachteile mit sich:
✅ Vorteil: Vereinfachte Mobile-Entwicklung
❌ Nachteil: Zusätzliche Komplexität - Ein weiterer Service, der entwickelt, deployed und überwacht werden muss.
✅ Vorteil: Performance durch Caching
❌ Nachteil: Cache-Konsistenz - Mehrere Cache-Ebenen bedeuten komplexere Invalidierungs-Strategien.
✅ Vorteil: Single API Call für Mobile
❌ Nachteil: Größere Response-Size - Aggregierte Responses sind größer als einzelne, spezifische API-Calls.
✅ Vorteil: Backend-Service-Unabhängigkeit
❌ Nachteil: Portal-Gateway als kritischer Pfad - Wird zum Single Point of Failure für alle mobilen Zugriffe.
Portal-Gateway Ownership und Team-Struktur
Team-Zuordnung: Das Portal-Gateway wird idealerweise vom CMS/Layout-Team betrieben, da es hauptsächlich Layout-Orchestrierung macht. Durch die schemalose Arbeitsweise bleibt das Team weitgehend unabhängig von Änderungen der Backend-Services - solange die Link-Struktur eingehalten wird.
Generische Aggregation: Da das Portal-Gateway nur Links auflöst und JSON-Objekte zusammenfügt, ohne die Inhalte zu interpretieren, können Backend-Teams ihre APIs frei weiterentwickeln. Das Portal-Gateway muss nur bei Änderungen der Link-Semantik (neue Link-Types, neue Dependency-Patterns) angepasst werden.
Übergang in den Produktionsbetrieb
Spätestens wenn das Portal-Gateway das erste Mal in Production läuft, wird einem klar: Die schöne Architektur nützt nichts, wenn man nicht weiß, was gerade passiert - oder wenn das Portal plötzlich 5 Sekunden zum Laden braucht. Also braucht es vernünftiges Monitoring und klare Performance-Ziele.
Mit dem Portal-Gateway übernimmt man zentrale Verantwortung - und das bedeutet, dass man sich im Voraus Gedanken über alle möglichen Ausfallszenarien machen muss. Was passiert, wenn der Segmentation-Service nicht antwortet? Wie reagiert man auf eine Lastspitze? Welche Alerts braucht man wirklich?
Die Erfahrung zeigt: Für jedes “Das wird schon nicht passieren” gibt es eine nächtliche Störung, die einem das Gegenteil beweist. Deswegen sollte man für die typischen Problemfälle bereits Lösungsstrategien und das richtige Tooling parat haben.
SLA-Definition und Monitoring: Das Portal-Gateway sollte klare Service Level Agreements definieren:
Mobile Portal SLAs:
- P95 Response Time: < 500ms
- Availability: > 99.9%
- Error Rate: < 0.1%
Backend-Service-Monitoring:
- Individual Service Latency Tracking
- Cache Hit Rate per Service
- Fallback Usage Rate
Distributed Tracing: Mit mehreren Backend-Services wird Performance-Debugging kompliziert - welcher Service ist der Flaschenhals? Die Lösung: Jeder Request bekommt eine eindeutige Trace-ID, die durch alle Services weitergegeben wird. Das Portal-Gateway loggt dann detailliert:
- Welche Services wurden aufgerufen
- Cache-Hit/Miss pro Service
- Individual Service Response Times
- Parallel vs. Sequential Call Performance
Operational Patterns für für häufige Problemfälle: Partial Failures handhaben: Bei Backend-Service-Ausfällen kann das Portal-Gateway auf ältere Cache-Einträge zurückgreifen (Graceful Degradation). Beispiel: Falls der Segmentation-Service nicht antwortet, wird die letzte bekannte Segmentierung verwendet, auch wenn sie 2 Stunden alt ist.
# Fallback-Strategy im Portal-Gateway
Cache-Control: private, max-age=3600, stale-while-revalidate=7200
API-Versionierung und Kompatibilität:
Der Mobile-Client sollte aufwärtskompatibel implementiert werden - zusätzliche JSON-Properties werden einfach ignoriert. Falls alte App-Versionen separat unterstützt werden müssen, kann der User-Agent
-Header durchgeschleift und entsprechende Response-Varianten bereitgestellt werden.
# Client-Request
User-Agent: MyApp/2.1.4 (iOS 16.0)
So kann das Portal-Gateway verschiedene Response-Schemas basierend auf Version liefern.
Monitoring und Debugging: Distributed Tracing wird essentiell - jeder Request sollte eine Trace-ID haben, die durch alle Services propagiert wird. Das Portal-Gateway sollte detaillierte Metriken über Backend-Call-Performance liefern.
Vorbereitung ist alles: Das Portal-Gateway-Team sollte Playbooks für typische Incident-Szenarien haben: Was tun bei Cache-Invalidierung? Wie debuggt man langsame Responses? Welche Backend-Services können notfalls abgeschaltet werden, ohne das gesamte Portal lahmzulegen?
Durch die klare Trennung zwischen Web und Mobile entfällt immerhin die komplexe “Dual-Mode” Logik - ein Problem weniger, um das man sich kümmern muss.
Fazit
HATEOAS und mobile Entwicklung müssen sich nicht ausschließen. Mit einem durchdachten Portal-Gateway-Pattern lassen sich die Vorteile beider Welten kombinieren: Backend-Services bleiben flexibel und entkoppelt, mobile Apps bekommen die Performance und Einfachheit, die sie brauchen.
Das Grundproblem der Service-Komposition ist nicht neu - von SSI über ESI bis zu modernen Frontend-Microservices wie Zalando’s Tailor haben verschiedene Generationen von Entwicklern ähnliche Probleme gelöst. Dieser Ansatz steht in dieser Tradition, adaptiert die Konzepte aber für heutige Multi-Platform-Anforderungen.
Die Kunst liegt darin, die richtige Balance zu finden zwischen architektureller Eleganz und praktischer Umsetzbarkeit. Manchmal bedeutet das, bewährte Patterns zu adaptieren und pragmatische Kompromisse einzugehen.
Ein Portal, das sowohl technisch durchdacht als auch praktisch nutzbar ist, ist letztendlich mehr wert als die akademisch perfekte Lösung, die niemand implementieren will.
Wie immer gilt: Architektur wächst mit den Anforderungen. Man sollte klein anfangen, aus den Erfahrungen lernen und die Lösung kontinuierlich verbessern - auch das ist agile Architekturarbeit.