Layered Architecture Gesamtwerk
Alle Entwicklungsphasen, Architekturgrenzen, Fachmodelle, Adapter, Tests, Qualitätsgates und bekannten Grenzen in einer einzigen offlinefähigen HTML-Datei.
Architektur auf einen Blick
Inhaltsübersicht
1. Orientierung und Maven-Module
Ziel
Die Layered-Variante besitzt sieben klar abgegrenzte Maven-Module. Jedes Modul hat genau eine primäre Verantwortung und eine dokumentierte Abhängigkeitsrichtung.
| Modul | Verantwortung | Erlaubte interne Abhängigkeiten |
|---|---|---|
| layered-domain | Fachtypen und Invarianten | keine |
| layered-application | Use Cases und benötigte Schnittstellen | layered-domain |
| layered-persistence | Datenbankadapter | layered-application, layered-domain |
| layered-integration | Messaging und Fremdsysteme | layered-application, layered-domain |
| layered-rest-api | REST-Eingang und DTO-Abbildung | layered-application, layered-domain |
| layered-bootstrap | Laufzeit-Zusammensetzung | REST, Persistence, Integration |
| layered-architecture-tests | Architektur-Fitness-Functions | alle Module ausschließlich im Test-Scope |
Buildbefehle
./mvnw -pl projects/01-layered -am verify
mvnw.cmd -pl projects/01-layered -am verifyArchitekturprüfung
Die Architekturtests sind ein eigenes Modul. Dadurch bleiben die produktiven Module frei von ArchUnit-Abhängigkeiten, während der Gesamtreaktor alle erzeugten Klassen gemeinsam prüfen kann.
2. Modulgrenzen
Erlaubter Fluss
REST ──────────────┐
Persistence ───────┼──> Application ───> Domain
Integration ───────┘
Bootstrap ───> REST + Persistence + Integration
Architecture Tests ───> alle Module, ausschließlich im Test-ScopeVerbotene Kopplungen
- Domain zu Application oder Technik
- Application zu REST, Persistence, Integration oder Bootstrap
- direkte Abhängigkeiten zwischen technischen Adaptern
- Fachlogik im Bootstrap-Modul
- Framework-Typen in Domain und Application
3. Domain-Modell
Ziel
Das Modul layered-domain schützt fachliche Invarianten mit reinem Java 21. Es enthält keine Framework-Anmerkungen und keine technischen Persistenzdetails.
Paketstruktur
com.aydinsude.enterprise.layered.domain
├── shared
├── customer
├── catalog
├── order
├── inventory
├── payment
├── billing
└── eventModellierungsprinzipien
- Identitäten sind typisierte Value Objects.
- Geld und Menge sind keine primitiven Parameter.
- Order ist die Konsistenzgrenze für Bestellpositionen und Statuswechsel.
- Produktdaten werden beim Bestellen als Snapshot übernommen.
- Invoice ist nach Ausstellung unveränderlich.
- Domain Events sind typisiert, ihre Zustellung bleibt ein Port der Anwendungsschicht.
Implementierte Zustandsübergänge
| Objekt | Ausgang | Operation | Ziel |
|---|---|---|---|
| Order | PLACED | Bestand vollständig reserviert | INVENTORY_RESERVED |
| Order | INVENTORY_RESERVED | Zahlung autorisiert | PAYMENT_AUTHORIZED |
| Order | INVENTORY_RESERVED | Zahlung fachlich abgelehnt | PAYMENT_FAILED |
| Customer | ACTIVE | suspend | SUSPENDED |
| Customer | SUSPENDED | activate | ACTIVE |
| Product | ACTIVE | deactivate | INACTIVE |
Schutz vor Primitive Obsession
Customer.Id, Product.Id, Order.Id, Payment.Id, Invoice.Id, Money, Quantity, Address, Sku, Email und IdempotencyKey verhindern, dass fachlich unterschiedliche Werte versehentlich vertauscht werden.
4. Application Services
Aufgabe der Schicht
Die Anwendungsschicht koordiniert einen Use Case von Eingang bis Ausgang. Sie entscheidet über Reihenfolge, Port-Aufrufe, Ereignisveröffentlichung und Fehlerklassifikation. Domain-Invarianten verbleiben in den Fachobjekten.
Services
RegisterCustomerService
- E-Mail normalisieren
- aktive Dublette prüfen
- Customer erzeugen und speichern
- CustomerRegistered veröffentlichen
RegisterProductService
- SKU normalisieren
- SKU-Dublette prüfen
- positiven Preis erzeugen
- Product speichern
- ProductRegistered veröffentlichen
PlaceOrderService
- Request-Fingerprint bilden
- Idempotency Key prüfen
- aktiven Kunden prüfen
- aktive Produkte laden
- Produkt-Snapshots in Order Lines übertragen
- OrderPlaced veröffentlichen
ReserveInventoryService
- bestehende Reservation idempotent behandeln
- alle Bestände laden
- alle Positionen vor Mutation prüfen
- Mengen reservieren
- Reservation und Order speichern
- InventoryReserved veröffentlichen
AuthorizePaymentService
- PaymentId-Konflikt prüfen
- technisch aufgeschobene Zahlung erneut ausführbar lassen
- Order-Zustand und Betrag prüfen
- Providerergebnis fachlich klassifizieren
- Payment und Order konsistent aktualisieren
- passendes Ereignis veröffentlichen
IssueInvoiceService
- Rechnungsfähigkeit prüfen
- zweite Rechnung verhindern
- Invoice aus Order Snapshot erzeugen
- InvoiceIssued veröffentlichen
Fehlervertrag
ApplicationException trägt
- stabilen ErrorCode
- fachliche oder technische Beschreibung
- Retry-Fähigkeit
Dadurch können spätere REST- und Messaging-Adapter Fehler einheitlich übersetzen, ohne die Geschäftslogik zu duplizieren.
5. Persistenz und Datenbank
Die Persistenzerweiterung ersetzt die bisherigen In-Memory-Adapter nicht, sondern ergänzt echte PostgreSQL- und JPA-Adapter. Die Domain bleibt vollständig frei von JPA- und Spring-Abhängigkeiten.
Lieferumfang
| Bereich | Umsetzung |
|---|---|
| Datenbank | PostgreSQL-Schema mit zwei Flyway-Migrationen |
| Mapping | explizite JPA-Entities und Data Mapper |
| Repositories | sieben Adapter für die vorhandenen Anwendungsports |
| Konsistenz | optimistische Versionierung und pessimistische Bestandsabfrage |
| Transaktionen | TransactionRunner im Application Layer, Spring-Adapter in Persistence |
| Events | transaktionale Outbox als persistierter Versandauftrag |
| Tests | Mapper-Unit-Test und PostgreSQL-Testcontainers-Integrationstests |
Zentrale Architekturentscheidung
JPA-Entities sind keine Domain Entities. Fachobjekte behalten Konstruktoren, Invarianten und typsichere IDs. Reconstitution Factories erlauben ausschließlich das kontrollierte Wiederherstellen gespeicherten Zustands.
Transaktionsgrenze
Jeder Application Service führt seinen vollständigen Ablauf über TransactionRunner.required(...) aus. In Unit-Tests arbeitet eine direkte Implementierung; im Spring-Betrieb verwendet SpringTransactionRunner einen TransactionTemplate.
Nächster Schritt
Die Erweiterung ergänzt REST-Adapter, Fehlerabbildung, Messaging-Dispatcher und die Veröffentlichung der vorgemerkten Outbox-Ereignisse.
6. Datenbankschema
Tabellen
| Tabelle | Verantwortung |
|---|---|
lay_customer |
Kunde und Status |
lay_product |
Katalogprodukt und aktueller Preis |
lay_orders / lay_order_line |
Bestellung und unveränderlicher Produktsnapshot |
lay_stock_item |
verfügbarer und reservierter Bestand |
lay_reservation / lay_reservation_line |
idempotenter Reservierungsnachweis |
lay_payment |
Providerentscheidung und Wiederholungsidentität |
lay_invoice / lay_invoice_line |
unveränderlicher Rechnungssnapshot |
lay_outbox_event |
atomar vorgemerkte Integrationsereignisse |
Schutzregeln
- E-Mail, SKU, Idempotency Key, Rechnungsnummer und Bestellung-zu-Rechnung sind eindeutig.
- Mengen und Geldbeträge besitzen Datenbank-Checks.
- Bestandswerte erfüllen
0 <= reserved <= on_hand. - Versionsspalten schützen veränderliche Datensätze.
- Fremdschlüssel verhindern verwaiste Kernobjekte.
7. JPA-Mapping
Trennung
Die Domain enthält keine Persistenzannotation. JPA-Entities bilden ausschließlich das relationale Modell ab. PersistenceMappers übersetzt in beide Richtungen.
Snapshot-Mapping
Bestell- und Rechnungspositionen speichern Produktbezeichnung, SKU, Einzelpreis, Währung, Menge und Positionssumme. Spätere Katalogänderungen verändern historische Bestellungen und Rechnungen nicht.
Nebenläufigkeit
StockItemEntity besitzt eine optimistische Versionsspalte. Die Mehrpositionsabfrage verwendet zusätzlich PESSIMISTIC_WRITE, sortiert Produkt-IDs stabil und hält die Sperren bis zum Ende der Use-Case-Transaktion.
8. Transaktionen und Outbox
Unit of Work
Die Anwendungsschicht definiert eine transport- und frameworkfreie Transaktionsschnittstelle. Dadurch bleibt die fachliche Orchestrierung unabhängig, bestimmt aber weiterhin die fachlich richtige Transaktionsgrenze.
Transactional Outbox
Der Event Publisher schreibt Business Events in lay_outbox_event. Aggregate-Änderung und Event-Vormerkung erfolgen in derselben lokalen Datenbanktransaktion. Die Erweiterung ergänzt den Dispatcher zu Kafka oder Redpanda.
Fehlerverhalten
- Rollback entfernt sowohl Fachänderung als auch Outbox-Eintrag.
- Event-IDs sind Primärschlüssel und verhindern doppelte Vormerkungen.
- Status, Versuchszähler und Versionsspalte bereiten sichere Wiederholungen vor.
9. REST und Integration
Ziel
Die Integrationsstufe macht die bisherige Layered-Implementierung über den gemeinsamen HTTP-Vertrag erreichbar und liefert die bereits transaktional vorgemerkten Business Events an Kafka oder Redpanda aus. Der Fachkern bleibt unverändert frameworkfrei.
Umgesetzter vertikaler Ablauf
HTTP JSON
→ Controller
→ DTO-Mapper
→ Application Service
→ Repository- und Gateway-Ports
→ PostgreSQL + Outbox in einer Transaktion
→ geleaster Outbox-Dispatcher
→ Kafka/RedpandaREST-Ergebnis
| Use Case | Endpunkt | Erfolg |
|---|---|---|
| UC-01 | POST /api/v1/customers |
201 Created |
| UC-02 | POST /api/v1/products |
201 Created |
| UC-03 | POST /api/v1/orders |
201 Created |
| UC-04 | POST /api/v1/orders/{orderId}/inventory-reservations |
200 OK |
| UC-05 | POST /api/v1/orders/{orderId}/payment-authorizations |
200 OK |
| UC-06 | POST /api/v1/orders/{orderId}/invoices |
201 Created |
Fehlersemantik
Fachliche Fehler werden durch einen zentralen Exception Translator in Problem Details überführt. Stabile Fehlercodes, HTTP-Status, Retry-Hinweis, Instanzpfad und Korrelationskennung bleiben voneinander getrennt.
Event-Auslieferung
Die Outbox wurde um Leasing erweitert. Ein Worker beansprucht eine begrenzte Menge von Datensätzen, kennzeichnet sie als IN_FLIGHT und bestätigt sie erst nach Broker-Acknowledgement. Abgelaufene Leases können erneut übernommen werden. Nach Erreichen der maximalen Versuche wird ein Datensatz als FAILED markiert.
Lokaler Runtime-Stack
PostgreSQL und Redpanda werden über Docker Compose gestartet. Das lokale Spring-Profil verwendet ein deterministisches Payment-Gateway-Fake. Außerhalb des lokalen Profils steht ein HTTP-Gateway als Anti-Corruption Layer bereit.
Ergebnis
REST, Persistenz und Messaging bleiben getrennte äußere Adapter. Die Zusammensetzung erfolgt ausschließlich im Bootstrap-Modul.
10. REST API
Thin Controller
Controller lösen nur Transportaufgaben: Header lesen, Eingaben validieren, DTOs in Commands überführen, Use Cases aufrufen und eine HTTP-Antwort erzeugen. Geschäftsregeln befinden sich weiterhin in Domain und Application.
DTO-Grenze
Die externen JSON-Modelle sind nicht mit Domain-Entities identisch. Dadurch bleiben Feldnamen, Validierungsregeln, API-Versionierung und Domänenentwicklung unabhängig voneinander.
@RestController
@RequestMapping("/api/v1/orders")
final class OrderController {
private final UseCases.PlaceOrder useCase;
@PostMapping
ResponseEntity<ResourceResponse> place(
@RequestHeader("Idempotency-Key") String key,
@RequestBody PlaceOrderRequest request) {
var id = useCase.execute(RestCommandMapper.order(request, key, correlationId));
return ResponseEntity.created(location(id)).body(response(id));
}
}
Korrelations- und Idempotenzkennung
X-Correlation-ID verfolgt einen Vorgang über alle technischen Grenzen. Idempotency-Key besitzt eine andere Aufgabe: dieselbe fachliche Mutation darf bei Wiederholung nicht doppelt entstehen.
Problem Details
| Fehlergruppe | Status | Beispiel |
|---|---|---|
| ungültige Transportdaten | 400 | fehlendes Pflichtfeld |
| Ressource fehlt | 404 | unbekannte Bestellung |
| Eindeutigkeitskonflikt | 409 | verwendeter Idempotenzschlüssel |
| fachlich nicht ausführbar | 422 | Bestand reicht nicht |
| abhängiger Dienst gestört | 503 | Zahlungsanbieter nicht erreichbar |
Architekturregel
Das REST-Modul kennt Application und Domain, aber weder Persistenz noch Integration. Dadurch kann derselbe Use Case später über einen anderen Eingangskanal aufgerufen werden.
11. Event-Auslieferung
Warum die Outbox nicht direkt an Kafka sendet
Ein Use Case schreibt Fachzustand und Outbox-Datensatz in dieselbe Datenbanktransaktion. Ein separater Dispatcher übernimmt die spätere Auslieferung. Dadurch gibt es keinen ungeschützten Dual Write zwischen PostgreSQL und Broker.
Lease-Ablauf
PENDING
→ Worker beansprucht Datensatz
IN_FLIGHT + locked_by + locked_until
→ Broker bestätigt
PUBLISHED
oder
IN_FLIGHT
→ Fehler
PENDING für Wiederholung
→ maxAttempts erreicht
FAILEDCompeting Consumers
Mehrere Instanzen dürfen parallel arbeiten. Die Persistenzseite beansprucht Datensätze unter pessimistischem Lock. Der Dispatcher kennt nur den OutboxStorePort und hängt nicht vom JPA-Modul ab.
Kafka-Envelope
Der JSON-Payload bleibt unverändert. Technische Metadaten werden zusätzlich als Kafka-Header übertragen
event-idevent-typeaggregate-typecorrelation-idoccurred-at
Der Aggregate-Schlüssel wird als Record-Key verwendet und erhält dadurch innerhalb einer Kafka-Partition eine stabile Reihenfolge.
Kafka und Redpanda
Die Auslieferung nutzt ausschließlich das Kafka-Protokoll. Die Anwendung muss für Redpanda keinen separaten Integrationscode besitzen.
12. Runtime-Stack
Composition Root
Nur das Bootstrap-Modul setzt alle Adapter und frameworkfreien Services zusammen. Die Application Services erhalten ihre Ports über Konstruktoren und benötigen keine Spring-Annotationen.
Runtime-Komponenten
| Komponente | Aufgabe |
|---|---|
| Spring Boot | Prozess und Konfiguration |
| PostgreSQL | Fachzustand und Outbox |
| Flyway | Schemafortschreibung |
| Redpanda | lokaler Kafka-kompatibler Broker |
| Actuator | Health, Info und Metriken |
| lokales Payment-Gateway | reproduzierbare Autorisierungsentscheidungen |
Startreihenfolge
docker compose -f projects/01-layered/infra/docker-compose.yml up -d
./mvnw -pl projects/01-layered/layered-bootstrap -am spring-boot:run
Profile
Das Profil local aktiviert das deterministische Payment-Gateway. Andere Profile verwenden den HTTP-Adapter zum externen Zahlungsanbieter. Die fachliche Anwendungsschicht bleibt in beiden Fällen identisch.
Betriebsrelevante Einstellungen
Batchgröße, Lease-Dauer, maximale Versuche, Send-Timeout und Topic-Präfix sind typisiert konfigurierbar. Zugangsdaten und Endpunkte werden über Umgebungsvariablen überschrieben.
13. Teststrategie
Leitgedanke
Tests werden nach Verantwortung und Fehlerart verteilt. Ein Fachwert benötigt keinen Spring-Kontext. Ein JPA-Mapping benötigt keine REST-Schicht. Ein HTTP-Fehlervertrag darf nicht nur indirekt über einen vollständigen Systemtest geprüft werden.
Testpyramide
| Testart | Schwerpunkt | Typische Beispiele |
|---|---|---|
| Value-Object-Test | lokale Invarianten | Geld, Menge, Adresse, E-Mail |
| Aggregate-/Entity-Test | Zustandsübergänge | Bestellung, Bestand, Zahlung |
| Application-Test | Geschäftsablauf und Ports | Bestellung anlegen, Bestand reservieren |
| Mapper-Test | verlustfreie Übersetzung | Domain zu JPA, Request zu Command |
| Adapter-Test | technische Verträge | PostgreSQL, REST, Kafka |
| Architekturtest | unerlaubte Abhängigkeiten | Domain ohne Spring, Adapter ohne Querverweise |
| Regressionslauf | durchgehender Kernprozess | UC-01 bis UC-06 |
Test-Doubles
In-Memory-Repositories werden nur für Application-Tests eingesetzt. Sie simulieren keine JPA-Semantik und ersetzen daher keine PostgreSQL-Integrationstests. Fremdsysteme werden über Ports als kontrollierbare Fakes angebunden, damit Ablehnung, Timeout und Wiederholung gezielt geprüft werden können.
Mutation Testing
Das Profil mutation konzentriert sich auf Domain und Application. Die Mutationsschwelle beträgt 70 Prozent, die erforderliche Testabdeckung 75 Prozent. Adapter werden nicht pauschal mutiert, weil dort Contract- und Integrationstests einen höheren Erkenntniswert besitzen.
Grenzen des aktuellen Prüfstands
Der bereitgestellte Build enthält echte Maven-Konfigurationen für JUnit, ArchUnit, JaCoCo, PIT, SpotBugs und Testcontainers. In der Erzeugungsumgebung konnten nur frameworkfreie Java-21-Kompilierung, Regressionsszenarien und statische Prüfungen ausgeführt werden. Ein vollständiger Maven- und Containerlauf bleibt als expliziter externer Prüfschritt dokumentiert.
14. Qualitätsgates
Maven-Profile
./mvnw -pl projects/01-layered -am verify
./mvnw -pl projects/01-layered -am verify -Pquality
./mvnw -pl projects/01-layered/layered-domain -am test -Pmutation
./mvnw -pl projects/01-layered/layered-application -am test -Pmutation
Qualitätsgates
| Gate | Domain | Application | Adapter |
|---|---|---|---|
| JaCoCo Linien | mindestens 80 % | mindestens 75 % | Bericht ohne starre globale Quote |
| JaCoCo Zweige | mindestens 70 % | mindestens 65 % | risikobasiert |
| PIT Mutation | mindestens 70 % | mindestens 70 % | nicht standardmäßig aktiviert |
| PIT Coverage | mindestens 75 % | mindestens 75 % | nicht standardmäßig aktiviert |
| SpotBugs | Medium oder höher schlägt fehl | Medium oder höher schlägt fehl | Medium oder höher schlägt fehl |
| Spotless | keine abweichenden Zeilenenden oder Leerzeichen | gleich | gleich |
Warum keine globale Abdeckungszahl?
Eine einzige Quote würde einfache Datenklassen und schwierige Integrationspfade vermischen. Die harten Gates liegen deshalb im Fachkern. Für Adapter zählen zusätzlich Contract-Tests, Testcontainers und klar benannte Fehlerszenarien.
Software Bill of Materials
Das Qualitätsprofil erzeugt eine aggregierte CycloneDX-SBOM im JSON- und XML-Format. Sie beschreibt direkte und transitive Buildbestandteile und unterstützt spätere Lieferketten- und Schwachstellenprüfungen.
Reproduzierbarkeit
Der Root-Build verwendet Java 21, einen festen Maven-Wrapper, explizite Pluginversionen und einen festen Build-Zeitstempel. Dadurch werden lokale und CI-Builds besser vergleichbar.
15. Entwurfsmuster
| Pattern | Zweck | Einsatzort | Begründung |
|---|---|---|---|
| Fitness Function | Schichten und Zyklen automatisch prüfen | Architekturtestmodul | verhindert schleichende Strukturverletzungen |
| Test Pyramid | Tests passend zur Verantwortung wählen | alle Module | vermeidet unnötig langsame Vollsystemtests |
| Quality Gate | Build bei relevanten Defekten stoppen | Quality-Profil | Qualitätskriterien werden reproduzierbar |
| Mutation Testing | Testwirksamkeit prüfen | Domain und Application | überlebt ein Fehler, fehlt eine wirksame Assertion |
| SBOM | Komponentenbestand ausgeben | Aggregator | Lieferkettentransparenz |
| Pattern | Zweck | Einsatzort | Begründung |
|---|---|---|---|
| DTO | HTTP-Vertrag isolieren | REST-Modul | Domain bleibt transportfrei |
| Thin Controller | Request/Response koordinieren | sechs Controller | keine Fachlogik im Eingangslayer |
| Exception Translator | stabile Problem Details | REST-Fehlerbehandlung | einheitliche externe Fehlersemantik |
| Outbox Dispatcher | vorgemerkte Events ausliefern | Integration-Modul | kein Dual Write im Use Case |
| Lease | konkurrierende Worker koordinieren | JPA-Outbox-Store | skalierbare, wiederanlaufbare Verarbeitung |
| Gateway | Kafka und Payment Provider kapseln | Integration-Modul | Fremdsysteme austauschbar |
| Composition Root | Module verdrahten | Bootstrap | äußere Frameworkkopplung konzentriert |
Persistenzmuster
| Pattern | Zweck | Einsatzort | Begründung |
|---|---|---|---|
| Data Mapper | Domain und JPA-Modell übersetzen | PersistenceMappers |
Keine ORM-Annotationen im Fachkern |
| Repository Adapter | Application Ports technisch implementieren | *JpaAdapter |
Dependency Inversion bleibt erhalten |
| Unit of Work | vollständigen Use Case atomar ausführen | TransactionRunner, SpringTransactionRunner |
fachliche Transaktionsgrenze ohne Spring-Kopplung |
| Transactional Outbox | Zustand und Event atomar speichern | JpaOutboxEventPublisher, lay_outbox_event |
kein Dual-Write-Verlust |
| Optimistic Lock | verlorene Updates erkennen | Versionsspalten | sichere konkurrierende Änderungen |
| Pessimistic Lock | Mehrpositionsbestand serialisieren | findAllForUpdate |
Überreservierung verhindern |
| Reconstitution Factory | gespeicherten Zustand kontrolliert laden | Domain restore(...) |
keine öffentlichen Setter |
Fachmodell und Application Services
| Pattern | Zweck | Einsatzort | Begründung |
|---|---|---|---|
| Aggregate Root | kontrollierte Konsistenz und Zustandsübergänge | domain.order.Order |
Bestellpositionen und Order-Status ändern sich nur über definierte Operationen |
| Entity | Identität und Lebenszyklus abbilden | Customer, Product, StockItem, Payment, Invoice | fachliche Gleichheit hängt nicht nur von Attributwerten ab |
| Value Object | validierte unveränderliche Werte | Money, Quantity, Address, IDs, Email, SKU | verhindert Primitive Obsession und verteilt keine Validierung |
| Snapshot Value Object | historische Bestell- und Rechnungsdaten erhalten | Product.Snapshot, Order.Line, Invoice.Line | spätere Katalogänderungen verändern abgeschlossene Vorgänge nicht |
| Service Layer | Use Cases orchestrieren | sechs Application Services | einheitlicher Einstieg für REST, Messaging und Tests |
| Repository | Speicherzugriff abstrahieren | Ports.CustomerRepository bis InvoiceRepository | Anwendung bleibt unabhängig von JPA und Datenbank |
| Strategy | Zahlungsanbieter austauschbar machen | Ports.PaymentGateway | Providerverhalten wird hinter einem stabilen Vertrag gekapselt |
| Domain Event | fachlich relevante Ergebnisse ausdrücken | BusinessEvent | entkoppelt Kernentscheidung von technischer Zustellung |
| Idempotent Receiver | Wiederholungen sicher behandeln | PlaceOrderService, ReserveInventoryService, AuthorizePaymentService | Netzwerk- und Messaging-Wiederholungen erzeugen keine Duplikate |
| In-Memory Fake | Application Services ohne Mock-Kaskaden testen | InMemoryPorts | realistischere Zustandsprüfung bei geringem Testaufwand |
| Application Exception | stabile Fehlerklassifikation | ApplicationException | Adapter können Fehler ohne Regelduplizierung übersetzen |
Modulgrundlage
| Pattern | Zweck | Einsatzort | Begründung |
|---|---|---|---|
| Layered Architecture | technische Verantwortungen trennen | Maven-Modulgraph | verständliche Referenzarchitektur |
| Composition Root | technische Implementierungen zusammensetzen | layered-bootstrap | keine Geschäftslogik im Startmodul |
| Fitness Function | Grenzen automatisiert prüfen | layered-architecture-tests | verhindert schleichende Architekturverletzungen |
16. Architekturbewertung
Stärken im vorliegenden Projekt
- Der Einstieg ist klar: REST ruft Application Services auf, diese verwenden Domain und Ports.
- Technische Adapter können getrennt ersetzt und getestet werden.
- Die fachlichen Kernmodule bleiben frei von Spring, JPA, HTTP und Kafka.
- Der Composition Root zeigt die konkrete Laufzeitverdrahtung an einer Stelle.
- Architekturregeln verhindern direkte Querverbindungen zwischen Adaptern.
Konkrete Risiken
Zentrale Application-Schicht kann wachsen
Mit weiteren Use Cases steigt die Gefahr, dass Application Services viele Repositories koordinieren. Die heutige Struktur ist für sechs Abläufe übersichtlich; bei deutlich mehr Prozessen muss auf fachliche Modulgrenzen geachtet werden.
Gemeinsames Domain-Modul kann Fachgrenzen verwischen
Customer, Catalog, Ordering, Inventory, Payment und Billing liegen in einem Domain-Modul. Maven verhindert daher keine unerwünschte fachliche Kopplung zwischen diesen Bereichen. Die spätere DDD-Variante wird genau diesen Punkt mit Bounded Contexts untersuchen.
Transaktionen sind technisch zentralisiert
Der Transaction Runner schützt Use Cases vor Spring-Abhängigkeiten. Dennoch bleiben große synchrone Transaktionen möglich, wenn ein Service zu viele Zuständigkeiten übernimmt.
Outbox löst nicht jede Zustellfrage
Die Outbox verhindert das klassische Dual-Write-Problem zwischen Datenbank und Broker. Sie garantiert jedoch keine exakt einmalige Verarbeitung beim Empfänger. Konsumenten benötigen weiterhin Idempotenz.
REST-Vertrag und Fachmodell entwickeln sich unterschiedlich
Eigene DTOs schützen das Fachmodell, verursachen aber Mappingaufwand. Fehlende Mappingtests würden schleichende Vertragsabweichungen begünstigen.
Geeigneter Einsatz
Die Variante eignet sich gut für Teams, die eine verständliche modulare Anwendung mit klaren technischen Schichten benötigen. Sie ist weniger geeignet, wenn mehrere stark autonome Fachbereiche unabhängig veröffentlicht werden sollen oder wenn die Fachsprache selbst die primäre Modularisierungsachse bilden muss.
Vergleichshypothese
TDD-First wird zeigen, welche Struktur aus testgetriebenen Schritten entsteht. DDD wird zeigen, wie stark sich das System verändert, wenn fachliche Kontexte statt technischer Layer die oberste Modularisierungsgrenze bilden.
17. Qualitätsnachweise
12. Juli 2026
- sieben Maven-Module und neun POM-Dateien strukturell geprüft
- Quality- und Mutation-Profil geprüft
- frameworkfreier Fachkern mit Java 21 kompiliert
- eigenständige Regressionssuite erfolgreich ausgeführt
- zusätzliche Domain- und Architekturtests erfasst
- Gesamtwerk, Navigation, Offlinefähigkeit und ZIP-Integrität geprüft
- vollständiger Maven- und Containerlauf extern offen
12. Juli 2026
- sechs REST-Endpunkte gegen OpenAPI-Pfade geprüft
- DTO-, Mapper- und Fehlerkatalogstruktur geprüft
- Outbox-Leasing und dritte Flyway-Migration geprüft
- Kafka-/Redpanda-Transport und Topic-Namensstrategie geprüft
- Bootstrap, Konfiguration und lokaler Stack geprüft
- Java-21-Kompilierung der frameworkfreien Kerne erfolgreich
- vollständiger Maven-/Containerlauf extern offen
12. Juli 2026
- acht JPA-Modelle einschließlich Outbox
- sieben Repository-Adapter
- zwei Flyway-Migrationen
- zwei PostgreSQL-Integrationstests und ein Mapper-Test
- Hauptquellen mit Java 21 syntaktisch kompiliert
- keine JPA-/Spring-Data-Imports in Domain oder Application
12. Juli 2026
- Domain- und Application-Hauptquellen mit
javac --release 21kompiliert. - UC-01 bis UC-06 in einem ausführbaren Smoke-Test durchlaufen.
- technischer Payment-Fehler mit derselben Payment-Identität erfolgreich wiederholt.
- Order- und Invoice-Snapshot geprüft.
- 21 Geschäftsregeln auf Implementierungs- und Teststellen abgebildet.
- Pattern-Kommentare und zentrale Pattern-Dokumentation geprüft.
12. Juli 2026
- sieben Module vorhanden
- alle POM-Dateien XML-syntaktisch gültig
- Java-21-Release zentral festgelegt
- produktive Hauptquellen mit
javac 21kompilierbar - Architekturtestquellen statisch geprüft
- lokale HTML-Verweise geprüft
- keine externen HTML-Abhängigkeiten
- vollständiger Maven-Download-Build in der Erzeugungsumgebung nicht ausführbar, da Maven dort nicht vorinstalliert und kein Netzdownload verfügbar war