Event-Namen sind mehr als technische Bezeichner – sie transportieren Geschäftslogik, definieren Systemgrenzen und ermöglichen oder verhindern Verständlichkeit. Durchdachte Namenskonventionen und Kontraktdesign schaffen Klarheit in komplexen Systemlandschaften, während schlecht benannte Events zu Missverständnissen und Integrationsproblemen führen.
Konsistente Event-Benennung folgt erkennbaren Mustern, die sowohl die Geschäftssemantik als auch die technische Handhabung unterstützen. Event-Namen sollten selbsterklärend sein und die Ubiquitous Language der jeweiligen Domäne widerspiegeln.
Grundlegende Naming-Patterns:
// Spring Boot - Konsistente Event-Naming-Patterns
public class ECommerceEventNaming {
// Pattern 1: Domain + BusinessAction + PastTense
public static class OrderPlaced { // Order-Domain, Place-Action, Past-Tense
private String orderId;
private String customerId;
// Event dokumentiert abgeschlossene Aktion
}
public static class PaymentProcessed { // Payment-Domain, Process-Action, Past-Tense
private String paymentId;
private String orderId;
// Vergangenheitsform zeigt vollendete Tatsache
}
public static class InventoryReserved { // Inventory-Domain, Reserve-Action, Past-Tense
private String productId;
private Integer quantity;
// Klare Geschäftssprache
}
// Pattern 2: BusinessEntity + StateChange
public static class CustomerActivated { // Customer-Entity, Activated-State
private String customerId;
private Instant activatedAt;
// Zustandsänderung explizit
}
public static class ProductDiscontinued { // Product-Entity, Discontinued-State
private String productId;
private String reason;
// Geschäftsereignis klar benannt
}
// Pattern 3: BusinessProcess + Milestone
public static class ShippingPrepared { // Shipping-Process, Prepared-Milestone
private String orderId;
private String shippingCarrier;
// Prozess-Fortschritt dokumentiert
}
public static class OrderFulfillmentCompleted { // OrderFulfillment-Process, Completed-Milestone
private String orderId;
private Instant completedAt;
// End-to-End-Prozess abgeschlossen
}
}Anti-Patterns vermeiden:
// Spring Boot - Event-Naming Anti-Patterns
public class EventNamingAntiPatterns {
// ❌ Zu generisch
public static class DataChanged { // Welche Daten? Wie geändert?
private String entityId;
private String changeType;
}
// ❌ Technisch statt geschäftlich
public static class DatabaseRecordInserted { // Implementierungsdetail
private String tableName;
private String recordId;
}
// ❌ Unklare Zeitform
public static class ProcessOrder { // Command, nicht Event
private String orderId;
}
// ❌ Domänen-übergreifend
public static class CustomerOrderPaymentShippingUpdate { // Zu viele Verantwortungen
private String customerId;
private String orderId;
private String paymentStatus;
private String shippingStatus;
}
// ✅ Bessere Alternativen:
public static class CustomerProfileUpdated {
private String customerId;
private CustomerProfile updatedProfile;
// Spezifisch und geschäftlich
}
public static class OrderStatusChanged {
private String orderId;
private OrderStatus previousStatus;
private OrderStatus newStatus;
// Klare Geschäftslogik
}
}# Python - Event-Naming-Konventionen mit Validierung
from dataclasses import dataclass
from typing import Pattern, List
import re
from enum import Enum
class EventNamingPattern(Enum):
DOMAIN_ACTION_PAST = "domain_action_past" # OrderPlaced
ENTITY_STATE_CHANGE = "entity_state_change" # CustomerActivated
PROCESS_MILESTONE = "process_milestone" # ShippingPrepared
BUSINESS_FACT = "business_fact" # PaymentDeclined
class EventNameValidator:
"""Validiert Event-Namen gegen Naming-Conventions"""
def __init__(self):
# Regex-Patterns für verschiedene Naming-Konventionen
self.patterns = {
EventNamingPattern.DOMAIN_ACTION_PAST: re.compile(
r'^[A-Z][a-zA-Z]+[A-Z][a-zA-Z]+(ed|d)$' # OrderPlaced, PaymentProcessed
),
EventNamingPattern.ENTITY_STATE_CHANGE: re.compile(
r'^[A-Z][a-zA-Z]+[A-Z][a-zA-Z]+$' # CustomerActivated, ProductDiscontinued
),
EventNamingPattern.PROCESS_MILESTONE: re.compile(
r'^[A-Z][a-zA-Z]+(Prepared|Completed|Started|Failed)$' # ShippingPrepared
)
}
# Verbotene Wörter (zu generisch oder technisch)
self.forbidden_words = {
'data', 'record', 'entity', 'object', 'item', 'thing',
'updated', 'changed', 'modified', 'processed'
}
def validate_event_name(self, event_name: str) -> List[str]:
"""Validiert Event-Name und gibt Verbesserungsvorschläge"""
issues = []
# Prüfe auf verbotene Wörter
lower_name = event_name.lower()
for forbidden in self.forbidden_words:
if forbidden in lower_name:
issues.append(f"Avoid generic word '{forbidden}' - be more specific")
# Prüfe Pattern-Konformität
matches_pattern = any(
pattern.match(event_name)
for pattern in self.patterns.values()
)
if not matches_pattern:
issues.append("Name doesn't follow standard patterns (DomainActionPast, EntityStateChange, ProcessMilestone)")
# Prüfe Geschäftssprache
if any(tech_word in lower_name for tech_word in ['database', 'table', 'record', 'insert', 'update']):
issues.append("Use business language, not technical implementation details")
return issues
# Korrekte Event-Namen mit Pattern-Zuordnung
@dataclass
class WellNamedEvents:
"""Beispiele für korrekt benannte Events"""
# Domain + Action + Past Tense
order_placed: str = "OrderPlaced"
payment_processed: str = "PaymentProcessed"
inventory_reserved: str = "InventoryReserved"
# Entity + State Change
customer_activated: str = "CustomerActivated"
product_discontinued: str = "ProductDiscontinued"
subscription_renewed: str = "SubscriptionRenewed"
# Process + Milestone
shipping_prepared: str = "ShippingPrepared"
fulfillment_completed: str = "FulfillmentCompleted"
quality_check_passed: str = "QualityCheckPassed"
def get_pattern(self, event_name: str) -> EventNamingPattern:
"""Ermittelt das Naming-Pattern eines Events"""
validator = EventNameValidator()
for pattern_type, regex in validator.patterns.items():
if regex.match(event_name):
return pattern_type
raise ValueError(f"Event name '{event_name}' doesn't match any standard pattern")Namespace- und Versionierungs-Konventionen:
// Spring Boot - Namespace und Versionierung
public class EventNamespacing {
// Hierarchische Namespaces
public static final String DOMAIN_PREFIX = "de.eda.training";
// Domain-spezifische Namespaces
public static class OrderDomain {
public static final String NAMESPACE = DOMAIN_PREFIX + ".order";
@JsonTypeName("OrderPlaced")
public static class OrderPlaced {
@JsonProperty("@type")
private String type = NAMESPACE + ".OrderPlaced";
@JsonProperty("@version")
private String version = "1.0";
// Event-Daten...
}
}
public static class PaymentDomain {
public static final String NAMESPACE = DOMAIN_PREFIX + ".payment";
@JsonTypeName("PaymentProcessed")
public static class PaymentProcessed {
@JsonProperty("@type")
private String type = NAMESPACE + ".PaymentProcessed";
@JsonProperty("@version")
private String version = "2.1";
// Event-Daten...
}
}
// Topic-Naming mit Konventionen
public static class TopicNaming {
// Pattern: domain.event.version
public static final String ORDER_PLACED_V1 = "order.placed.v1";
public static final String ORDER_PLACED_V2 = "order.placed.v2";
public static final String PAYMENT_PROCESSED_V2 = "payment.processed.v2";
// Environment-spezifische Topics
public static String getEnvironmentTopic(String baseTopic, String environment) {
return environment + "." + baseTopic; // prod.order.placed.v1
}
}
}API-First Design behandelt Events als APIs mit definierten Kontrakten, Dokumentation und Backward-Compatibility-Garantien. Events werden bewusst designed, nicht zufällig entstehen gelassen.
Event-Kontrakt-Definition:
// Spring Boot - API-First Event-Design
/**
* OrderPlaced Event - Public API Contract
*
* Publiziert wenn eine Bestellung erfolgreich aufgegeben wurde.
*
* Business Rules:
* - Wird nur bei gültiger Zahlung publiziert
* - Enthält alle Items zur Zeit der Bestellung
* - totalAmount entspricht Summe aller Item-Preise inkl. Steuern
*
* Consumer Expectations:
* - PaymentService: Verarbeitet Zahlung basierend auf totalAmount
* - InventoryService: Reserviert Items basierend auf items-Liste
* - ShippingService: Bereitet Versand basierend auf customerId vor
*
* Schema Stability:
* - Alle Felder sind required und dürfen nicht entfernt werden
* - Neue optionale Felder können hinzugefügt werden
* - Feldtypen dürfen nicht geändert werden
*/
@EventContract(
name = "OrderPlaced",
version = "1.0",
description = "Published when a customer successfully places an order",
compatibility = CompatibilityLevel.BACKWARD_COMPATIBLE
)
public class OrderPlaced {
@EventField(
description = "Unique identifier for this specific event instance",
required = true,
stability = FieldStability.STABLE
)
private String eventId;
@EventField(
description = "Business identifier of the placed order",
required = true,
stability = FieldStability.STABLE,
businessKey = true
)
private String orderId;
@EventField(
description = "Customer who placed the order",
required = true,
stability = FieldStability.STABLE
)
private String customerId;
@EventField(
description = "Complete list of ordered items with quantities and prices",
required = true,
stability = FieldStability.STABLE
)
private List<OrderItem> items;
@EventField(
description = "Total order amount including taxes and fees",
required = true,
stability = FieldStability.STABLE,
validation = "Must equal sum of all item totals"
)
private BigDecimal totalAmount;
@EventField(
description = "Timestamp when order was placed (ISO 8601)",
required = true,
stability = FieldStability.STABLE
)
private Instant placedAt;
// Konstruktor, Getter, Builder...
}
// Event-Kontrakt Metadaten
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EventContract {
String name();
String version();
String description();
CompatibilityLevel compatibility() default CompatibilityLevel.BACKWARD_COMPATIBLE;
String[] deprecatedIn() default {};
String[] removedIn() default {};
}
public enum CompatibilityLevel {
BACKWARD_COMPATIBLE, // Neue Felder nur optional
FORWARD_COMPATIBLE, // Consumer ignorieren unbekannte Felder
FULL_COMPATIBLE, // Beide Richtungen
BREAKING_CHANGE // Expliziter Breaking Change
}# Python - API-First Event-Kontrakte
from dataclasses import dataclass, field
from typing import Dict, Any, List, Optional
from enum import Enum
from datetime import datetime
import json
class FieldStability(Enum):
STABLE = "stable" # Feld wird nie geändert
EVOLVING = "evolving" # Feld kann kompatibel erweitert werden
EXPERIMENTAL = "experimental" # Feld kann sich ändern
DEPRECATED = "deprecated" # Feld wird entfernt
@dataclass
class EventFieldMeta:
"""Metadaten für Event-Felder"""
description: str
required: bool = True
stability: FieldStability = FieldStability.STABLE
business_key: bool = False
validation_rules: Optional[str] = None
@dataclass
class EventContractMeta:
"""Metadaten für Event-Kontrakte"""
name: str
version: str
description: str
published_by: str
consumers: List[str]
stability_guarantees: Dict[str, str]
breaking_changes: List[str] = field(default_factory=list)
class EventContractRegistry:
"""Registry für Event-Kontrakte und deren Metadaten"""
def __init__(self):
self.contracts = {}
self.field_metadata = {}
def register_contract(self, event_class: type, meta: EventContractMeta) -> None:
"""Registriert Event-Kontrakt mit Metadaten"""
contract_key = f"{meta.name}.{meta.version}"
self.contracts[contract_key] = {
'event_class': event_class,
'metadata': meta
}
def get_contract_documentation(self, event_name: str, version: str) -> Dict[str, Any]:
"""Generiert API-Dokumentation für Event-Kontrakt"""
contract_key = f"{event_name}.{version}"
if contract_key not in self.contracts:
raise ValueError(f"Contract {contract_key} not found")
contract = self.contracts[contract_key]
event_class = contract['event_class']
meta = contract['metadata']
return {
'name': meta.name,
'version': meta.version,
'description': meta.description,
'published_by': meta.published_by,
'consumers': meta.consumers,
'fields': self._extract_field_docs(event_class),
'stability_guarantees': meta.stability_guarantees,
'breaking_changes': meta.breaking_changes,
'example': self._generate_example(event_class)
}
def _extract_field_docs(self, event_class: type) -> Dict[str, Any]:
"""Extrahiert Feld-Dokumentation aus Event-Klasse"""
# Vereinfachte Implementierung - in Realität würde man
# Annotations und Typing-Informationen auswerten
return {
'event_id': {
'type': 'string',
'required': True,
'description': 'Unique event identifier'
},
'order_id': {
'type': 'string',
'required': True,
'description': 'Business order identifier'
}
# Weitere Felder...
}
# Konkrete Event-Kontrakte
@dataclass
class OrderPlacedContract:
"""API-First Design für OrderPlaced Event"""
# Stabile Kern-Felder (niemals ändern)
event_id: str
order_id: str
customer_id: str
total_amount: str # String für Decimal-Kompatibilität
placed_at: str # ISO 8601 String
# Evolving Fields (kompatible Erweiterung möglich)
items: List[Dict[str, Any]]
# Experimentelle Felder (können sich ändern)
metadata: Optional[Dict[str, Any]] = None
@classmethod
def get_contract_metadata(cls) -> EventContractMeta:
return EventContractMeta(
name="OrderPlaced",
version="1.0",
description="Published when customer successfully places order",
published_by="OrderService",
consumers=["PaymentService", "InventoryService", "ShippingService"],
stability_guarantees={
"backward_compatibility": "All existing fields remain stable",
"forward_compatibility": "New optional fields may be added",
"field_types": "Field types never change",
"required_fields": "Required fields never become optional"
}
)
def validate_contract(self) -> List[str]:
"""Validiert Event gegen Kontrakt-Regeln"""
violations = []
# Business Rule Validations
if not self.event_id:
violations.append("event_id is required")
if not self.order_id:
violations.append("order_id is required")
try:
from decimal import Decimal
amount = Decimal(self.total_amount)
if amount <= 0:
violations.append("total_amount must be positive")
except:
violations.append("total_amount must be valid decimal")
return violationsEvent-Dokumentation und Discovery:
// Spring Boot - Event-Registry und Dokumentation
@Component
public class EventContractRegistry {
private final Map<String, EventContractInfo> registeredEvents = new HashMap<>();
@PostConstruct
public void registerKnownEvents() {
// Automatische Registrierung via Classpath-Scanning
registerEvent(OrderPlaced.class);
registerEvent(PaymentProcessed.class);
registerEvent(InventoryReserved.class);
}
public void registerEvent(Class<?> eventClass) {
EventContract annotation = eventClass.getAnnotation(EventContract.class);
if (annotation != null) {
EventContractInfo info = EventContractInfo.builder()
.name(annotation.name())
.version(annotation.version())
.description(annotation.description())
.eventClass(eventClass)
.fields(extractFieldInfo(eventClass))
.build();
registeredEvents.put(annotation.name(), info);
}
}
@GetMapping("/api/events/contracts")
public Map<String, Object> getEventCatalog() {
return registeredEvents.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> Map.of(
"contract", entry.getValue(),
"schema", generateJsonSchema(entry.getValue().getEventClass()),
"example", generateExample(entry.getValue().getEventClass())
)
));
}
private Map<String, FieldInfo> extractFieldInfo(Class<?> eventClass) {
Map<String, FieldInfo> fields = new HashMap<>();
for (Field field : eventClass.getDeclaredFields()) {
EventField annotation = field.getAnnotation(EventField.class);
if (annotation != null) {
fields.put(field.getName(), FieldInfo.builder()
.name(field.getName())
.type(field.getType().getSimpleName())
.description(annotation.description())
.required(annotation.required())
.stability(annotation.stability())
.build());
}
}
return fields;
}
}Cross-Context Events überschreiten Bounded Context-Grenzen und erfordern besondere Designprinzipien, um lose Kopplung und Autonomie zu bewahren.
Integration Event Patterns:
// Spring Boot - Cross-Context Event-Design
public class CrossContextEventDesign {
// Pattern 1: Domain Event → Integration Event Transformation
public static class OrderDomainEvent {
// Interne Domänen-Repräsentation (reich an Details)
private String orderId;
private CustomerId customerId; // Value Object
private List<OrderLine> orderLines; // Domain-spezifische Struktur
private Money totalAmount; // Domain-spezifisches Value Object
private OrderStatus status; // Domain-Enum
private Instant placedAt;
}
public static class OrderPlacedIntegrationEvent {
// Vereinfachte Cross-Context-Repräsentation
private String eventId;
private String eventType = "OrderPlaced";
private String sourceContext = "Order";
private Instant occurredAt;
// Nur für andere Kontexte relevante Daten
private String orderId;
private String customerId; // String statt Value Object
private BigDecimal totalAmount; // Standard-Typ
private String currency;
private Map<String, String> orderItems; // Vereinfachte Item-Darstellung
// Anti-Corruption Layer Informationen
private String sourceVersion = "1.0";
private Map<String, Object> metadata;
}
// Pattern 2: Kontext-spezifische Event-Übersetzung
@Service
public class OrderEventTranslationService {
public OrderPlacedIntegrationEvent translateForIntegration(OrderDomainEvent domainEvent) {
return OrderPlacedIntegrationEvent.builder()
.eventId(UUID.randomUUID().toString())
.orderId(domainEvent.getOrderId())
.customerId(domainEvent.getCustomerId().getValue()) // Value Object → String
.totalAmount(domainEvent.getTotalAmount().getAmount()) // Money → BigDecimal
.currency(domainEvent.getTotalAmount().getCurrency().getCurrencyCode())
.orderItems(transformOrderLines(domainEvent.getOrderLines()))
.occurredAt(domainEvent.getPlacedAt())
.metadata(createMetadata(domainEvent))
.build();
}
private Map<String, String> transformOrderLines(List<OrderLine> orderLines) {
// Vereinfachung für Cross-Context-Konsum
return orderLines.stream()
.collect(Collectors.toMap(
line -> line.getProductId().getValue(),
line -> String.valueOf(line.getQuantity())
));
}
}
// Pattern 3: Kontext-Boundary Events
public static class CustomerContextBoundaryEvents {
// Event für Änderungen, die andere Kontexte interessieren
public static class CustomerTierChanged {
private String customerId;
private String previousTier;
private String newTier;
private Instant changedAt;
private String changeReason;
// Explizit für Cross-Context-Konsum designed
// Enthält nur Informationen, die andere Kontexte brauchen
}
// Event für interne Änderungen (bleibt im Kontext)
public static class CustomerProfileUpdated {
private String customerId;
private CustomerProfile profile; // Internes Value Object
private Instant updatedAt;
// Nicht für Cross-Context-Konsum gedacht
}
}
}# Python - Cross-Context Event-Translation und Anti-Corruption Layer
from dataclasses import dataclass, asdict
from typing import Dict, Any, Optional, List
from abc import ABC, abstractmethod
from datetime import datetime
class BoundedContextIdentifier(Enum):
ORDER = "order"
PAYMENT = "payment"
INVENTORY = "inventory"
SHIPPING = "shipping"
CUSTOMER = "customer"
@dataclass
class CrossContextEventEnvelope:
"""Wrapper für Cross-Context Events"""
# Metadaten für Cross-Context-Kommunikation
event_id: str
event_type: str
source_context: BoundedContextIdentifier
target_contexts: List[BoundedContextIdentifier]
occurred_at: datetime
correlation_id: Optional[str] = None
causation_id: Optional[str] = None
# Eigentliche Event-Daten
payload: Dict[str, Any]
# Anti-Corruption Layer Info
schema_version: str = "1.0"
translation_metadata: Dict[str, Any] = None
class EventTranslationService(ABC):
"""Basis für Event-Translation zwischen Kontexten"""
@abstractmethod
def translate_to_integration_event(self, domain_event: Any) -> CrossContextEventEnvelope:
pass
@abstractmethod
def translate_from_integration_event(self, envelope: CrossContextEventEnvelope) -> Any:
pass
class OrderEventTranslationService(EventTranslationService):
"""Übersetzt Order-Domain-Events für Cross-Context-Kommunikation"""
def translate_to_integration_event(self, domain_event) -> CrossContextEventEnvelope:
"""Übersetzt reiches Domain-Event in vereinfachtes Integration-Event"""
# Anti-Corruption: Domain-Komplexität vor anderen Kontexten verstecken
simplified_payload = {
'order_id': domain_event.order_id,
'customer_id': str(domain_event.customer_id), # Value Object → String
'total_amount': str(domain_event.total_amount.amount), # Money → String
'currency': domain_event.total_amount.currency,
'item_count': len(domain_event.order_lines),
'categories': self._extract_categories(domain_event.order_lines)
}
return CrossContextEventEnvelope(
event_id=self._generate_event_id(),
event_type="OrderPlaced",
source_context=BoundedContextIdentifier.ORDER,
target_contexts=[
BoundedContextIdentifier.PAYMENT,
BoundedContextIdentifier.INVENTORY,
BoundedContextIdentifier.SHIPPING
],
occurred_at=domain_event.placed_at,
payload=simplified_payload,
translation_metadata={
'original_event_type': type(domain_event).__name__,
'translation_time': datetime.now(),
'simplified_fields': ['customer_id', 'total_amount', 'order_lines']
}
)
def translate_from_integration_event(self, envelope: CrossContextEventEnvelope):
"""Übersetzt Integration-Event zurück in Domain-Kontext"""
# In der Regel nicht notwendig, da Integration-Events
# von anderen Kontexten kommen
pass
def _extract_categories(self, order_lines: List) -> List[str]:
"""Extrahiert Produkt-Kategorien für andere Kontexte"""
# Vereinfachung für Cross-Context-Konsum
return list(set(line.product.category for line in order_lines))
class ContextBoundaryEventDesign:
"""Design-Patterns für Context-Boundary Events"""
@dataclass
class CustomerEligibilityChanged:
"""Optimal für Cross-Context: Enthält Entscheidungskontext"""
customer_id: str
previous_tier: str
new_tier: str
tier_benefits: List[str]
change_effective_date: datetime
change_reason: str
# Ermöglicht autonome Entscheidungen in anderen Kontexten
def enables_free_shipping(self) -> bool:
return self.new_tier in ['premium', 'vip']
def enables_priority_support(self) -> bool:
return self.new_tier == 'vip'
@dataclass
class ProductAvailabilityChanged:
"""Cross-Context Event mit vollständigem Kontext"""
product_id: str
previous_stock_level: int
new_stock_level: int
availability_status: str # "available", "low_stock", "out_of_stock"
restock_expected_date: Optional[datetime]
affected_categories: List[str]
# Ermöglicht autonome Reaktionen
def should_notify_customers(self) -> bool:
return (self.previous_stock_level == 0 and
self.new_stock_level > 0)
def should_trigger_reorder(self) -> bool:
return (self.new_stock_level < 10 and
self.availability_status == "low_stock")
class CrossContextEventValidator:
"""Validiert Events für Cross-Context-Tauglichkeit"""
def validate_cross_context_event(self, event_data: Dict[str, Any]) -> List[str]:
"""Prüft Event auf Cross-Context-Design-Prinzipien"""
issues = []
# Prüfe auf Domain-spezifische Value Objects
if self._contains_value_objects(event_data):
issues.append("Contains domain-specific value objects - use primitives for cross-context")
# Prüfe auf zu viele interne Details
if len(event_data) > 10:
issues.append("Too many fields - consider simplifying for cross-context consumption")
# Prüfe auf fehlenden Entscheidungskontext
if not self._has_decision_context(event_data):
issues.append("Missing decision context - other contexts cannot act autonomously")
return issues
def _contains_value_objects(self, data: Dict[str, Any]) -> bool:
"""Prüft auf komplexe Domain-Objekte"""
# Vereinfachte Implementierung
return any(isinstance(value, dict) and 'class' in str(type(value))
for value in data.values())
def _has_decision_context(self, data: Dict[str, Any]) -> bool:
"""Prüft auf ausreichenden Kontext für autonome Entscheidungen"""
# Heuristic: Events sollten mindestens 3-4 relevante Felder haben
relevant_fields = [k for k, v in data.items()
if not k.startswith('_') and v is not None]
return len(relevant_fields) >= 3Context Mapping für Events:
| Kontext | Published Events | Consumed Events | Integration Points |
|---|---|---|---|
| Order | OrderPlaced, OrderCancelled | PaymentProcessed, InventoryReserved | Payment, Inventory, Shipping |
| Payment | PaymentProcessed, PaymentDeclined | OrderPlaced | Order, Fraud |
| Inventory | InventoryReserved, StockLevelChanged | OrderPlaced, OrderCancelled | Order, Procurement |
| Shipping | ShippingPrepared, OrderShipped | OrderPlaced, PaymentProcessed | Order, Payment, Logistics |
Event-Namen, Kontrakte und Context-Design bilden das Fundament verständlicher und maintainbarer Event-Architekturen. Konsistente Naming-Conventions schaffen Klarheit, API-First Design gewährleistet stabile Kontrakte, und durchdachtes Cross-Context-Design ermöglicht autonome System-Evolution bei loser Kopplung.