Händelsestyrd Arkitektur (Event-Driven Architecture)
Introduktion
Händelsestyrd arkitektur (EDA) är ett designmönster där systemets komponenter kommunicerar genom att publicera och konsumera händelser (events). Istället för att direkt anropa varandra är komponenterna löst kopplade via en gemensam eventbuss eller meddelandekö.
Grundbegrepp
Vad är ett Event?
Ett event är ett faktum — något som har hänt i systemet. Det är oföränderligt och beskrivs alltid i dåtid.
{
"eventId": "evt-7f3a9b2c",
"eventType": "order.placed",
"occurredAt": "2025-03-15T14:32:00Z",
"payload": {
"orderId": "ord-001",
"customerId": "cust-42",
"totalAmount": 1299.00,
"currency": "SEK"
}
}
Aktörer
| Roll | Ansvar |
|---|---|
| Producer | Publicerar ett event när något händer (t.ex. Order Service publicerar order.placed) |
| Event Broker | Tar emot, lagrar och distribuerar events (Kafka, RabbitMQ, Azure Service Bus) |
| Consumer | Prenumererar på events och reagerar på dem (t.ex. Payment Service, Email Service) |
Arkitekturmönster
Pub/Sub (Publish-Subscribe)
Producers och consumers känner inte till varandra — de kommunicerar bara via topiken.
Order Service ──────▶ Topic: "order.placed" ──────▶ Payment Service
├────▶ Inventory Service
└────▶ Email Service
Event Sourcing
Istället för att bara spara nuläget sparas alla händelser som lett dit. State rekonstrueras genom att spela upp eventhistoriken.
Events: [OrderCreated] → [ItemAdded] → [ItemAdded] → [OrderConfirmed]
│
Nuläge = summan
CQRS — Command Query Responsibility Segregation
Separerar skrivoperationer (Commands) från läsoperationer (Queries) i olika modeller — ofta kombinerat med Event Sourcing.
Command: PlaceOrder ──▶ Write Model ──▶ Event Store ──▶ Read Model ──▶ Query: GetOrder
Exempel — E-handelssystem (Java / Kafka)
Producer: Order Service
@Service
public class OrderService {
private final KafkaTemplate<String, Object> kafkaTemplate;
private final OrderRepository repository;
public Order placeOrder(CreateOrderRequest request) {
// 1. Spara ordern
Order order = new Order(request.getCustomerId(), request.getItems());
repository.save(order);
// 2. Publicera event — vet inte vilka som lyssnar
OrderPlacedEvent event = new OrderPlacedEvent(
order.getId(),
order.getCustomerId(),
order.getTotalAmount(),
Instant.now()
);
kafkaTemplate.send("order.placed", order.getId(), event);
return order;
}
}
Consumer: Payment Service
@Component
public class PaymentEventHandler {
private final PaymentService paymentService;
@KafkaListener(topics = "order.placed", groupId = "payment-service")
public void handleOrderPlaced(OrderPlacedEvent event) {
// Reagerar på eventet — vet inte var det kom ifrån
try {
PaymentResult result = paymentService.processPayment(
event.getOrderId(),
event.getCustomerId(),
event.getTotalAmount()
);
if (result.isSuccessful()) {
// Publicerar ett nytt event
kafkaTemplate.send("payment.processed", new PaymentProcessedEvent(
event.getOrderId(), result.getTransactionId()
));
} else {
kafkaTemplate.send("payment.failed", new PaymentFailedEvent(
event.getOrderId(), result.getFailureReason()
));
}
} catch (Exception e) {
// Dead Letter Queue — felaktiga meddelanden hamnar här för granskning
kafkaTemplate.send("payment.dlq", event);
}
}
}
Consumer: Email Service
@Component
public class EmailEventHandler {
private final EmailService emailService;
private final CustomerRepository customerRepository;
@KafkaListener(topics = "payment.processed", groupId = "email-service")
public void handlePaymentProcessed(PaymentProcessedEvent event) {
Customer customer = customerRepository.findByOrderId(event.getOrderId());
emailService.sendOrderConfirmation(customer.getEmail(), event.getOrderId());
}
@KafkaListener(topics = "payment.failed", groupId = "email-service")
public void handlePaymentFailed(PaymentFailedEvent event) {
Customer customer = customerRepository.findByOrderId(event.getOrderId());
emailService.sendPaymentFailedNotification(customer.getEmail(), event.getReason());
}
}
Händelsekedjan visualiserad
Kund klickar "Köp"
│
▼
Order Service ──▶ order.placed
│
┌───────────┼────────────┐
▼ ▼ ▼
Payment Service Inventory Email Service
│ Service (väntar...)
▼
payment.processed
│
┌────┘
▼
Email Service ──▶ "Orderbekräftelse skickad"
Fördelar
- Löst koppling — Producers och consumers är oberoende av varandra.
- Skalbarhet — Consumers kan skalas horisontellt oberoende.
- Resiliens — Om Email Service är nere, buffras events och levereras när den startar om.
- Auditlog — Alla events kan sparas och granskas i efterhand.
- Extensibilitet — Lägg till nya consumers utan att ändra producers.
Nackdelar
- Eventual consistency — Systemet är inte alltid i ett konsekvent tillstånd.
- Svår debugging — Asynkrona flöden är svårare att följa och felsöka.
- Duplicerade events — Consumers måste vara idempotenta (tåla samma event flera gånger).
- Infrastrukturkostnad — Kräver en event broker (Kafka, RabbitMQ) att driftsätta.
Av Victor Hernandez från Bytebase.se