50 Vergleich und Anwendungsszenarien

50.1 Choreography vs. Orchestration Trade-offs

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.

50.1.1 Struktureller Vergleich

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

50.1.2 Performance-Charakteristika

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);
        }
    }
}

50.1.3 Ausfallverhalten und Resilience

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)

50.2 Hybrid Approaches

50.2.1 Event-gesteuerte Orchestratoren

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()
        ));
    }
}

50.2.2 Choreographie mit Process Managers

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
            ))

50.2.3 Domain-spezifische Mischformen

// 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
    }
}

50.3 Decision Criteria

50.3.1 Entscheidungsmatrix

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

50.3.2 Implementierungs-Leitfaden

// 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;
    }
}

50.3.3 Praktische Anwendungsbeispiele

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.