Systemgrenzen
Layered, Hexagonal und Clean Architecture danach vergleichen, wie Abhängigkeiten kontrolliert werden.
Alle zugehörigen Inhalte befinden sich auf dieser einen großen Seite. Kapitel und Beispiele sind standardmäßig geschlossen und lassen sich gezielt öffnen.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Die vorhandenen Kapitel bleiben vollständig erhalten. Diese Orientierung gruppiert sie nach den Entscheidungen, die ein Enterprise-Team tatsächlich treffen muss.
Layered, Hexagonal und Clean Architecture danach vergleichen, wie Abhängigkeiten kontrolliert werden.
Modularen Monolithen als prüfbaren Zwischenschritt vor verteilten Systemen verstehen.
Für jede Zielarchitektur einen inkrementellen Migrationspfad statt eines Big Bang ableiten.
Transaktionen, Beobachtbarkeit, Datenbesitz und Ausfallgrenzen als Architekturkräfte behandeln.
Sechs Architekturmodule zeigen nicht nur Diagramme, sondern Kräfte, Grenzen, Java-Zuordnung, Migrationspfad und Alternativen.
Sechs Architekturmodule zeigen nicht nur Diagramme, sondern Kräfte, Grenzen, Java-Zuordnung, Migrationspfad und Alternativen.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Kleine bis mittlere Systeme mit stabilen Schichtgrenzen
Domänenlogik darf nicht in Controller oder Persistenzschicht wandern
Hexagonal Architecture
Wenn Adapter austauschbar sein müssen oder Tests durch Infrastrukturkopplung leiden
Controller -> Application -> Domain -> Infrastructure
Grenze zuerst sichtbar machen, Vertrag stabilisieren, Abhängigkeiten umdrehen und erst danach Infrastruktur verschieben.
LayeredArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Layered Architecture
// Zweck: Verantwortlichkeiten nach technischen und fachlichen Schichten ordnen.
public final class LayeredArchitectureModule {
public record PlaceOrderCommand(String customerId,long totalMinor) {}
public interface OrderRepository { void save(Order order); }
public record Order(String customerId,long totalMinor) {}
public static final class PlaceOrderUseCase {
private final OrderRepository repository;
public PlaceOrderUseCase(OrderRepository repository){ this.repository=repository; }
public Order execute(PlaceOrderCommand command){
if(command.totalMinor()<=0) throw new IllegalArgumentException("totalMinor");
var order=new Order(command.customerId(),command.totalMinor());
repository.save(order); return order;
}
}
}
Systeme mit mehreren Ein- und Ausgängen und hohem Testbedarf
Ports fachlich benennen; keine Framework-Typen im Kern
Clean Architecture
Wenn ein einfaches Schichtenmodell ausreicht
Adapter -> Port -> Use Case -> Port -> Adapter
Grenze zuerst sichtbar machen, Vertrag stabilisieren, Abhängigkeiten umdrehen und erst danach Infrastruktur verschieben.
HexagonalArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Hexagonal Architecture (Ports and Adapters)
// Zweck: Fachlogik unabhängig von technischen Ein- und Ausgängen halten.
public final class HexagonalArchitectureModule {
public record Money(long minor,String currency) {}
public interface LoadCustomerPort { Customer load(String id); }
public interface PublishDecisionPort { void publish(Decision decision); }
public record Customer(String id,boolean active) {}
public record Decision(String customerId,boolean approved) {}
public static final class ApproveCustomerUseCase {
private final LoadCustomerPort customers; private final PublishDecisionPort events;
public ApproveCustomerUseCase(LoadCustomerPort c,PublishDecisionPort e){customers=c;events=e;}
public Decision execute(String id){ var c=customers.load(id); var d=new Decision(id,c.active()); events.publish(d); return d; }
}
}
Langfristige Produkte mit mehreren Delivery-Mechanismen
Ringgrenzen nicht mit unnötigen DTO-Kopien überfrachten
Hexagonal Architecture
Wenn zusätzliche Ringe keinen echten Änderungsdruck adressieren
Frameworks -> Interface Adapters -> Use Cases -> Entities
Grenze zuerst sichtbar machen, Vertrag stabilisieren, Abhängigkeiten umdrehen und erst danach Infrastruktur verschieben.
CleanArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Clean Architecture
// Zweck: Use Cases und Enterprise-Regeln vor Framework- und UI-Details schützen.
public final class CleanArchitectureModule {
public record RegisterRequest(String email) {}
public record RegisterResponse(String customerId) {}
public interface CustomerGateway { String insert(String normalizedEmail); }
public interface RegisterInputBoundary { RegisterResponse register(RegisterRequest request); }
public static final class RegisterInteractor implements RegisterInputBoundary {
private final CustomerGateway gateway;
public RegisterInteractor(CustomerGateway gateway){this.gateway=gateway;}
public RegisterResponse register(RegisterRequest r){
var email=r.email().trim().toLowerCase();
if(!email.contains("@")) throw new IllegalArgumentException("email");
return new RegisterResponse(gateway.insert(email));
}
}
}
Wachsende Domänen, die unabhängig strukturiert, aber gemeinsam betrieben werden
Keine direkten Tabellen- oder Package-Zugriffe zwischen Modulen
Microservices
Wenn unabhängige Skalierung und Deployment zwingend erforderlich sind
Module API -> Module intern; gemeinsame Laufzeit
Grenze zuerst sichtbar machen, Vertrag stabilisieren, Abhängigkeiten umdrehen und erst danach Infrastruktur verschieben.
ModularMonolithModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Modular Monolith
// Zweck: Fachmodule stark kapseln, ohne verteilte Betriebs- und Konsistenzkosten.
public final class ModularMonolithModule {
public interface BillingApi { Invoice issueFor(String orderId,long totalMinor); }
public record Invoice(String id,String orderId,long totalMinor) {}
public static final class OrderModule {
private final BillingApi billing;
public OrderModule(BillingApi billing){this.billing=billing;}
public Invoice complete(String orderId,long totalMinor){ return billing.issueFor(orderId,totalMinor); }
}
private ModularMonolithModule() {}
}
Unabhängige Teams, unterschiedliche Skalierung und klare Bounded Contexts
Verteilte Transaktionen, Observability und Vertragsversionierung bewusst tragen
Modular Monolith
Wenn Team- und Deployment-Autonomie den Betriebsaufwand nicht rechtfertigen
Service Contract -> Network -> Autonomous Service
Grenze zuerst sichtbar machen, Vertrag stabilisieren, Abhängigkeiten umdrehen und erst danach Infrastruktur verschieben.
MicroservicesArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.UUID;
// Architecture Pattern: Microservices
// Zweck: Fachliche Fähigkeiten unabhängig deployen und skalieren.
public final class MicroservicesArchitectureModule {
public record ReserveInventoryRequest(String correlationId,String sku,int quantity) {
public static ReserveInventoryRequest of(String sku,int qty){return new ReserveInventoryRequest(UUID.randomUUID().toString(),sku,qty);}
}
public record ReservationReply(String correlationId,boolean reserved) {}
public interface InventoryClient { ReservationReply reserve(ReserveInventoryRequest request); }
public static final class CheckoutService {
private final InventoryClient inventory;
public CheckoutService(InventoryClient inventory){this.inventory=inventory;}
public ReservationReply checkout(String sku,int quantity){return inventory.reserve(ReserveInventoryRequest.of(sku,quantity));}
}
}
Mehrere unabhängige Reaktionen, Integrationsentkopplung und hohe Änderungsrate
Idempotenz, Reihenfolge, Schemaevolution und Fehlerkanäle explizit lösen
Request-Response
Wenn eine sofortige konsistente Antwort benötigt wird
Producer -> Event Bus -> Independent Consumers
Grenze zuerst sichtbar machen, Vertrag stabilisieren, Abhängigkeiten umdrehen und erst danach Infrastruktur verschieben.
EventDrivenArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.time.Instant;
// Architecture Pattern: Event-Driven Architecture
// Zweck: Fachliche Reaktionen zeitlich und organisatorisch entkoppeln.
public final class EventDrivenArchitectureModule {
public record OrderCompleted(String eventId,String orderId,long totalMinor,Instant occurredAt) {}
public interface EventPublisher { void publish(OrderCompleted event); }
public static final class CompleteOrderUseCase {
private final EventPublisher publisher;
public CompleteOrderUseCase(EventPublisher publisher){this.publisher=publisher;}
public void execute(String eventId,String orderId,long totalMinor){
if(totalMinor<=0) throw new IllegalArgumentException("totalMinor");
publisher.publish(new OrderCompleted(eventId,orderId,totalMinor,Instant.now()));
}
}
}
Sechs weitere Architekturentscheidungen für verteilte Daten, Prozesse, Plattformgrenzen und Fehlerisolation.
Sechs weitere Architekturentscheidungen für verteilte Daten, Prozesse, Plattformgrenzen und Fehlerisolation.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Hohe Lese-/Schreibasymmetrie und unterschiedliche Modellanforderungen
Konsistenz, Projektionen und Betriebsaufwand explizit behandeln
CRUD oder modulare Schichten
Command -> Write Model -> Event -> Projection -> Query Model
Grenze und Messgrößen zuerst definieren, eine vertikale Scheibe migrieren, Verhalten absichern und erst danach skalieren.
Verträge, Fehlerpfade, Idempotenz und Architekturgrenzen automatisiert prüfen.
CqrsArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
// Architecture Pattern: CQRS
// Zweck: Schreibentscheidungen und optimierte Leseprojektionen bewusst trennen.
public final class CqrsArchitectureModule {
public record ChangePrice(String sku,long minor) {} public record ProductView(String sku,long minor) {}
public interface CommandHandler { void handle(ChangePrice command); }
public interface ProductQuery { ProductView bySku(String sku); }
public static final class ProductProjection implements ProductQuery {
private final Map<String,ProductView> views=new ConcurrentHashMap<>();
public void project(ChangePrice c){views.put(c.sku(),new ProductView(c.sku(),c.minor()));}
public ProductView bySku(String sku){return views.get(sku);}
}
}Auditierbare Domänen mit zeitlicher Nachvollziehbarkeit und seltenen Modellwechseln
Ereignisschema, Snapshots und Wiederaufbau dauerhaft beherrschen
State-based Persistence
Command -> Aggregate -> Events -> Event Store -> Replay
Grenze und Messgrößen zuerst definieren, eine vertikale Scheibe migrieren, Verhalten absichern und erst danach skalieren.
Verträge, Fehlerpfade, Idempotenz und Architekturgrenzen automatisiert prüfen.
EventSourcingArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.List;
// Architecture Pattern: Event Sourcing
// Zweck: Zustand als Ergebnis einer unveränderlichen Ereignisfolge rekonstruieren.
public final class EventSourcingArchitectureModule {
public sealed interface AccountEvent permits Opened,Deposited {}
public record Opened(String accountId) implements AccountEvent {} public record Deposited(long minor) implements AccountEvent {}
public record AccountState(String id,long balance) {
public static AccountState replay(List<AccountEvent> events){ AccountState s=new AccountState("",0); for(var e:events){ if(e instanceof Opened o)s=new AccountState(o.accountId(),0); else if(e instanceof Deposited d)s=new AccountState(s.id(),s.balance()+d.minor()); } return s;}
}
}Lange Geschäftsprozesse über autonome Services ohne globale Transaktion
Kompensation ist Fachlogik; Wiederholung und Beobachtbarkeit sind Pflicht
Process Manager oder atomare lokale Transaktion
Start -> Reserve -> Charge -> Ship / Compensate
Grenze und Messgrößen zuerst definieren, eine vertikale Scheibe migrieren, Verhalten absichern und erst danach skalieren.
Verträge, Fehlerpfade, Idempotenz und Architekturgrenzen automatisiert prüfen.
SagaArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Saga
// Zweck: Verteilte Geschäftsprozesse über lokale Transaktionen und Kompensationen koordinieren.
public final class SagaArchitectureModule {
public interface Inventory { void reserve(String orderId); void release(String orderId); }
public interface Payment { void charge(String orderId); }
public static final class CheckoutSaga {
private final Inventory inventory; private final Payment payment;
public CheckoutSaga(Inventory i,Payment p){inventory=i;payment=p;}
public void execute(String orderId){ inventory.reserve(orderId); try{payment.charge(orderId);}catch(RuntimeException ex){inventory.release(orderId);throw ex;} }
}
}Viele externe Clients und mehrere interne Services mit einheitlichen Edge-Regeln
Keine Fachlogik oder verteilte Monolith-Kopplung im Gateway
Backend for Frontend oder direkte Service-APIs
Client -> Gateway -> Service Routes
Grenze und Messgrößen zuerst definieren, eine vertikale Scheibe migrieren, Verhalten absichern und erst danach skalieren.
Verträge, Fehlerpfade, Idempotenz und Architekturgrenzen automatisiert prüfen.
ApiGatewayArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.Map;
// Architecture Pattern: API Gateway
// Zweck: Externe Zugriffe über einen stabilen Edge-Vertrag zu internen Services routen.
public final class ApiGatewayArchitectureModule {
public interface Route { String invoke(String payload); }
public static final class Gateway {
private final Map<String,Route> routes; public Gateway(Map<String,Route> routes){this.routes=Map.copyOf(routes);}
public String dispatch(String path,String payload){var route=routes.get(path); if(route==null)throw new IllegalArgumentException("route"); return route.invoke(payload);}
}
}Viele Services mit einheitlichen Transportregeln und Plattformbetrieb
Fachliche Wiederholungslogik und Domänenentscheidungen bleiben im Service
Library-based Service Communication
Service -> Sidecar/Data Plane -> Service; Control Plane steuert
Grenze und Messgrößen zuerst definieren, eine vertikale Scheibe migrieren, Verhalten absichern und erst danach skalieren.
Verträge, Fehlerpfade, Idempotenz und Architekturgrenzen automatisiert prüfen.
ServiceMeshArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Service Mesh
// Zweck: Einheitliche Transport- und Observability-Regeln außerhalb der Fachlogik betreiben.
public final class ServiceMeshArchitectureModule {
public record Request(String service,String body) {} public record Response(int status,String body) {}
public interface DataPlane { Response forward(Request request); }
public static final class OrderClient {
private final DataPlane mesh; public OrderClient(DataPlane mesh){this.mesh=mesh;}
public Response load(String id){return mesh.forward(new Request("order-service","/orders/"+id));}
}
}Große Multi-Tenant-Plattformen mit klarer Partitionierung und hoher Verfügbarkeit
Zellrouting, Datenplatzierung und Cross-Cell-Prozesse explizit entwerfen
Shared Cluster oder klassische Microservices
Router -> Cell A/B/C -> isolated data and services
Grenze und Messgrößen zuerst definieren, eine vertikale Scheibe migrieren, Verhalten absichern und erst danach skalieren.
Verträge, Fehlerpfade, Idempotenz und Architekturgrenzen automatisiert prüfen.
CellBasedArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.List;
// Architecture Pattern: Cell-Based Architecture
// Zweck: Mandanten und Last auf isolierte, wiederholbare Zellen verteilen.
public final class CellBasedArchitectureModule {
public record Cell(String id,String endpoint) {}
public static final class CellRouter {
private final List<Cell> cells; public CellRouter(List<Cell> cells){if(cells.isEmpty())throw new IllegalArgumentException("cells");this.cells=List.copyOf(cells);}
public Cell route(String tenantId){return cells.get(Math.floorMod(tenantId.hashCode(),cells.size()));}
}
}Sechs Architekturentscheidungen für Mandantenfähigkeit, elastische Ausführung, hochskalierende Datenräume und dezentrale Verarbeitung.
Sechs Architekturentscheidungen für Mandantenfähigkeit, elastische Ausführung, hochskalierende Datenräume und dezentrale Verarbeitung.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
SaaS-Plattformen mit gemeinsamer Produktlogik und klarer Tenant-Grenze
Tenant-Kontext muss durch jede Schicht propagiert und serverseitig geprüft werden
Separate Deployments pro Kunde
Request -> Tenant Context -> Use Case -> Tenant-aware Repository
Zuerst Grenze und Messgrößen definieren, eine vertikale Scheibe absichern und erst danach skalieren.
Verträge, Fehlerpfade, Isolation, Konsistenz und Wiederanlauf automatisiert prüfen.
MultiTenantArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Multi-Tenant Architecture
// Zweck: Mandantenkontext explizit durch Use Case und Persistenzgrenze führen.
public final class MultiTenantArchitectureModule {
public record TenantId(String value){ public TenantId{ if(value==null||value.isBlank()) throw new IllegalArgumentException("tenant"); }}
public record Order(TenantId tenantId,String orderId){}
public interface OrderRepository { Order find(TenantId tenantId,String orderId); }
public static final class LoadOrder {
private final OrderRepository repository; public LoadOrder(OrderRepository r){this.repository=r;}
public Order execute(TenantId tenantId,String orderId){return repository.find(tenantId,orderId);}
}
}Ereignisgetriebene, unregelmäßige Last mit klar begrenzten Funktionen
Cold Starts, Laufzeitlimits, Zustandslosigkeit und Provider-Kopplung bewusst behandeln
Container-basierte Services
Event -> Function -> Port -> External Service
Zuerst Grenze und Messgrößen definieren, eine vertikale Scheibe absichern und erst danach skalieren.
Verträge, Fehlerpfade, Isolation, Konsistenz und Wiederanlauf automatisiert prüfen.
ServerlessArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Serverless Architecture
// Zweck: Eine kleine zustandslose Funktion über Ports an Infrastruktur anbinden.
public final class ServerlessArchitectureModule {
public record InvoiceRequested(String invoiceId){}
public interface InvoicePort { void generate(String invoiceId); }
public static final class InvoiceFunction {
private final InvoicePort port; public InvoiceFunction(InvoicePort p){this.port=p;}
public void handle(InvoiceRequested event){port.generate(event.invoiceId());}
}
}Extrem hohe Last, temporäre Zustände und horizontale Partitionierung
Konsistenzmodell, Rebalancing und Persistenzrückfluss explizit entwerfen
Event-Driven Microservices mit externer Datenbank
Router -> Partitioned Space -> Processing Unit -> Durable Store
Zuerst Grenze und Messgrößen definieren, eine vertikale Scheibe absichern und erst danach skalieren.
Verträge, Fehlerpfade, Isolation, Konsistenz und Wiederanlauf automatisiert prüfen.
SpaceBasedArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.concurrent.ConcurrentHashMap;
// Architecture Pattern: Space-Based Architecture
// Zweck: Arbeitszustand partitioniert im Speicher halten und lokal verarbeiten.
public final class SpaceBasedArchitectureModule {
public record Cart(String id,long totalMinor){}
public static final class CartSpace {
private final ConcurrentHashMap<String,Cart> carts=new ConcurrentHashMap<>();
public Cart update(String id,long delta){return carts.compute(id,(k,v)->new Cart(id,(v==null?0:v.totalMinor())+delta));}
}
}Import, Transformation, Validierung und Datenaufbereitung
Filter müssen klein, beobachtbar und möglichst seiteneffektfrei bleiben
Template Method oder monolithischer Batch
Input -> Parse -> Validate -> Enrich -> Persist
Zuerst Grenze und Messgrößen definieren, eine vertikale Scheibe absichern und erst danach skalieren.
Verträge, Fehlerpfade, Isolation, Konsistenz und Wiederanlauf automatisiert prüfen.
PipeAndFilterArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.List;
// Architecture Pattern: Pipe and Filter
// Zweck: Verarbeitungsschritte als unabhängige Filter komponieren.
public final class PipeAndFilterArchitectureModule {
public interface Filter { String apply(String input); }
public static final class Pipeline {
private final List<Filter> filters; public Pipeline(List<Filter> f){filters=List.copyOf(f);}
public String run(String input){String value=input; for(Filter f:filters)value=f.apply(value); return value;}
}
}Komplexe Analyseprobleme mit mehreren unabhängigen Heuristiken
Gemeinsamer Zustand braucht Versionierung, Priorisierung und klare Abbruchkriterien
Pipeline oder Orchestrator
Knowledge Sources <-> Blackboard -> Controller
Zuerst Grenze und Messgrößen definieren, eine vertikale Scheibe absichern und erst danach skalieren.
Verträge, Fehlerpfade, Isolation, Konsistenz und Wiederanlauf automatisiert prüfen.
BlackboardArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.*;
// Architecture Pattern: Blackboard
// Zweck: Mehrere Analysekomponenten über einen gemeinsamen Wissensspeicher koordinieren.
public final class BlackboardArchitectureModule {
public static final class Blackboard { private final Map<String,Object> facts=new HashMap<>(); public void put(String k,Object v){facts.put(k,v);} public Object get(String k){return facts.get(k);} }
public interface KnowledgeSource { boolean supports(Blackboard board); void contribute(Blackboard board); }
public static void solve(Blackboard board,List<KnowledgeSource> sources){for(var s:sources)if(s.supports(board))s.contribute(board);}
}Dezentrale Replikation, lokale Autonomie und robuste Verteilung
Konfliktauflösung, Discovery, Authentisierung und Konsens sind Kernprobleme
Leader-basierte Clusterarchitektur
Peer A <-> Peer B <-> Peer C
Zuerst Grenze und Messgrößen definieren, eine vertikale Scheibe absichern und erst danach skalieren.
Verträge, Fehlerpfade, Isolation, Konsistenz und Wiederanlauf automatisiert prüfen.
PeerToPeerArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.Set;
// Architecture Pattern: Peer-to-Peer
// Zweck: Gleichberechtigte Knoten über ein kleines Replikationsprotokoll verbinden.
public final class PeerToPeerArchitectureModule {
public record Change(String id,long version,String value){}
public interface Peer { void receive(Change change); }
public static final class Replicator {
private final Set<Peer> peers; public Replicator(Set<Peer> peers){this.peers=Set.copyOf(peers);}
public void publish(Change change){peers.forEach(p->p.receive(change));}
}
}Sechs Architekturentscheidungen für Domänenzentrierung, erweiterbare Plattformen und moderne Datenverarbeitung.
Sechs Architekturentscheidungen für Domänenzentrierung, erweiterbare Plattformen und moderne Datenverarbeitung.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Komplexe Fachdomänen mit langlebigen Geschäftsregeln
Abhängigkeitsregel und Modellreinheit müssen in Build und Reviews geschützt werden
Hexagonal Architecture
UI/Adapter -> Application -> Domain
Fachlogik aus Services ins Domänenmodell ziehen, Ports definieren, Infrastruktur nach außen verschieben
Domainregeln ohne Framework testen; Adapter separat per Vertrag prüfen
OnionArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Onion Architecture
// Zweck: Fachlogik im inneren Ring halten und Infrastruktur nur über Ports anbinden.
public final class OnionArchitectureModule {
public record Money(long minor) { public Money add(Money other){ return new Money(minor + other.minor); } }
public interface OrderPort { void save(Order order); }
public static final class Order {
private Money total = new Money(0);
public void add(Money price){ if(price.minor() < 0) throw new IllegalArgumentException("price"); total = total.add(price); }
public Money total(){ return total; }
}
public static final class PlaceOrder {
private final OrderPort port; public PlaceOrder(OrderPort port){ this.port=port; }
public void execute(Order order){ port.save(order); }
}
}Produkte mit kundenspezifischen Erweiterungen und austauschbaren Modulen
Plugin-Verträge, Versionierung und Isolation müssen explizit sein
Modular Monolith
Host -> Kernel API -> Plugin
Erweiterungspunkte identifizieren, Vertrag extrahieren, vorhandene Varianten als Plugins migrieren
Kompatibilitätstests pro Plugin und Kernvertrag
MicrokernelArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.*;
// Architecture Pattern: Microkernel
// Zweck: Stabilen Kern durch versionierte Plugins erweitern.
public final class MicrokernelArchitectureModule {
public interface PricingPlugin { String id(); long price(long baseMinor); }
public static final class Kernel {
private final Map<String,PricingPlugin> plugins;
public Kernel(Collection<PricingPlugin> plugins){ this.plugins=new HashMap<>(); plugins.forEach(p->this.plugins.put(p.id(),p)); }
public long price(String pluginId,long baseMinor){ var p=plugins.get(pluginId); if(p==null) throw new IllegalArgumentException("plugin"); return p.price(baseMinor); }
}
}Viele Fachdomänen mit zentralem Datenengpass
Ownership ohne interoperable Verträge erzeugt neue Silos
Zentrales Data Warehouse
Domain Source -> Data Product -> Federated Catalog
Ein priorisiertes Datenprodukt mit Owner, Schema, SLO und Zugriffspolitik etablieren
Schema-, Qualitäts-, Zugriffs- und Freshness-Verträge automatisieren
DataMeshArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.time.*;
// Architecture Pattern: Data Mesh
// Zweck: Ein fachlich verantwortetes Datenprodukt mit explizitem Vertrag modellieren.
public final class DataMeshArchitectureModule {
public record OrderFact(String orderId,long totalMinor,Instant occurredAt){}
public interface OrderDataProduct { OrderFact byId(String orderId); String schemaVersion(); }
public static final class OrderAnalytics {
private final OrderDataProduct product; public OrderAnalytics(OrderDataProduct p){this.product=p;}
public long total(String orderId){ return product.byId(orderId).totalMinor(); }
}
}Analytik über große strukturierte und semistrukturierte Datenmengen
Dateiformat allein ersetzt keine Governance, Katalogisierung oder Datenqualität
Warehouse plus separater Data Lake
Ingest -> Bronze -> Silver -> Gold Table
Einen klaren Datensatz mit Qualitätsstufen, Katalog und reproduzierbarer Transformation pilotieren
Schemaevolution, Deduplizierung, Reproduzierbarkeit und Zugriff prüfen
DataLakehouseArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.*;
// Architecture Pattern: Data Lakehouse
// Zweck: Rohdaten über explizite Qualitätsstufen in ein kuratiertes Tabellenmodell überführen.
public final class DataLakehouseArchitectureModule {
public record RawOrder(String id,String total){}
public record CuratedOrder(String id,long totalMinor){}
public static final class SilverTransformer {
public CuratedOrder transform(RawOrder raw){ return new CuratedOrder(raw.id(), Long.parseLong(raw.total())); }
public List<CuratedOrder> transformAll(List<RawOrder> rows){ return rows.stream().map(this::transform).toList(); }
}
}Analytik mit historischer Korrektheit und sehr schneller Aktualisierung
Doppelte Geschäftslogik in Batch und Stream erhöht Wartungskosten
Kappa Architecture
Event Log -> Batch Layer + Speed Layer -> Serving View
Gemeinsames Ereignismodell definieren, Lesemodell messen und Doppelimplementierung bewusst begrenzen
Gleichheit der Batch- und Speed-Ergebnisse sowie Rebuild testen
LambdaArchitectureModule.javapackage com.aydinsude.workbench.architecture;
// Architecture Pattern: Lambda Architecture
// Zweck: Historische Batch-Sicht und schnelle Delta-Sicht kontrolliert kombinieren.
public final class LambdaArchitectureModule {
public record Metric(long batchValue,long speedDelta){ public long current(){ return batchValue + speedDelta; } }
public interface BatchView { long value(String key); }
public interface SpeedView { long delta(String key); }
public static final class ServingView {
private final BatchView batch; private final SpeedView speed;
public ServingView(BatchView b,SpeedView s){batch=b;speed=s;}
public Metric metric(String key){ return new Metric(batch.value(key),speed.delta(key)); }
}
}Ereigniszentrierte Systeme mit beherrschbarer Stream-Logik
Reprocessing, Schemaänderungen und Zustandsspeicher müssen betrieblich beherrscht werden
Lambda Architecture
Event Log -> Stream Processor -> Materialized View
Einen Lesefall aus dem Ereignislog reproduzierbar aufbauen und Replay operationalisieren
Live-Verarbeitung und vollständiges Replay müssen identische Sichten liefern
KappaArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.*;
// Architecture Pattern: Kappa Architecture
// Zweck: Live-Verarbeitung und Rebuild über dieselbe Ereignisfunktion ausführen.
public final class KappaArchitectureModule {
public sealed interface Event permits OrderPlaced,OrderCancelled {}
public record OrderPlaced(String id,long totalMinor) implements Event {}
public record OrderCancelled(String id) implements Event {}
public static final class Projection {
private final Map<String,Long> totals=new HashMap<>();
public void apply(Event event){ if(event instanceof OrderPlaced p) totals.put(p.id(),p.totalMinor()); else if(event instanceof OrderCancelled c) totals.remove(c.id()); }
public Map<String,Long> snapshot(){ return Map.copyOf(totals); }
}
}Sechs abschließende Architekturentscheidungen für hochparallele, reaktive, verteilte und föderierte Systeme.
Sechs abschließende Architekturentscheidungen für hochparallele, reaktive, verteilte und föderierte Systeme.
Grundlage → Entscheidungskriterien → Refactoring-Pfad → ausführbare Referenz → Einsatzgrenzen
Viele unabhängige, zustandsbehaftete Einheiten mit hoher Parallelität
Mailbox-Überlastung, Nachrichtenduplikate und Lebenszyklus müssen explizit kontrolliert werden
Thread-per-request oder klassische Executor-Services
Sender -> Mailbox -> Actor State
Einen abgegrenzten zustandsbehafteten Prozess als Actor modellieren und Nachrichtenvertrag stabilisieren
Deterministische Zustandsübergänge, Reihenfolge, Duplikate und Backpressure
ActorModelArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.concurrent.*;
// Architecture Pattern: Actor Model
// Zweck: Veränderlichen Zustand hinter einer seriell verarbeiteten Mailbox kapseln.
public final class ActorModelArchitectureModule {
public sealed interface Message permits Reserve,Release {}
public record Reserve(int quantity) implements Message {}
public record Release(int quantity) implements Message {}
public static final class InventoryActor implements AutoCloseable {
private final ExecutorService mailbox=Executors.newSingleThreadExecutor();
private int available;
public InventoryActor(int initial){ available=initial; }
public CompletableFuture<Integer> tell(Message message){
return CompletableFuture.supplyAsync(() -> {
if(message instanceof Reserve r){ if(r.quantity()>available) throw new IllegalStateException("stock"); available-=r.quantity(); }
else if(message instanceof Release r){ available+=r.quantity(); }
return available;
},mailbox);
}
public void close(){ mailbox.shutdown(); }
}
}I/O-intensive Systeme mit schwankender Last und strengen Latenzzielen
Reactive APIs ohne Ende-zu-Ende-Backpressure verschieben nur den Engpass
Klassische synchrone Architektur mit begrenzten Pools
Publisher -> Demand -> Subscriber
Einen Engpass messen, asynchronen Vertrag definieren und Kapazitätsgrenzen explizit machen
Demand-Steuerung, Timeout, Abbruch, Fehlerpfade und Lastgrenzen
ReactiveArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
// Architecture Pattern: Reactive Architecture
// Zweck: Datenfluss mit expliziter Nachfrage und Backpressure modellieren.
public final class ReactiveArchitectureModule {
public static final class OrderPublisher extends SubmissionPublisher<String> {
public void publish(String orderId){ submit(orderId); }
}
public static final class BoundedSubscriber implements Flow.Subscriber<String> {
private Flow.Subscription subscription; private int processed;
public void onSubscribe(Flow.Subscription s){ subscription=s; s.request(1); }
public void onNext(String item){ processed++; subscription.request(1); }
public void onError(Throwable error){ }
public void onComplete(){ }
public int processed(){ return processed; }
}
}Kontinuierliche Fachereignisse, Replays und mehrere unabhängige Konsumenten
Schlüsselwahl, Schemaevolution, Reihenfolge und genau-einmalige Wirkung müssen beherrscht werden
Message Queue oder klassische Event-Driven Architecture
Producer -> Partitioned Log -> Stream Processor -> View
Einen Ereignisstrom mit stabilem Schlüssel und idempotenter Projektion pilotieren
Replay, Partitionierung, Schemaänderungen, Duplikate und Lag
EventStreamingArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.*;
// Architecture Pattern: Event Streaming Architecture
// Zweck: Unveränderliche Fachereignisse in reproduzierbare Projektionen überführen.
public final class EventStreamingArchitectureModule {
public sealed interface Event permits OrderPlaced,OrderCancelled {}
public record OrderPlaced(String orderId,long totalMinor) implements Event {}
public record OrderCancelled(String orderId) implements Event {}
public static final class OrderProjection {
private final Map<String,Long> totals=new HashMap<>();
public void apply(Event event){
if(event instanceof OrderPlaced p) totals.put(p.orderId(),p.totalMinor());
else if(event instanceof OrderCancelled c) totals.remove(c.orderId());
}
public Map<String,Long> snapshot(){ return Map.copyOf(totals); }
}
}IoT, Filialen, Produktion oder globale Anwendungen mit Offline-Anforderungen
Datenkonsistenz, Geräteverwaltung, Security und Update-Strategie werden komplexer
Zentrale Cloud-Verarbeitung
Device -> Edge Node -> Cloud Control Plane
Einen latenzkritischen Entscheidungsfall lokal ausführen und Synchronisationsvertrag definieren
Offline-Betrieb, Konflikte, sichere Updates, Telemetrie und Wiederanlauf
EdgeComputingArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.time.*;
// Architecture Pattern: Edge Computing Architecture
// Zweck: Latenzkritische Entscheidung lokal treffen und später synchronisieren.
public final class EdgeComputingArchitectureModule {
public record SensorReading(String deviceId,double value,Instant measuredAt) {}
public record EdgeDecision(boolean alert,String reason) {}
public static final class EdgeRule {
private final double threshold; public EdgeRule(double threshold){ this.threshold=threshold; }
public EdgeDecision evaluate(SensorReading reading){
return reading.value()>threshold ? new EdgeDecision(true,"threshold") : new EdgeDecision(false,"ok");
}
}
public interface CloudSyncPort { void enqueue(SensorReading reading,EdgeDecision decision); }
}Große Organisationen mit eigenständigen Domänen, Regionen oder Unternehmen
Zu viel Autonomie ohne Standards erzeugt inkompatible Silos und doppelte Fähigkeiten
Zentralisierte Plattform oder Shared Database
Domain Node <-> Contract Hub <-> Domain Node
Gemeinsamen Minimalvertrag, Ownership und Konformitätstests für einen domänenübergreifenden Prozess etablieren
Vertragskompatibilität, Versionierung, Teilverfügbarkeit und Governance
FederatedArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.*;
// Architecture Pattern: Federated Architecture
// Zweck: Autonome Domänen über einen kleinen versionierten Vertrag integrieren.
public final class FederatedArchitectureModule {
public record CustomerReference(String domain,String externalId) {}
public interface CustomerDirectory { Optional<CustomerReference> resolve(String email); String contractVersion(); }
public static final class FederationGateway {
private final List<CustomerDirectory> directories;
public FederationGateway(List<CustomerDirectory> directories){ this.directories=List.copyOf(directories); }
public Optional<CustomerReference> resolve(String email){
return directories.stream().map(d->d.resolve(email)).flatMap(Optional::stream).findFirst();
}
}
}Sehr große Datenmengen oder Workloads mit natürlicher Partitionierung
Hotspots, Rebalancing und domänenübergreifende Transaktionen müssen vermieden oder explizit gelöst werden
Shared Database Cluster
Routing Key -> Independent Shard -> Local State
Einen stabilen Partitionierungsschlüssel wählen und einen isolierten Workload ohne Cross-Shard-Transaktion migrieren
Routing, Rebalancing, Hot Keys, Ausfall einzelner Shards und Datenlokalität
SharedNothingArchitectureModule.javapackage com.aydinsude.workbench.architecture;
import java.util.*;
// Architecture Pattern: Shared-Nothing Architecture
// Zweck: Anfragen deterministisch auf unabhängige Datenpartitionen verteilen.
public final class SharedNothingArchitectureModule {
public interface Shard { void store(String key,String value); Optional<String> load(String key); }
public static final class ShardRouter {
private final List<Shard> shards; public ShardRouter(List<Shard> shards){ if(shards.isEmpty()) throw new IllegalArgumentException("shards"); this.shards=List.copyOf(shards); }
private Shard shard(String key){ return shards.get(Math.floorMod(key.hashCode(),shards.size())); }
public void store(String key,String value){ shard(key).store(key,value); }
public Optional<String> load(String key){ return shard(key).load(key); }
}
}