Die Bedeutung und der Detailgrad von Events beeinflussen maßgeblich die Flexibilität und Wartbarkeit event-getriebener Systeme. Während technische Korrektheit eine Grundvoraussetzung ist, entscheidet die semantische Klarheit über die langfristige Entwickelbarkeit und Verständlichkeit der Architektur.
Das Single Responsibility Principle gilt nicht nur für Klassen und Module, sondern auch für Events. Ein Event sollte genau ein geschäftlich relevantes Ereignis repräsentieren und alle dafür notwendigen, aber nur diese Informationen enthalten.
Verletzung des Single Responsibility:
// Spring Boot - Anti-Pattern: Zu viele Verantwortlichkeiten
public class OrderProcessingEvent {
// Bestellung
private String orderId;
private List<OrderItem> items;
private BigDecimal totalAmount;
// Zahlung
private String paymentMethod;
private String transactionId;
private PaymentStatus paymentStatus;
// Versand
private String shippingAddress;
private String carrierService;
private LocalDate estimatedDelivery;
// Inventar
private Map<String, Integer> reservedQuantities;
private List<String> unavailableItems;
}Korrekte Single Responsibility:
// Spring Boot - Focused Events mit klarer Verantwortung
public class OrderEvents {
public static class OrderPlaced {
private String orderId;
private String customerId;
private List<OrderItem> items;
private BigDecimal totalAmount;
private Instant placedAt;
// Nur bestellungsrelevante Daten
}
public static class PaymentProcessed {
private String orderId;
private String paymentId;
private String paymentMethod;
private BigDecimal amount;
private PaymentStatus status;
// Nur zahlungsrelevante Daten
}
public static class InventoryReserved {
private String orderId;
private Map<String, Integer> reservedItems;
private String reservationId;
private Instant reservedUntil;
// Nur inventarrelevante Daten
}
}# Python - Entsprechende fokussierte Events
from dataclasses import dataclass
from typing import Dict, List
from datetime import datetime
from decimal import Decimal
@dataclass(frozen=True)
class OrderPlaced:
"""Event für reine Bestellinformationen"""
order_id: str
customer_id: str
items: List[dict]
total_amount: Decimal
placed_at: datetime
@dataclass(frozen=True)
class PaymentProcessed:
"""Event für reine Zahlungsinformationen"""
order_id: str
payment_id: str
payment_method: str
amount: Decimal
status: str
@dataclass(frozen=True)
class InventoryReserved:
"""Event für reine Inventarinformationen"""
order_id: str
reserved_items: Dict[str, int]
reservation_id: str
reserved_until: datetimeSingle Responsibility bei Events ermöglicht:
Event-Sourcing und State-Transfer repräsentieren zwei grundlegend verschiedene Philosophien für die Event-Modellierung mit jeweils eigenen Vor- und Nachteilen.
Event-Sourcing Events dokumentieren die Intention oder Aktion, die zu einer Zustandsänderung geführt hat:
// Spring Boot - Event-Sourcing Stil
public class OrderEventSourcing {
public static class OrderItemAdded {
private String orderId;
private String productId;
private Integer quantity;
private BigDecimal unitPrice;
private Instant addedAt;
// Dokumentiert die AKTION des Hinzufügens
}
public static class OrderItemQuantityChanged {
private String orderId;
private String productId;
private Integer oldQuantity;
private Integer newQuantity;
private String changeReason;
// Dokumentiert die ÄNDERUNG der Menge
}
public static class OrderSubmitted {
private String orderId;
private String submittedBy;
private Instant submittedAt;
// Dokumentiert die AKTION des Absendens
}
}State-Transfer Events übertragen den aktuellen Zustand nach einer Änderung:
// Spring Boot - State-Transfer Stil
public class OrderStateTransfer {
public static class OrderStateChanged {
private String orderId;
private OrderState currentState;
private List<OrderItem> currentItems;
private BigDecimal currentTotal;
private Instant lastModified;
// Überträgt den ZUSTAND nach Änderung
}
public static class OrderSnapshot {
private String orderId;
private String customerId;
private OrderStatus status;
private List<OrderItem> items;
private BigDecimal totalAmount;
private PaymentInfo paymentInfo;
private ShippingInfo shippingInfo;
// Vollständiger aktueller ZUSTAND
}
}# Python - Vergleich beider Ansätze
@dataclass(frozen=True)
class OrderItemAdded: # Event-Sourcing
"""Dokumentiert WAS passiert ist"""
order_id: str
product_id: str
quantity: int
unit_price: Decimal
added_at: datetime
@dataclass(frozen=True)
class OrderUpdated: # State-Transfer
"""Überträgt WIE es jetzt ist"""
order_id: str
current_status: str
current_items: List[dict]
current_total: Decimal
last_modified: datetime| Aspekt | Event-Sourcing | State-Transfer |
|---|---|---|
| Information | Intention/Aktion | Aktueller Zustand |
| Historisierung | Vollständig möglich | Nur aktueller Stand |
| Replay-Fähigkeit | Ja, alle Schritte | Nein, nur Endstand |
| Komplexität | Höher (Rekonstruktion) | Niedriger (direkter Zustand) |
| Auditierbarkeit | Exzellent | Begrenzt |
| Performance | Langsamer (Aggregation) | Schneller (direkter Zugriff) |
Choreography-basierte Systeme koordinieren sich durch Event-Austausch ohne zentrale Steuerung. Events müssen daher so designed werden, dass sie autonome Entscheidungen ermöglichen.
Choreography-unfreundlich - Events mit wenig Kontext:
// Spring Boot - Schlechtes Choreography-Design
public class PoorChoreographyEvents {
public static class OrderStatusChanged {
private String orderId;
private String newStatus; // Zu wenig Kontext!
// Consumer wissen nicht, WARUM sich der Status änderte
}
public static class ProcessNext {
private String orderId;
// Zu generisch - was soll "next" sein?
}
}Choreography-freundlich - Events mit ausreichendem Kontext:
// Spring Boot - Choreography-optimierte Events
public class ChoreographyFriendlyEvents {
public static class OrderPaymentConfirmed {
private String orderId;
private String paymentId;
private BigDecimal paidAmount;
private String paymentMethod;
private Instant confirmedAt;
private List<OrderItem> paidItems;
// Vollständiger Kontext für Folgeaktionen
}
public static class OrderReadyForShipping {
private String orderId;
private String customerId;
private ShippingAddress destination;
private List<ShippableItem> items;
private ShippingPreferences preferences;
private Instant readyAt;
// Alle Infos für autonome Versandentscheidung
}
public static class InventoryBelowThreshold {
private String productId;
private Integer currentStock;
private Integer thresholdLevel;
private Integer recommendedReorderQuantity;
private List<String> affectedOrders;
// Kontext für autonome Nachbestellentscheidung
}
}# Python - Choreography-optimierte Event-Designs
@dataclass(frozen=True)
class OrderPaymentConfirmed:
"""Ermöglicht autonome Folgeentscheidungen"""
order_id: str
payment_id: str
paid_amount: Decimal
payment_method: str
confirmed_at: datetime
paid_items: List[dict]
def enables_shipping(self) -> bool:
"""Consumer kann autonom entscheiden"""
return self.paid_amount > 0 and len(self.paid_items) > 0
@dataclass(frozen=True)
class CustomerEligibilityChanged:
"""Vollständiger Kontext für Berechtigungsentscheidungen"""
customer_id: str
previous_tier: str
new_tier: str
tier_benefits: List[str]
change_reason: str
effective_date: datetime
def can_access_premium_features(self) -> bool:
"""Autonome Berechtigungsprüfung möglich"""
return self.new_tier in ['premium', 'vip']Gestaltungsprinzipien für Choreography:
// Spring Boot - Event-Design-Patterns für Choreography
public class ChoreographyPatterns {
// Pattern 1: Event mit Entscheidungskontext
public static class OrderEvaluated {
private String orderId;
private Boolean fraudRiskDetected;
private BigDecimal riskScore;
private List<String> riskFactors;
private Boolean requiresManualReview;
// Ermöglicht autonome Behandlungsentscheidung
}
// Pattern 2: Event mit Aktionsvorschlägen
public static class CustomerBehaviorAnalyzed {
private String customerId;
private CustomerSegment currentSegment;
private List<RecommendedAction> suggestedActions;
private Map<String, Double> behaviorScores;
// Gibt Handlungsempfehlungen mit
}
// Pattern 3: Event mit Kompensationsinfo
public static class PaymentFailed {
private String orderId;
private String paymentId;
private String failureReason;
private Boolean retryable;
private Integer maxRetryAttempts;
private Duration retryDelay;
// Ermöglicht autonome Wiederholungsstrategie
}
}Die semantische Gestaltung von Events manifestiert sich in konkreten Design-Entscheidungen:
// Spring Boot - Vollständiges Beispiel semantisch optimierter Events
@Component
public class SemanticEventExamples {
// Single Responsibility: Ein Event = Ein Geschäftsereignis
public static class CustomerSubscribedToNewsletter {
private String customerId;
private String newsletterType;
private String subscriptionSource;
private Instant subscribedAt;
private Map<String, String> preferences;
}
// Event-Sourcing: Dokumentiert Intention
public static class CustomerNewsletterPreferenceChanged {
private String customerId;
private String newsletterType;
private String oldFrequency;
private String newFrequency;
private String changeReason;
private Instant changedAt;
}
// Choreography: Vollständiger Entscheidungskontext
public static class CustomerNewsletterEngagementEvaluated {
private String customerId;
private String newsletterType;
private Double engagementScore;
private Integer consecutiveUnopened;
private Boolean recommendUnsubscribe;
private List<String> alternativeNewsletters;
private Instant evaluatedAt;
}
}# Python - Semantische Event-Design-Patterns
from abc import ABC, abstractmethod
from enum import Enum
class EventSemantic(Enum):
SINGLE_RESPONSIBILITY = "single_responsibility"
EVENT_SOURCING = "event_sourcing"
CHOREOGRAPHY_FRIENDLY = "choreography_friendly"
class SemanticEvent(ABC):
"""Basis für semantisch klare Events"""
@abstractmethod
def semantic_type(self) -> EventSemantic:
pass
@abstractmethod
def business_meaning(self) -> str:
pass
@dataclass(frozen=True)
class ProductReviewSubmitted(SemanticEvent):
"""Einzelne Verantwortlichkeit: Review-Ereignis"""
product_id: str
customer_id: str
rating: int
review_text: str
submitted_at: datetime
def semantic_type(self) -> EventSemantic:
return EventSemantic.SINGLE_RESPONSIBILITY
def business_meaning(self) -> str:
return f"Customer {self.customer_id} reviewed product {self.product_id}"
@dataclass(frozen=True)
class ProductRatingRecalculated(SemanticEvent):
"""Choreography-freundlich: Vollständiger Kontext"""
product_id: str
previous_average_rating: float
new_average_rating: float
total_reviews: int
rating_distribution: Dict[int, int]
significant_change: bool
recalculated_at: datetime
def semantic_type(self) -> EventSemantic:
return EventSemantic.CHOREOGRAPHY_FRIENDLY
def business_meaning(self) -> str:
return f"Product {self.product_id} rating changed from {self.previous_average_rating} to {self.new_average_rating}"Die semantische Gestaltung von Events erfordert bewusste Designentscheidungen, die die zukünftige Evolution und Flexibilität des Systems maßgeblich beeinflussen. Single Responsibility sorgt für Klarheit, die Wahl zwischen Event-Sourcing und State-Transfer beeinflusst Auditierbarkeit und Performance, während Choreography-freundliche Designs autonome Systemkoordination ermöglichen.