Choreographie in Event-Driven Architecture beschreibt ein Koordinationsmuster, bei dem komplexe Geschäftsprozesse durch die autonome Reaktion verteilter Services auf Events entstehen. Anstatt einer zentralen Steuerungsinstanz folgt jeder Service nur seinen eigenen Geschäftsregeln und reagiert auf relevante Events im System.
Es ist wie ein Tanzabend: Anstatt eines Choreografen, der jeden Schritt dirigiert, kennt jeder Tänzer seine Rolle und reagiert auf die Bewegungen der anderen. Das Gesamtbild entsteht durch das Zusammenspiel autonomer Akteure.
In unserem E-Commerce-Szenario entsteht der Bestellprozess durch das Zusammenspiel mehrerer Services, ohne dass ein zentraler Koordinator existiert:
// OrderService - Startet den Workflow
@Service
public class OrderService {
@EventListener
public void handleOrderPlacement(PlaceOrderCommand command) {
Order order = new Order(command);
orderRepository.save(order);
// Event publizieren - andere Services reagieren autonom
eventPublisher.publish(new OrderPlacedEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotalAmount()
));
}
}
// PaymentService - Reagiert autonom auf OrderPlaced
@Service
public class PaymentService {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
PaymentRequest payment = createPaymentRequest(event);
PaymentResult result = processPayment(payment);
if (result.isSuccessful()) {
eventPublisher.publish(new PaymentProcessedEvent(
event.getOrderId(),
result.getTransactionId(),
event.getTotalAmount()
));
} else {
eventPublisher.publish(new PaymentFailedEvent(
event.getOrderId(),
result.getErrorCode()
));
}
}
}
// InventoryService - Reagiert parallel auf OrderPlaced
@Service
public class InventoryService {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
boolean available = checkAvailability(event.getItems());
if (available) {
reserveItems(event.getItems());
eventPublisher.publish(new InventoryReservedEvent(
event.getOrderId(),
event.getItems()
));
} else {
eventPublisher.publish(new InventoryInsufficientEvent(
event.getOrderId(),
findUnavailableItems(event.getItems())
));
}
}
}# order_service.py
class OrderService:
def __init__(self, event_publisher, order_repository):
self.event_publisher = event_publisher
self.order_repository = order_repository
async def handle_place_order(self, command):
order = Order(command)
await self.order_repository.save(order)
# Workflow startet durch Event-Publikation
await self.event_publisher.publish(OrderPlacedEvent(
order_id=order.id,
customer_id=order.customer_id,
items=order.items,
total_amount=order.total_amount
))
# payment_service.py
class PaymentService:
def __init__(self, event_publisher, payment_processor):
self.event_publisher = event_publisher
self.payment_processor = payment_processor
async def handle_order_placed(self, event: OrderPlacedEvent):
result = await self.payment_processor.process(
customer_id=event.customer_id,
amount=event.total_amount
)
if result.successful:
await self.event_publisher.publish(PaymentProcessedEvent(
order_id=event.order_id,
transaction_id=result.transaction_id
))
else:
await self.event_publisher.publish(PaymentFailedEvent(
order_id=event.order_id,
error_code=result.error_code
))Der komplexe Bestellprozess entsteht durch das emergente Verhalten dieser autonomen Services. Keiner “weiß” von dem Gesamtworkflow - jeder folgt nur seinen eigenen Geschäftsregeln.
Die Services kommunizieren ausschließlich über Events, wodurch eine lose Kopplung entsteht. Ein Service kennt nicht die Implementierung anderer Services, sondern nur die Events, auf die er reagieren muss.
| Kollaborationsmuster | Beschreibung | Beispiel |
|---|---|---|
| Parallel Processing | Mehrere Services reagieren gleichzeitig auf dasselbe Event | PaymentService und InventoryService verarbeiten OrderPlaced parallel |
| Sequential Chains | Ein Event löst weitere Events aus, die eine Kette bilden | OrderPlaced → PaymentProcessed → ShippingInitiated |
| Conditional Flows | Events werden nur unter bestimmten Bedingungen ausgelöst | PaymentFailed → OrderCancelled (nur bei Zahlungsfehlern) |
| Fan-out/Fan-in | Ein Event löst mehrere aus, deren Ergebnisse später zusammenfließen | OrderPlaced → mehrere Validierungen → OrderConfirmed |
// ShippingService - Wartet auf multiple Events
@Service
public class ShippingService {
private final Map<String, OrderShippingState> pendingOrders = new ConcurrentHashMap<>();
@EventListener
public void handlePaymentProcessed(PaymentProcessedEvent event) {
OrderShippingState state = getOrCreateState(event.getOrderId());
state.setPaymentConfirmed(true);
checkReadyForShipping(state);
}
@EventListener
public void handleInventoryReserved(InventoryReservedEvent event) {
OrderShippingState state = getOrCreateState(event.getOrderId());
state.setInventoryConfirmed(true);
checkReadyForShipping(state);
}
private void checkReadyForShipping(OrderShippingState state) {
if (state.isPaymentConfirmed() && state.isInventoryConfirmed()) {
eventPublisher.publish(new ShippingInitiatedEvent(
state.getOrderId(),
state.getShippingAddress()
));
}
}
}Da bei Choreographie kein zentraler Koordinator existiert, ist die Nachverfolgung von Workflows komplexer. Der Zustand eines Geschäftsprozesses ist über mehrere Services verteilt.
// Event Correlation für Workflow-Monitoring
@Component
public class WorkflowTracker {
@EventListener
public void trackEvent(DomainEvent event) {
WorkflowInstance workflow = findOrCreateWorkflow(event.getCorrelationId());
workflow.addEvent(event);
// Workflow-Status bestimmen
WorkflowStatus status = determineWorkflowStatus(workflow);
// Monitoring-Event senden
monitoringPublisher.publish(new WorkflowStatusChangedEvent(
workflow.getId(),
status,
event.getTimestamp()
));
}
private WorkflowStatus determineWorkflowStatus(WorkflowInstance workflow) {
boolean hasOrderPlaced = workflow.hasEvent(OrderPlacedEvent.class);
boolean hasPaymentProcessed = workflow.hasEvent(PaymentProcessedEvent.class);
boolean hasInventoryReserved = workflow.hasEvent(InventoryReservedEvent.class);
boolean hasShippingInitiated = workflow.hasEvent(ShippingInitiatedEvent.class);
if (hasShippingInitiated) return WorkflowStatus.COMPLETED;
if (hasPaymentProcessed && hasInventoryReserved) return WorkflowStatus.READY_FOR_SHIPPING;
if (hasOrderPlaced) return WorkflowStatus.PROCESSING;
return WorkflowStatus.UNKNOWN;
}
}# workflow_tracker.py
class WorkflowTracker:
def __init__(self):
self.workflows = {}
async def track_event(self, event):
correlation_id = event.correlation_id
if correlation_id not in self.workflows:
self.workflows[correlation_id] = WorkflowInstance(correlation_id)
workflow = self.workflows[correlation_id]
workflow.add_event(event)
# Status-Berechnung
status = self._determine_status(workflow)
await self._publish_status_update(workflow.id, status)
def _determine_status(self, workflow):
events = workflow.events
event_types = [type(e).__name__ for e in events]
if 'ShippingInitiatedEvent' in event_types:
return 'COMPLETED'
elif 'PaymentProcessedEvent' in event_types and 'InventoryReservedEvent' in event_types:
return 'READY_FOR_SHIPPING'
elif 'OrderPlacedEvent' in event_types:
return 'PROCESSING'
return 'UNKNOWN'Autonomie und Skalierbarkeit Services können unabhängig entwickelt, deployed und skaliert werden. Jeder Service ist für seine eigene Geschäftslogik verantwortlich und kann diese ohne Rücksicht auf andere Services ändern.
Resilience durch Entkopplung Der Ausfall eines Service beeinträchtigt nicht die Funktion der anderen. Workflows können auch bei partiellen Systemausfällen weiterlaufen, sofern die kritischen Pfade verfügbar bleiben.
Evolutionsfähigkeit Neue Services können einfach in bestehende Workflows integriert werden, indem sie auf relevante Events reagieren. Bestehende Services müssen nicht modifiziert werden.
| Challenge | Beschreibung | Mitigation Strategy |
|---|---|---|
| Workflow Visibility | Schwierige Nachverfolgung des Gesamtzustands | Correlation IDs, Event Tracking, Workflow Dashboards |
| Error Handling | Komplexe Fehlerbehandlung ohne zentralen Koordinator | Saga Pattern, Compensation Events, Circuit Breaker |
| Testing Complexity | Schwierige End-to-End Tests | Contract Testing, Event Sourcing für Replay |
| Eventual Consistency | Workflows sind nicht sofort konsistent | Kompensation, Monitoring, User Communication |
Choreographie eignet sich nicht für alle Szenarien:
// Anti-Pattern: Zu komplexe Choreographie
@EventListener
public void handleComplexBusinessRule(OrderPlacedEvent event) {
// Vermeiden: Komplexe Bedingungslogik in Events
if (event.getCustomer().isPremium() &&
event.getTotalAmount() > 1000 &&
isWeekend() &&
hasSpecialPromotion() &&
inventoryService.hasStock(event.getItems()) &&
paymentService.hasValidPaymentMethod(event.getCustomerId())) {
// Diese Logik gehört in einen Orchestrator
applySpecialProcessing(event);
}
}Die Choreographie glänzt bei klar abgegrenzten Geschäftsprozessen mit autonomen Services, stößt aber bei komplexen Abhängigkeiten und strikten Konsistenzanforderungen an ihre Grenzen. Das Verständnis dieser Grenzen ist entscheidend für die richtige Architekturentscheidung zwischen Choreographie und Orchestrierung, die wir im nächsten Abschnitt behandeln werden.