Die Entscheidung zwischen Choreographie und Orchestrierung ist eine der fundamentalen Architekturentscheidungen in Event-Driven Systemen. Beide Ansätze haben spezifische Stärken und Schwächen, die sich je nach Anwendungskontext unterschiedlich auswirken.
| Dimension | Choreographie | Orchestrierung |
|---|---|---|
| Kontrolle | Dezentral, emergent | Zentral, explizit |
| Wissen über Workflow | Fragmentiert über Services | Vollständig im Orchestrator |
| Kopplung | Lose gekoppelt über Events | Orchestrator kennt alle Services |
| Skalierung | Services unabhängig skalierbar | Orchestrator kann Bottleneck werden |
| Fehlerbehandlung | Komplex, verteilt | Zentral, übersichtlich |
| Änderungsaufwand | Service-lokal | Orchestrator-Modifikation |
Choreographie-Performance:
// Parallele Verarbeitung in Choreographie
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
// PaymentService und InventoryService arbeiten parallel
// Keine zentrale Koordination = weniger Latenz
processPaymentAsync(event);
}
// Gleichzeitig in anderem Service
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
// Parallel Processing ohne Wartezeiten
reserveInventoryAsync(event);
}Orchestrierung-Performance:
// Sequenzielle Kontrolle in Orchestrierung
public void processOrder(OrderPlacedEvent event) {
// Schritt 1: Payment (blockierend)
PaymentResult payment = paymentService.processPayment(event);
// Schritt 2: Inventory (wartet auf Payment)
if (payment.isSuccessful()) {
InventoryResult inventory = inventoryService.reserveItems(event);
// Schritt 3: Shipping (wartet auf beide)
if (inventory.isSuccessful()) {
shippingService.initiateShipping(event);
}
}
}Choreographie - Graceful Degradation:
# Service-isolierte Fehlerbehandlung
class PaymentService:
async def handle_order_placed(self, event: OrderPlacedEvent):
try:
await self._process_payment(event)
except PaymentGatewayException:
# Service fällt aus, aber andere Services arbeiten weiter
await self._schedule_retry(event)
# InventoryService und ShippingService sind nicht betroffen
class InventoryService:
async def handle_order_placed(self, event: OrderPlacedEvent):
# Läuft weiter, auch wenn PaymentService ausfällt
await self._reserve_inventory(event)Orchestrierung - Zentralisierte Fehlerbehandlung:
# Orchestrator verwaltet alle Fehler zentral
class OrderOrchestrator:
async def process_order(self, event: OrderPlacedEvent):
try:
# Kontrollierter Ablauf mit Rollback-Möglichkeit
payment_result = await self.payment_service.process_payment(event)
inventory_result = await self.inventory_service.reserve_inventory(event)
if not payment_result.successful:
# Zentrale Compensation Logic
await self._compensate_workflow(event, compensation_step=0)
except Exception as e:
# Einheitliche Fehlerbehandlung für gesamten Workflow
await self._handle_workflow_failure(event, e)Moderne Architekturen kombinieren oft beide Ansätze, um die Vorteile beider Welten zu nutzen. Orchestratoren können event-driven arbeiten und müssen nicht synchron blockieren.
// Hybrid: Event-driven Orchestrator
@Component
public class EventDrivenOrderOrchestrator {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
WorkflowState state = initializeWorkflow(event.getOrderId());
// Asynchrone Commands senden, nicht warten
commandPublisher.publish(new ProcessPaymentCommand(
event.getOrderId(),
event.getCustomerId(),
event.getTotalAmount()
));
}
@EventListener
public void handlePaymentProcessed(PaymentProcessedEvent event) {
WorkflowState state = getWorkflowState(event.getOrderId());
state.markPaymentComplete(event.getTransactionId());
// Nächstes Command senden
commandPublisher.publish(new ReserveInventoryCommand(
event.getOrderId(),
state.getOrderItems()
));
}
@EventListener
public void handleInventoryReserved(InventoryReservedEvent event) {
WorkflowState state = getWorkflowState(event.getOrderId());
state.markInventoryReserved(event.getReservationId());
// Abschließendes Command
commandPublisher.publish(new InitiateShippingCommand(
event.getOrderId(),
state.getShippingAddress()
));
}
}Process Manager kombinieren lose Kopplung mit zentraler Zustandsverwaltung - sie reagieren auf Events, behalten aber den Workflow-Zustand im Blick.
# Process Manager Pattern
class OrderProcessManager:
def __init__(self, state_repository, event_publisher):
self.state_repository = state_repository
self.event_publisher = event_publisher
async def handle_order_placed(self, event: OrderPlacedEvent):
# State tracking wie Orchestrator
process_state = OrderProcessState(
order_id=event.order_id,
expected_events=['PaymentProcessed', 'InventoryReserved']
)
await self.state_repository.save(process_state)
# Aber keine direkten Commands - Services reagieren eigenständig
async def handle_payment_processed(self, event: PaymentProcessedEvent):
state = await self.state_repository.get(event.order_id)
state.mark_event_received('PaymentProcessed')
if state.all_events_received():
# Trigger next phase through event
await self.event_publisher.publish(ReadyForShippingEvent(
order_id=event.order_id
))
async def handle_inventory_reserved(self, event: InventoryReservedEvent):
state = await self.state_repository.get(event.order_id)
state.mark_event_received('InventoryReserved')
if state.all_events_received():
await self.event_publisher.publish(ReadyForShippingEvent(
order_id=event.order_id
))// Unterschiedliche Patterns je Domain
@Component
public class ECommerceWorkflowCoordinator {
// Core Order Domain: Orchestrierung für Konsistenz
@EventListener
public void handleHighValueOrder(OrderPlacedEvent event) {
if (event.getTotalAmount().compareTo(BigDecimal.valueOf(1000)) > 0) {
// Für teure Bestellungen: strenge Orchestrierung
orchestrateHighValueOrder(event);
}
}
// Marketing Domain: Choreographie für Flexibilität
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
// Marketing-Events werden choreographiert
// Services reagieren autonom (Newsletter, Analytics, Recommendations)
// Kein zentraler Koordinator nötig
}
// Fulfillment Domain: Hybrid je nach Kontext
@EventListener
public void handleInventoryReserved(InventoryReservedEvent event) {
if (requiresSpecialHandling(event)) {
// Spezialbehandlung: Orchestrierung
specialHandlingOrchestrator.processSpecialOrder(event);
}
// Standard-Bestellungen: Choreographie über Events
}
}Die Wahl zwischen Choreographie und Orchestrierung sollte anhand spezifischer Kriterien erfolgen, die sowohl technische als auch organisatorische Aspekte berücksichtigen.
| Kriterium | Choreographie wählen wenn… | Orchestrierung wählen wenn… |
|---|---|---|
| Workflow-Komplexität | Einfache, lineare Abläufe | Komplexe Bedingungslogik |
| Service-Ownership | Verschiedene Teams/Domains | Ein Team verantwortlich |
| Fehlertoleranz | Eventual Consistency OK | Strong Consistency erforderlich |
| Skalierungsanforderungen | Hoher Durchsatz | Kontrollierte Verarbeitung |
| Auditierbarkeit | Lose Nachverfolgung ausreichend | Vollständige Audit-Trails |
| Änderungshäufigkeit | Services ändern sich unabhängig | Workflow ändert sich häufig |
| Team-Struktur | Autonome Teams | Zentrales Architecture-Team |
// Decision Framework als Code
public enum WorkflowPattern {
CHOREOGRAPHY, ORCHESTRATION, HYBRID
}
@Component
public class WorkflowPatternSelector {
public WorkflowPattern selectPattern(WorkflowRequirements requirements) {
int choreographyScore = calculateChoreographyScore(requirements);
int orchestrationScore = calculateOrchestrationScore(requirements);
if (Math.abs(choreographyScore - orchestrationScore) < 2) {
return WorkflowPattern.HYBRID;
}
return choreographyScore > orchestrationScore
? WorkflowPattern.CHOREOGRAPHY
: WorkflowPattern.ORCHESTRATION;
}
private int calculateChoreographyScore(WorkflowRequirements req) {
int score = 0;
if (req.isSimpleLinearFlow()) score += 3;
if (req.hasMultipleTeamOwnership()) score += 2;
if (req.allowsEventualConsistency()) score += 2;
if (req.requiresHighThroughput()) score += 3;
if (req.hasLowAuditRequirements()) score += 1;
return score;
}
private int calculateOrchestrationScore(WorkflowRequirements req) {
int score = 0;
if (req.hasComplexConditionalLogic()) score += 3;
if (req.requiresStrongConsistency()) score += 3;
if (req.hasStrictAuditRequirements()) score += 2;
if (req.hasFrequentWorkflowChanges()) score += 2;
if (req.requiresCentralizedErrorHandling()) score += 2;
return score;
}
}E-Commerce Order Processing - Choreographie: - Services: Order, Payment, Inventory, Shipping gehören verschiedenen Teams - Workflow: Einfach, parallele Verarbeitung möglich - Fehlertoleranz: Eventual Consistency akzeptabel - Skalierung: Hoher Durchsatz erforderlich
Loan Approval Process - Orchestrierung: - Komplexe Bedingungslogik (Kreditscoring, manuelle Prüfungen) - Strikte Compliance-Anforderungen - Zentrale Fehlerbehandlung und Rollback nötig - Ein Team verantwortlich für gesamten Prozess
Customer Onboarding - Hybrid: - Core Flow: Orchestriert (KYC, Account Creation) - Marketing Activities: Choreographiert (Welcome Email, Analytics) - Support Processes: Choreographiert (Ticket Creation, Follow-ups)
# Beispiel: Hybrid Customer Onboarding
class CustomerOnboardingCoordinator:
async def handle_customer_registration(self, event: CustomerRegisteredEvent):
# Core Business Logic: Orchestriert
await self._orchestrate_core_onboarding(event)
# Marketing und Support: Choreographiert über Events
await self.event_publisher.publish(CustomerOnboardingStartedEvent(
customer_id=event.customer_id,
registration_source=event.source
))
async def _orchestrate_core_onboarding(self, event: CustomerRegisteredEvent):
# Strikte Reihenfolge für kritische Schritte
kyc_result = await self.kyc_service.verify_customer(event.customer_id)
if kyc_result.approved:
account = await self.account_service.create_account(event.customer_id)
await self.notification_service.send_welcome_message(event.customer_id)
else:
await self._handle_kyc_rejection(event, kyc_result)Die Entscheidung zwischen Choreographie und Orchestrierung ist selten binär. Moderne Event-Driven Architekturen nutzen oft hybride Ansätze, die die Stärken beider Patterns in verschiedenen Bereichen der Anwendung kombinieren. Das Verständnis der Trade-offs und die bewusste Anwendung passender Patterns je Kontext führt zu robusten, wartbaren Systemen.