10 Event- vs. Message-Driven Thinking

Der Unterschied zwischen Event-driven und Message-driven Ansätzen ist fundamental für das Verständnis moderner Architekturparadigmen. Obwohl beide Konzepte asynchrone Kommunikation verwenden, führen sie zu völlig unterschiedlichen Systemdesigns und Verantwortlichkeiten.

10.1 Konzeptionelle Unterschiede

Die grundlegende Denkweise unterscheidet sich fundamental: Events sind unveränderliche Fakten über etwas, das bereits geschehen ist, während Messages Anweisungen für etwas sind, das geschehen soll.

10.1.1 Sprachliche Indikatoren

Die verwendete Sprache verrät oft den jeweiligen Ansatz:

Event-driven:

Message-driven:

10.2 Auswirkungen auf Systemdesign

10.2.1 Ownership und Verantwortlichkeit

Bei Events liegt die Definitionsmacht beim Producer. Ein OrderService definiert die Struktur seines OrderPlaced-Events basierend auf seinen internen Geschäftsregeln. Consumer müssen sich an diese Struktur anpassen.

Bei Messages liegt die Definitionsmacht beim Consumer. Ein PaymentService definiert, wie ein ProcessPayment-Command strukturiert sein muss, damit er ihn verarbeiten kann.

// Event-driven: Producer definiert Struktur
public class OrderService {
    public void placeOrder(Order order) {
        // OrderService entscheidet, welche Daten relevant sind
        OrderPlacedEvent event = new OrderPlacedEvent(
            order.getId(),
            order.getCustomerId(), 
            order.getItems(),
            order.getTotalAmount(),
            order.getOrderDate()
        );
        publishEvent(event);
    }
}

// Message-driven: Consumer definiert Struktur  
public class PaymentService {
    // PaymentService definiert, was er für ProcessPayment braucht
    @MessageHandler
    public void handle(ProcessPaymentCommand command) {
        // Command muss paymentMethod, amount, orderId enthalten
        processPayment(command.getPaymentMethod(), 
                      command.getAmount(), 
                      command.getOrderId());
    }
}
# Event-driven: Producer definiert Struktur
class OrderService:
    def place_order(self, order):
        # OrderService entscheidet, welche Daten relevant sind
        event = {
            'eventType': 'OrderPlaced',
            'orderId': order['id'],
            'customerId': order['customerId'],
            'items': order['items'],
            'totalAmount': order['totalAmount'],
            'orderDate': order['orderDate']
        }
        self.publish_event(event)

# Message-driven: Consumer definiert Struktur
class PaymentService:
    def handle_process_payment(self, command):
        # PaymentService definiert Command-Struktur
        self.process_payment(
            command['paymentMethod'],
            command['amount'], 
            command['orderId']
        )

10.2.2 Fehlerbehandlung und Resilience

Event-driven Systeme behandeln Fehler defensiv. Wenn ein Consumer ein Event nicht verarbeiten kann, ist das sein Problem – das Event bleibt bestehen und kann später erneut verarbeitet werden.

Message-driven Systeme erfordern oft koordinierte Fehlerbehandlung zwischen Sender und Empfänger, da der Sender ein Ergebnis erwartet.

10.2.3 Versionierung und Evolution

Events entwickeln sich typischerweise additiv. Neue Felder werden hinzugefügt, aber bestehende bleiben erhalten, um Kompatibilität zu gewährleisten:

// OrderPlaced Event v1
{
    "eventType": "OrderPlaced",
    "version": "v1",
    "orderId": "123",
    "customerId": "456"
}

// OrderPlaced Event v2 (additiv)
{
    "eventType": "OrderPlaced", 
    "version": "v2",
    "orderId": "123",
    "customerId": "456",
    "customerTier": "premium",
    "promotionCode": "SAVE10"
}

Messages hingegen entwickeln sich oft disruptiv, da sie spezifische Aktionen auslösen und Änderungen direktere Auswirkungen haben.

10.3 Best Practices für Event-Modellierung

10.3.1 Rich Events vs. Thin Events

Rich Events enthalten alle relevanten Informationen zum Zeitpunkt des Ereignisses:

// Rich Event - enthält alle relevanten Daten
public class OrderPlacedEvent {
    private String orderId;
    private CustomerInfo customer;     // Vollständige Kundeninformation
    private List<OrderItem> items;     // Alle Bestellpositionen
    private Address shippingAddress;   // Lieferadresse
    private PaymentInfo paymentInfo;   // Zahlungsinformationen
    private BigDecimal totalAmount;
}

Thin Events enthalten nur Referenzen und grundlegende Informationen:

// Thin Event - nur Referenzen
public class OrderPlacedEvent {
    private String orderId;
    private String customerId;
    private BigDecimal totalAmount;
    // Consumer müssen weitere Daten bei Bedarf abrufen
}

Empfehlung: Verwenden Sie Rich Events für kritische Geschäftsereignisse, um Consumer-Autonomie zu fördern und Round-Trips zu vermeiden.

10.3.2 Event-Granularität

Events sollten eine einzelne Geschäftseinheit repräsentieren:

// Gut: Ein Event pro Geschäftsereignis
OrderPlacedEvent, PaymentProcessedEvent, OrderShippedEvent

// Vermeiden: Zu generische Events
OrderChangedEvent, OrderUpdatedEvent, DataChangedEvent

10.3.3 Domain Events vs. Integration Events

Unterscheiden Sie zwischen Domain Events (innerhalb einer Bounded Context) und Integration Events (zwischen Bounded Contexts):

// Domain Event (intern im Order-Service)
class OrderValidationCompleted {
    private String orderId;
    private boolean isValid;
    private List<ValidationError> errors;
}

// Integration Event (zwischen Services)  
class OrderPlaced {
    private String orderId;
    private String customerId;
    private BigDecimal totalAmount;
    // Nur öffentlich relevante Informationen
}

10.3.4 Naming Conventions

Event-driven Thinking ermöglicht lose gekoppelte, skalierbare Systeme, erfordert aber ein Umdenken von imperativer zu deklarativer Kommunikation. Das Verständnis dieser konzeptionellen Unterschiede ist entscheidend für den Erfolg einer Event-Driven Architecture.