Enterprise Order · Layered Architecture

Layered Architecture Gesamtwerk

Alle Entwicklungsphasen, Architekturgrenzen, Fachmodelle, Adapter, Tests, Qualitätsgates und bekannten Grenzen in einer einzigen offlinefähigen HTML-Datei.

Java 217 Maven-Module6 Use Cases17 Kapitel
7
Maven-Module
6
Use Cases
8
umgesetzte Fachkapitel

Architektur auf einen Blick

hochVerständlichkeit
klartechnische Grenzen
mittelfachliche Isolation
hochTestbarkeit
mittelSkalierung der Struktur
Abschlussstatus Die Layered-Variante ist als erste Vergleichsarchitektur vollständig dokumentiert. Ein externer Maven- und Containerlauf bleibt gesondert offen.

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 verify

Architekturprü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-Scope

Verbotene 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
└── event

Modellierungsprinzipien

  • 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/Redpanda

REST-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
FAILED

Competing 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-id
  • event-type
  • aggregate-type
  • correlation-id
  • occurred-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, Regres­sionsszenarien 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 Regres­sionssuite 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 21 kompiliert.
  • 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 21 kompilierbar
  • 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

Darstellung

Design
Text
Dichte
⌂ Cockpit