Backend-Architektur · Teamarbeit · Juli 2026, danach iteriert

Einen monolithischen BFF in Feature-Slices zerlegen, ohne einen Vertrag zu ändern

Während einer inkrementellen React-Migration war das ASP.NET-Core-Backend-for-Frontend der Integrationspunkt zwischen einer mandantenfähigen Webanwendung und ihrer internen API — und HTTP-Verdrahtung, Orchestrierung, Verträge und Testaufbau lagen in wenigen grossen geteilten Dateien, die jeder Entwickler anfassen musste. Ich habe den Refactor in Endpunkte, Services, Verträge und Tests pro Feature geleitet, abgesichert durch Swagger-Snapshots an jedem Kontrollpunkt: 307 Operationen erhalten, null Handler in der Composition Root übrig.

Meine Rolle
Architektur, Extraktionsreihenfolge, Kompositionsmodell, Kompatibilitätsprüfungen und Release-Sicherheit: meine. Die verschobene Feature-Logik: die ihrer Autoren.
Stack
  • ASP.NET Core
  • C#
  • Endpoint routing
  • Dependency injection
  • Swagger / OpenAPI
  • NUnit

Auf einen Blick

  • Zentrale Endpunktdatei von 5'657 auf 81 Zeilen; von 128 Registrierungen auf 0 Handler
  • Alle 307 Swagger-Operationen beim Staging-Umschalten erhalten
  • In Testing, Staging und Produktion in Betrieb innerhalb von rund zwei Wochen

Kontext

Der BFF sitzt zwischen einer mandantenfähigen Webanwendung und ihrer internen API und exponierte bereits Hunderte Operationen. HTTP-Verdrahtung, Anwendungsorchestrierung, Verträge und Testinfrastruktur lebten in wenigen grossen geteilten Dateien: Die Hauptendpunktdatei hatte 5'657 Zeilen mit 128 HTTP-Registrierungen erreicht (133 Verb-Pfad-Operationen über 19 Tags), ein geteilter Service vermischte fachfremde Domänen, und die Abhängigkeitsregistrierung von Produktion und Tests konnte auseinanderlaufen.

Mit mehreren Entwicklern, die gleichzeitig neue React-Bereiche bauten, brauchten alle dieselben BFF-Flächen. Die Struktur war eher zum Merge- und Koordinationsengpass geworden als zu einem Codequalitätsproblem — und das Ziel war, jedes Feature unabhängig verständlich und änderbar zu machen und dabei Routen, Payloads, Statuscodes, Authentifizierung, Mandantenverhalten und Frontend-Kompatibilität zu erhalten.

Was ich getan habe

  • Die HTTP-Fläche inventarisiert und in Swagger/OpenAPI-Snapshots eingefroren, bevor irgendetwas bewegt wurde.
  • Die Feature-Slice entworfen: React / RTK Query → /api/bff → Endpunktmodul des Features → Service des Features → benutzerbezogener Client der nachgelagerten API → Vertrag des Features → Frontend-Adapter und UI-Modell.
  • Eine einzige gemeinsame Kompositionsmethode für die Abhängigkeitsregistrierung eingeführt, genutzt vom Produktions- und vom Test-Host, damit beide nicht mehr auseinanderlaufen können; Session-Validierung, Mandantenkontext und Zugriff auf die nachgelagerte API blieben geteilt statt in jede Slice kopiert.
  • Endpunktgruppen, Anwendungslogik und DTOs in kontrollierten Kontrollpunkten verschoben und nach jedem Schritt Routen, Operation-IDs, Tags und Payload-Metadaten mit der Baseline verglichen.
  • Absicherungen für Start, Routen, Operation-IDs, Tags und Abhängigkeitsauflösung ergänzt, plus eine explizite Build- und Aktualitätsprüfung, nachdem sich zeigte, dass eine veraltete kompilierte Assembly den Swagger-Vergleich fälschlich bestehen lassen konnte.
  • Onboarding-Material in Markdown und HTML für Entwickler in der neuen Struktur geschrieben und später das lokale Quality Gate und die gemeinsamen Test-Fixtures optimiert, als sich die erste Version für die tägliche Pull-Request-Arbeit als zu langsam erwies.

Entscheidungen

Extraktion, kein Neuschreiben

Bestehende Feature-Logik behielt Verhalten und Autoren. Meine Verantwortung waren die Grenzen, die Reihenfolge und das Sicherheitsnetz, nicht das Neuimplementieren fachlicher Regeln.

Vertrag zuerst

OpenAPI-Snapshots definierten, was «unverändert» bedeutet: Routen, Payload-Formen, Statuscodes, Authentifizierung und Mandantenverhalten mussten an jedem Kontrollpunkt der Baseline entsprechen, bevor der nächste begann.

Eine Composition Root für Produktion und Tests

Derselbe Registrierungskatalog speist beide Hosts. Querschnittsbelange bleiben geteilt; eine Slice besitzt nur, was für ihr Feature spezifisch ist.

Erst ein strenges Gate, dann günstig machen

Das erste Quality Gate schützte Verträge und Tests, verlangte aber von Entwicklern, zusätzliche Schritte zu merken, und einige lokale Prüfungen waren langsam genug, um die normale PR-Arbeit zu unterbrechen. Den Schutz zu behalten und die Kosten zu senken war die zweite Hälfte der Arbeit.

Die konkrete Schwierigkeit

Der Refactor musste laufen, während dieselben Dateien von anderen Entwicklern für neue Features geändert wurden, also bewegte er sich in Kontrollpunkten, die klein genug zum Mergen und Verifizieren waren — nie als ein einzelner langlebiger Branch.

Die Entdeckung des falschen Grüns war wichtig: Eine veraltete kompilierte Assembly liess den Vertragsvergleich bestehen, obwohl sich der Code geändert hatte. Die Absicherung zuerst bauen und Aktualität prüfen zu lassen, machte aus einer Beruhigung einen echten Schutz.

Ergebnis

  • Beim Staging-Umschalten ging die zentrale Endpunktdatei von 5'657 auf 81 Zeilen und von 128 Registrierungen auf null Handler; dedizierte Endpunktdateien wuchsen von 18 auf 42, BFF-Service-Klassen von 20 auf 40 und Feature-Vertragsdateien von 18 auf 40, mit allen 307 erhaltenen Swagger-Operationen.
  • Die Struktur erreichte Testing, Staging und Produktion und wurde zum Muster, das das Team verwendet. Ich habe keine durch die Extraktion verursachten Regressionen beobachtet, und innerhalb von rund zwei Wochen nach Erkennen des Engpasses lief die modulare Struktur in allen Umgebungen.
  • Neue Bereiche konnten parallel gebaut werden, ohne fachfremde Arbeit in einer einzigen Endpunkt-, Service-, Vertrags- oder Testdatei zu konzentrieren; Request-Pfade wurden leichter nachvollziehbar, für Menschen wie für KI-gestützte Werkzeuge.
  • In einem späteren Snapshot war die Architektur auf 92 Endpunktmodule, 91 Service-Klassen und 93 Vertragsdateien gewachsen, weiterhin ohne Handler in der Composition Root — ein Wachstum, das der Produktarbeit des ganzen Teams gehört, nicht dem Refactor allein.

Zahlen, mit ihren Grenzen

  • Zentrale EndpunktdateiVorher5'657 ZeilenNachher81 Zeilennur diese Datei; Logik umverteilt, nicht gelöscht
  • HTTP-Handler in der Composition RootVorher128 RegistrierungenNachher0
  • Dedizierte EndpunktdateienVorher18Nachher42
  • BFF-Service-KlassenVorher20Nachher40
  • Feature-VertragsdateienVorher18Nachher40
  • Swagger-OperationenVorher307Nachher307beim Staging-Umschalten erhalten

Was diese Zahlen nicht sagen

  • Die Zeilenreduktion betrifft die zentrale Kompositionsdatei: Gesamtcode und Komplexität des BFF wurden in Feature-Grenzen umverteilt, nicht im selben Verhältnis gekürzt.
  • Es wurden keine unabhängigen Kennzahlen zu Merge-Konflikthäufigkeit, Durchlaufzeit, Fehlerrate oder eingesparten Stunden aufbewahrt. Der Rollout in alle Umgebungen und das Ausbleiben von Regressionen beruhen auf direkter Beobachtung während der Arbeit.
  • Die späteren Zählungen (92 / 91 / 93) spiegeln die fortlaufende Teamentwicklung, und die Beschleunigung des Quality Gate wurde nicht gemessen.

Was es zeigt

Ich kann einen geteilten Engpass in einem produktiven Backend unter Vertragsgarantie umstrukturieren, in Kontrollpunkten, die das Team verkraftet — und die Absicherungen danach so günstig machen, dass sie den täglichen Gebrauch überstehen.

Zuordnung

Extraktion, Kompositionsmodell und Absicherungen waren meine Arbeit, mit KI-gestützter Analyse und Dokumentation unter meinem Review. Die in die neuen Slices verschobenen Features wurden von Teamkollegen geschrieben und bleiben ihre.