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

A vertical slice request pipeline from endpoint to handler, validation, persistence, event publication, and tests

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.

  1. Validator-Tests decken Grenzwerte ab.
  2. Handler-Tests decken Erfolg, fehlende Bestellung, Ownership-Fehler und Duplicate Request ab.
  3. Domain-Tests schuetzen Aggregate-Invarianten.
  4. Integration-Tests beweisen, dass Transaction und Outbox zusammen committen.
  5. Architekturtests verhindern Abhängigkeit von internen Klassen anderer Slices.
  6. 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

  1. Repraesentiert der Slice eine Nutzerintention?
  2. Orchestriert der Handler, statt Domain-Regeln zu verstecken?
  3. Werden Command- und Query-Modelle getrennt, wenn sie aus unterschiedlichen Gruenden aendern?
  4. Ist die Transaction-Grenze explizit?
  5. Ist Event Publishing ueber Outbox an den Datenbank-Write gebunden?
  6. Bleiben Consumer bei Duplicate Delivery idempotent?
  7. Reduziert die Projection echte Query-Kosten?
  8. Ist zusätzliche Infrastruktur durch Latency, Kosten oder Blast-Radius belegt?
  9. Sind Interfaces eng und use-case-orientiert?
  10. 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

Verwandte Beitrage

Verwandte Beitrage

Paylaş