Lesson 5 of 15Event Driven Architecture

Event Driven Architecture

An event is a fact that already happened.

OrderPlaced
PaymentSuccess
UserRegistered
PasswordChanged
InvoiceGenerated

Good event names are past tense because producers should not command consumers. "SendOrderEmail" is a command. "OrderPlaced" is a fact. The notification system can decide whether that fact creates email, SMS, push, or nothing.

Publisher and Consumer

Order Service publishes OrderPlaced
Payment Service publishes PaymentSuccess
Identity Service publishes PasswordChanged

Notification Service consumes events
Analytics Service consumes events
Warehouse Service consumes events

The publisher owns business truth. The consumer owns its reaction. This makes systems independently deployable.

Event Contract

{
  "eventId": "evt_01H...",
  "eventType": "OrderPlaced",
  "version": 1,
  "occurredAt": "2026-07-08T10:00:00Z",
  "tenantId": "tenant_123",
  "userId": "user_456",
  "data": {
    "orderId": "ord_789",
    "amount": "1299.00",
    "currency": "INR"
  }
}

The event must contain enough information to route and render notifications, but not so much that it becomes a database dump. If the template needs product names, the notification worker can fetch a read model or receive a compact list.

Why Events Help

Netflix may publish "EpisodeReleased." Push, email digest, recommendations, and analytics can all react without Netflix's catalog service calling every downstream team.

GitHub may publish "IssueMentioned." Notification service creates in-app and email jobs. Search updates mention indexes. Audit service records activity.

Stripe may publish "InvoicePaymentFailed." Notification service alerts the account owner. Billing workflow schedules retry. Analytics tracks churn risk.

Outbox Pattern

The dangerous moment is when the product database commit succeeds but the event publish fails.

1. Save order in database
2. Publish OrderPlaced to broker
3. Broker is down
4. Order exists, but notification never happens

The outbox pattern stores the event in the same database transaction as the business write.

BEGIN;

INSERT INTO orders (id, user_id, status)
VALUES ('ord_1', 'user_1', 'placed');

INSERT INTO outbox_events (id, event_type, payload, status)
VALUES ('evt_1', 'OrderPlaced', '{"orderId":"ord_1"}', 'pending');

COMMIT;

A separate relay publishes pending outbox rows to the broker. This prevents lost events when the broker is temporarily unavailable.

If the business transaction commits, the event record commits with it. That is why the outbox pattern is so powerful.

Idempotent Consumer

Consumers must expect duplicate events.

async function handleOrderPlaced(event: EventPayload) {
  const exists = await db.notification.findUnique({
    where: {
      tenantId_eventId_userId_templateKey: {
        tenantId: event.tenantId,
        eventId: event.eventId,
        userId: event.userId,
        templateKey: "order_confirmation"
      }
    }
  });

  if (exists) return;

  await createNotificationFromEvent(event);
}

Event-driven systems prefer safe repetition over fragile perfection.

Common Mistakes

  1. Naming events as commands.
  2. Publishing events outside the business transaction with no outbox.
  3. Changing event payloads without versioning.
  4. Building consumers that cannot handle duplicate delivery.
  5. Making one event contain every possible downstream field.

Interview Questions

  1. What is the difference between an event and a command?
  2. How does the outbox pattern prevent lost notifications?
  3. Why must consumers be idempotent?
  4. How would you version an event payload?

Exercise

Design the PasswordChanged event contract. Include only fields that a security notification needs.

What you will learn

What an event is and why notifications should consume events.

How publishers and consumers stay decoupled.

Why event contracts need versioning.

How the outbox pattern prevents lost events.

Production checklist

  • Events are named as facts
  • Event schema is versioned
  • Publisher does not know channel details
  • Consumer is idempotent
  • Outbox is considered
  • Replay behavior is documented
Event Driven Architecture - Production Notification System Design | Niraj Kumar