Playbook
Wie funktioniert ein Vertical Slice von innen? (Wie Funktioniert Ein Vertical Slice Von Innen)
Ein technischer Leitfaden zum inneren Ablauf eines Vertical Slice: Request, Validation, Handler, Aggregate, Outbox, Projection, Idempotenz, Performance, Tests…
Vertical Slice — Feature-orientiertes Engineering
Teil 2 von 4
Zusammenfassung in 30 Sekunden
Ein Vertical Slice ist keine Ordnerkonvention. Er hält eine Nutzerintention von Request, Validation, Authorization, Handler, Domain-Verhalten, Persistenz, Event-Publishing, Projection und Tests in einer Grenze zusammen.
Nutzerintention
-> Endpoint
-> Command / Query
-> Validation
-> Handler
-> Domain-Verhalten
-> Transaction
-> Outbox Event
-> Projection / Read Model
-> Tests und Observability
Das Ziel ist nicht vertikal aussehender Code. Das Ziel ist, Veränderung, Fehler, Tests und Kosten in der richtigen Feature-Grenze zu halten.
Das konkrete Problem
Nehmen wir eine Liefernotiz in einer Marketplace-Bestellung. Es sieht nach einem String-Feld aus, landet aber schnell in Checkout-Zusammenfassung, Merchant Operations, Shipping-Integration, Kunden-E-Mail und Audit Records. Die Architekturfrage lautet: Welche Nutzerintention besitzt dieses Verhalten, und wo muss Konsistenz abgeschlossen werden?
Begriffe bei der ersten Verwendung
Command
Eine Absicht, die Verhalten im System anfordert. Sie kann fehlschlagen.
Query
Eine nebenwirkungsfreie Leseanforderung.
Handler
Der Application-Orchestrator fuer einen Command oder eine Query.
Pipeline Behaviour
Eine Kette um den Handler fuer Validation, Authorization, Transaction, Logging und Idempotenz.
Outbox
Ein Pattern, das Business-Aenderung und publishbares Event in derselben lokalen Transaction speichert.
Projection
Ein Read Model, das fuer einen Screen oder Query-Pattern geformt ist.
Diese Teile sind nicht austauschbar. Der Handler orchestriert; das Domain Model besitzt Regeln. Die Pipeline schuetzt gemeinsame Application Concerns. Die Outbox zeichnet Events auf, die nicht verloren gehen duerfen.
Geschichte: der Liefernotiz-Slice
Ein praktischer Slice kann so aussehen:
Features/Orders/AddDeliveryNote
AddDeliveryNoteEndpoint.cs
AddDeliveryNoteCommand.cs
AddDeliveryNoteValidator.cs
AddDeliveryNoteHandler.cs
AddDeliveryNoteResponse.cs
AddDeliveryNoteTests.cs
Jede Datei hat genau einen Grund zu existieren. Der Endpoint besitzt den HTTP-Contract, der Command beschreibt die Absicht, der Validator schuetzt die Eingabegrenze, der Handler koordiniert den Use Case, das Aggregate erzwingt Business-Regeln und Tests beweisen Verhalten.
Algorithmusentwurf
algorithm AddDeliveryNote(command):
input: orderId, customerId, note, requestId
output: AddDeliveryNoteResult
validate command
reject if requestId was already processed
order = orderRepository.load(orderId)
reject if order does not exist
reject if order.customerId != customerId
order.addDeliveryNote(note)
outbox.add(DeliveryNoteUpdated(orderId, note, occurredAt))
transaction.commit()
return success(orderId, note)
Der normale Command Path ist O(1): ein Aggregate Lookup, eine begrenzte Regelmenge und eine begrenzte Anzahl Events. Idempotenz mit Unique Index ist O(log n); ein Key-Value Store kann im Durchschnitt O(1) erreichen. Der Speicherbedarf ist O(1), weil der Handler keine unbegrenzte Historie lädt.
Warum daraus ein Designproblem wird
Schichten koennen Klassen klein halten und trotzdem eine Business-Entscheidung ueber mehrere Stellen verteilen.
Controller validiert scheinbar
Service besitzt scheinbar Regeln
Repository schuetzt scheinbar Daten
Frontend prueft erneut
Tests schuetzen Call-Reihenfolge
Das ist keine verhaltensbezogene Single Responsibility. Vertical Slice verschiebt SOLID von Klassennamen zu Ownership von Veränderung. Interface Segregation bedeutet enge Ports wie IOrderRepository, IOutboxWriter und IRequestDeduplicationStore, nicht ein breites IOrderService fuer jeden Use Case.
CQRS und Mediator Pipeline
CQRS macht Absicht explizit. Ein Command fordert Verhalten an; eine Query liest ohne Nebenwirkung. Ein Mediator ist nur dann nuetzlich, wenn er wiederkehrende Application Concerns aus Handlern heraushält: Validation, Authorization, Transaction, Logging und Idempotenz. Die Bibliothekswahl muss trotzdem Lizenz, AOT-Support, Reflection-Kosten und Enterprise Approval berücksichtigen.
Alternativen und Entscheidung
| Alternative | Staerke | Akzeptierte Kosten |
|---|---|---|
| Einfacher CRUD Service | Niedrigste Startkosten | Shared Service wächst mit Verhalten |
| Vertical Slice ohne CQRS | Gute Feature-Locality | Absichtstrennung ist weniger explizit |
| Vertical Slice mit CQRS | Klare Absicht, Tests und Pipeline | Mehr Contracts und Disziplin |
| Physische Read/Write-Trennung | Unabhängige Read Performance | Eventual Consistency und Operations-Kosten |
| Microservice | Unabhängiges Deployment und Scale | Netzwerk-, Daten- und Observability-Kosten |
Meine Standardentscheidung ist gestuft: zuerst die Feature-Grenze im Modular Monolith beweisen; Read Models, Broker, Cache oder Deployment-Einheiten erst trennen, wenn Metriken die Kosten rechtfertigen.
Daten, Outbox und Kosten
Eine Read Replica ist kein CQRS. Eine Replica wiederholt dasselbe Schema; eine Projection formt Daten fuer die Query. Der gefaehrliche Ablauf ist, zuerst die Datenbank zu schreiben und danach ein Broker-Event separat zu senden. Wenn der Broker-Call nach dem Commit scheitert, existiert die Bestellung, aber das Event fehlt. Transactional Outbox schliesst diese Luecke durch eine lokale Transaction. Weil Lieferung oft at least once ist, muessen Consumer idempotent bleiben.
Vertical Slice macht auch die Scale-Einheit sichtbar. Checkout Commands brauchen starke Konsistenz und kurze Transactions; Catalog Queries brauchen Cache, Projection und hohe Concurrency. Jede Queue, jeder Cache, Load Balancer, Read Store und jede Deployment-Einheit kostet Geld. Deshalb sollten p95 Latency, Read/Write Ratio, Connection Saturation, Egress, Retry-Volumen und Blast Radius die Entscheidung treiben.
Performance und Tests
Ein heisser Command Path sollte unnoetige Allocations vermeiden. Eine Query sollte nur benoetigte Spalten lesen und nach Cursor paginieren. Eine Liste mit p Elementen kostet O(p) Zeit und Speicher; das gesamte Dataset zu laden kostet O(n) Speicher und ist in Production meist falsch.
- Validator-Tests decken Grenzwerte ab.
- Handler-Tests decken Erfolg, fehlende Bestellung, Ownership-Fehler und Duplicate Request ab.
- Domain-Tests schuetzen Aggregate-Invarianten.
- Integration-Tests beweisen, dass Transaction und Outbox zusammen committen.
- Architekturtests verhindern Abhängigkeit von internen Klassen anderer Slices.
- Projection-Tests beweisen, dass Duplicate Events harmlos bleiben.
Häufige Verwechslungen
❌ Vertical Slice bedeutet Controller-bis-Repository-Dateien in einen Ordner legen
✓ Es bedeutet, Verhalten, Daten und Tests um eine Nutzerintention zu gestalten.
❌ CQRS bedeutet immer getrennte Datenbanken
✓ CQRS beginnt als Absichtstrennung; physische Trennung ist eine eigene Kostenentscheidung.
❌ Mediator ist die Architektur
✓ Mediator ist Dispatch und Pipeline; Ownership definiert die Grenze.
❌ Outbox liefert exactly once
✓ Outbox verhindert Event-Verlust; Consumer brauchen weiter Idempotenz.
Entscheidungs-Checkliste
- Repraesentiert der Slice eine Nutzerintention?
- Orchestriert der Handler, statt Domain-Regeln zu verstecken?
- Werden Command- und Query-Modelle getrennt, wenn sie aus unterschiedlichen Gruenden aendern?
- Ist die Transaction-Grenze explizit?
- Ist Event Publishing ueber Outbox an den Datenbank-Write gebunden?
- Bleiben Consumer bei Duplicate Delivery idempotent?
- Reduziert die Projection echte Query-Kosten?
- Ist zusätzliche Infrastruktur durch Latency, Kosten oder Blast-Radius belegt?
- Sind Interfaces eng und use-case-orientiert?
- Schuetzen Architekturtests Feature-Grenzen?
Was bleiben sollte
Ein reifer Vertical Slice verwaltet die Oekonomie von Veränderung. CQRS, Mediator Pipelines, Outbox und Projections sind getrennte Werkzeuge; jedes muss seine Kosten verdienen. Beginnen Sie mit einer logischen Feature-Grenze und fuegen Sie physische Trennung erst hinzu, wenn Production-Evidenz sie stuetzt.
Als Naechstes betrachten wir, wie Vertical Slice, DDD Aggregates und Modular-Monolith-Grenzen in Production geschuetzt bleiben.
FAQ
Häufige Fragen
Stimmt es, dass „Vertical Slice bedeutet Controller-bis-Repository-Dateien in einen Ordner legen“?
Es bedeutet, Verhalten, Daten und Tests um eine Nutzerintention zu gestalten.
Stimmt es, dass „CQRS bedeutet immer getrennte Datenbanken“?
CQRS beginnt als Absichtstrennung; physische Trennung ist eine eigene Kostenentscheidung.
Worum geht es in „Wie funktioniert ein Vertical Slice von innen?“?
Ein technischer Leitfaden zum inneren Ablauf eines Vertical Slice: Request, Validation, Handler, Aggregate, Outbox, Projection, Idempotenz, Performance, Tests…
Gelernte Engineering-Prinzipien
- Die echte Grenze eines Slice ist Nutzerintention und Konsistenzentscheidung, die gemeinsam aendern.
- CQRS und Outbox machen Absicht und Zuverlaessigkeitskosten sichtbar; sie erzwingen keine physische Trennung.
- Production-Architektur wird ueber p95 Latency, I/O, Egress, Retry-Volumen und Rollback-Kosten gemessen.
Weiterlesen
Weiterlesen
Nachster Teil der Serie
Von Schichten zu Features: Warum entstand Vertical Slice?
Warum verlangsamt eine Schichtenarchitektur Veränderung, wenn ein System wächst? Ein Architekturleitfaden zu Vertical Slice als Entscheidung über Feature…
Verwandte Beitrage
Von CRUD (Create, Read, Update, Delete) zu CQRS (Command Query Responsibility Segregation): Das Problem ist nicht der Code, sondern das Modell
Was ist CQRS, worin liegt der Unterschied zu CRUD und wann sollte CQRS eingesetzt werden? Ein Leitfaden dazu, warum ein Modell in großen Systemen nicht…
Verwandte Beitrage
Wie funktioniert eine CQRS (Command Query Responsibility Segregation)-Pipeline? Anatomie von Command- und Query-Flows
Was ist eine CQRS Request Pipeline? Wie durchläuft eine HTTP-Anfrage Controller, MediatR, Pipeline Behaviors, Handler, Outbox und Read Model?