Tags: event-driven, architecture, pub-sub, kafka, rabbitmq, microservices
Level: intermediate
TL;DR: Quick reference on Event-Driven Architecture (EDA), focusing on asynchronous communication, service decoupling, pub/sub patterns, and eventual consistency.
System components communicate by emitting and reacting to events instead of making direct synchronous calls.
Producers generate events without knowing who consumes them, ensuring strict service decoupling.
Consumers subscribe to event channels or topics and process incoming events asynchronously.
Event brokers (e.g., Kafka, RabbitMQ) act as intermediaries for routing, queuing, and persisting events.
EDA naturally promotes scalability since producers and consumers can be scaled independently based on load.
The architecture embraces eventual consistency, shifting away from traditional ACID distributed transactions.
Event Sourcing is a related pattern where the system state is stored as an immutable sequence of state-changing events.
Idempotency is mandatory for consumers to safely handle retry mechanisms and at-least-once delivery.
Q: How do you handle distributed transactions in an event-driven system?R: I typically use the Saga pattern, implementing either choreography via events or orchestration through a central coordinator, handling rollbacks with compensating transactions.
Q: What is the difference between message queues like RabbitMQ and event streams like Kafka?R: I use RabbitMQ for traditional task queuing, complex routing, and point-to-point delivery. I choose Kafka for high-throughput event streaming, log retention, and event replayability.
Q: How do you guarantee an event is published when a database state changes?R: I implement the Transactional Outbox pattern to atomically update the business entity and write the event to an outbox table, using a relay process to publish it safely.
Assuming immediate consistency across services instead of designing UI and logic for eventual consistency.
Failing to make consumer processing idempotent, leading to corrupted state during message retries.
Creating complex, untracked cyclic event chains that result in infinite processing loops.
Losing events by publishing them before the local database transaction successfully commits.
Treating events as commands (imperative) rather than facts about something that already happened (declarative).
// Example: Basic Idempotent Consumer Structure
async function processOrderCreatedEvent(event: OrderCreatedEvent) {
const isProcessed = await idempotencyRepo.exists(event.eventId);
if (isProcessed) {
console.log(`Event ${event.eventId} already processed. Skipping.`);
return;
}
await paymentService.reserveFunds(event.payload.amount);
await idempotencyRepo.save(event.eventId);
}
[ ] Review differences between Choreography and Orchestration in Sagas.
[ ] Memorize the exact mechanics of the CDC (Change Data Capture) approach for the Outbox pattern.