# Event Schema Documentation

# Event Schema Documentation

> **Project:** {{PROJECT_NAME}}
> **Version:** {{VERSION}}
> **Date:** {{DATE}}
> **Author:** {{AUTHOR}}
> **Status:** Draft | In Review | Approved
> **Reviewers:** {{REVIEWERS}}

## Document History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.1     | {{DATE}} | {{AUTHOR}} | Initial draft |

---

## 1. Event-Driven Architecture Overview

<!-- GUIDANCE: Describe the event-driven topology. Show which services publish and which consume. -->

```mermaid
graph LR
    subgraph "Publishers"
        UserService["user-service"]
        OrderService["order-service"]
        PaymentService["payment-service"]
    end

    subgraph "Message Broker"
        Broker["{{Kafka / RabbitMQ / AWS SQS + SNS}}"]
    end

    subgraph "Consumers"
        NotifService["notification-service"]
        AnalyticsService["analytics-service"]
        SearchService["search-service"]
        AuditService["audit-service"]
    end

    UserService -->|user.* events| Broker
    OrderService -->|order.* events| Broker
    PaymentService -->|payment.* events| Broker

    Broker -->|filtered| NotifService
    Broker -->|all events| AnalyticsService
    Broker -->|entity events| SearchService
    Broker -->|all events| AuditService
```

**Event-driven use cases in this system:**
- Decoupled notifications (user.created → send welcome email)
- Search index updates (entity.updated → reindex)
- Audit trail (all mutations → audit log)
- Cross-service data sync (order.created → update inventory)

---

## 2. Message Broker Configuration

<!-- GUIDANCE: Document the broker technology, topic/queue naming, and infrastructure setup. -->

**Broker:** `{{Apache Kafka | RabbitMQ | AWS SQS/SNS | NATS | Upstash Kafka}}`
**Version:** `{{3.x}}`
**Hosting:** `{{Confluent Cloud / self-hosted / AWS MSK}}`

### Topic / Queue Naming Convention

```
{{DOMAIN}}.{{ENTITY}}.{{ACTION}}

Examples:
  user.user.created
  order.order.status_changed
  payment.invoice.generated
  notification.email.sent
```

**Pattern rules:**
- All lowercase, dot-separated
- Domain prefix = service name (without `-service`)
- Entity = singular noun
- Action = past tense verb (created, updated, deleted, completed)

### Topic Configuration

| Topic | Partitions | Replication | Retention | Compaction |
|-------|-----------|-------------|-----------|------------|
| `user.user.*` | 6 | 3 | 7 days | No |
| `order.order.*` | 12 | 3 | 30 days | No |
| `payment.invoice.*` | 6 | 3 | 90 days | No |
| `*.*.deleted` | 6 | 3 | 30 days | Log compaction |

---

## 3. Event Naming Conventions

| Component | Rule | Examples |
|-----------|------|---------|
| Full event type | `{domain}.{entity}.{action}` | `user.user.created` |
| Domain | Lowercase, matches service prefix | `user`, `order`, `payment` |
| Entity | Singular noun, lowercase with underscores | `user`, `order_item`, `invoice` |
| Action | Past-tense verb, lowercase with underscores | `created`, `updated`, `status_changed`, `payment_failed` |

**Do NOT use:**
- Present tense (`user.user.create` — wrong)
- Generic names (`user.user.changed` — too vague)
- Abbreviations (`usr.usr.crtd` — unreadable)

---

## 4. Event Envelope Format (CloudEvents 1.0)

<!-- GUIDANCE: ALL events must follow this envelope. The `data` field contains the domain payload. -->

```json
{
  "specversion": "1.0",
  "type": "{{DOMAIN}}.{{ENTITY}}.{{ACTION}}",
  "source": "{{SERVICE_NAME}}",
  "id": "evt_01HX7M2K5N3P4Q5R6S7T8V9W0",
  "time": "2024-01-15T10:30:00.000Z",
  "datacontenttype": "application/json",
  "subject": "{{optional: entity ID}}",
  "data": {
    "{{field}}": "{{value}}"
  }
}
```

**Envelope fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `specversion` | string | Yes | Always `"1.0"` |
| `type` | string | Yes | Event type (see naming convention) |
| `source` | string | Yes | Emitting service name |
| `id` | string | Yes | Unique event ID (ULID format) |
| `time` | string | Yes | ISO 8601 timestamp (UTC) |
| `datacontenttype` | string | Yes | Always `"application/json"` |
| `subject` | string | No | Primary entity ID (for routing) |
| `data` | object | Yes | Domain-specific event payload |

**TypeScript interface:**
```ts
interface CloudEvent<T = Record<string, unknown>> {
  specversion: '1.0';
  type: string;
  source: string;
  id: string;
  time: string;
  datacontenttype: 'application/json';
  subject?: string;
  data: T;
}
```

---

## 5. Per-Event Documentation

<!-- GUIDANCE: Add one subsection per event type. Group by publisher service. -->

---

### 5.1 User Service Events

#### user.user.created

Published when a new user account is created.

| Property | Value |
|----------|-------|
| **Publisher** | `user-service` |
| **Consumers** | `notification-service`, `analytics-service`, `audit-service` |
| **Topic** | `user.user.created` |
| **Ordering guarantee** | Per user ID (partitioned by subject) |
| **Idempotency key** | `id` (event ID) — consumers must deduplicate |
| **Retry behavior** | Consumer retries up to 5x before DLQ |

**Payload schema (JSON Schema):**
```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["userId", "email", "name", "role", "createdAt"],
  "properties": {
    "userId": { "type": "string", "format": "uuid" },
    "email": { "type": "string", "format": "email" },
    "name": { "type": "string", "minLength": 1 },
    "role": { "type": "string", "enum": ["admin", "user", "viewer"] },
    "createdAt": { "type": "string", "format": "date-time" }
  }
}
```

**Example event:**
```json
{
  "specversion": "1.0",
  "type": "user.user.created",
  "source": "user-service",
  "id": "evt_01HX7M2K5N3P4Q5R6S7T8V9W0",
  "time": "2024-01-15T10:30:00.000Z",
  "datacontenttype": "application/json",
  "subject": "usr_01HX7...",
  "data": {
    "userId": "usr_01HX7...",
    "email": "newuser@example.com",
    "name": "Jane Doe",
    "role": "user",
    "createdAt": "2024-01-15T10:30:00.000Z"
  }
}
```

---

#### user.user.updated

Published when user profile data changes.

| Property | Value |
|----------|-------|
| **Publisher** | `user-service` |
| **Consumers** | `search-service`, `notification-service`, `analytics-service` |
| **Topic** | `user.user.updated` |
| **Ordering guarantee** | Per user ID |
| **Idempotency key** | `id` (event ID) |

**Payload schema:**
```json
{
  "type": "object",
  "required": ["userId", "updatedFields", "updatedAt"],
  "properties": {
    "userId": { "type": "string" },
    "updatedFields": {
      "type": "array",
      "items": { "type": "string" },
      "description": "List of field names that changed"
    },
    "before": { "type": "object", "description": "Previous values (only changed fields)" },
    "after": { "type": "object", "description": "New values (only changed fields)" },
    "updatedAt": { "type": "string", "format": "date-time" }
  }
}
```

**Example event:**
```json
{
  "specversion": "1.0",
  "type": "user.user.updated",
  "source": "user-service",
  "id": "evt_01HX8...",
  "time": "2024-01-16T08:00:00.000Z",
  "datacontenttype": "application/json",
  "subject": "usr_01HX7...",
  "data": {
    "userId": "usr_01HX7...",
    "updatedFields": ["name"],
    "before": { "name": "Jane Doe" },
    "after": { "name": "Jane Smith" },
    "updatedAt": "2024-01-16T08:00:00.000Z"
  }
}
```

---

#### user.user.deleted

Published when a user account is soft-deleted.

| Property | Value |
|----------|-------|
| **Publisher** | `user-service` |
| **Consumers** | `order-service`, `notification-service`, `analytics-service` |
| **Payload** | `{ userId, deletedAt, reason }` |
| **Ordering guarantee** | Per user ID |

**Example event:**
```json
{
  "specversion": "1.0",
  "type": "user.user.deleted",
  "source": "user-service",
  "id": "evt_01HX9...",
  "time": "2024-01-17T12:00:00.000Z",
  "datacontenttype": "application/json",
  "subject": "usr_01HX7...",
  "data": {
    "userId": "usr_01HX7...",
    "deletedAt": "2024-01-17T12:00:00.000Z",
    "reason": "user_requested"
  }
}
```

---

### 5.2 Order Service Events

#### order.order.created

| Property | Value |
|----------|-------|
| **Publisher** | `order-service` |
| **Consumers** | `payment-service`, `notification-service`, `inventory-service` |
| **Topic** | `order.order.created` |
| **Ordering guarantee** | Per order ID |

**Payload schema:**
```json
{
  "type": "object",
  "required": ["orderId", "userId", "items", "total", "currency", "createdAt"],
  "properties": {
    "orderId": { "type": "string" },
    "userId": { "type": "string" },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "productId": { "type": "string" },
          "quantity": { "type": "integer" },
          "unitPrice": { "type": "number" }
        }
      }
    },
    "total": { "type": "number" },
    "currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
    "createdAt": { "type": "string", "format": "date-time" }
  }
}
```

---

### 5.3 {{DOMAIN}} Service Events

<!-- GUIDANCE: Add domain sections following the same pattern for each publishing service. -->

#### {{domain}}.{{entity}}.{{action}}

| Property | Value |
|----------|-------|
| **Publisher** | `{{service-name}}` |
| **Consumers** | `{{consumer-a, consumer-b}}` |
| **Topic** | `{{domain.entity.action}}` |
| **Ordering guarantee** | `{{Per entity ID | No guarantee}}` |
| **Idempotency key** | `{{id}}` |

**Payload schema:** `TODO: Define JSON Schema`

**Example event:** `TODO: Add example`

---

## 6. Dead Letter Queue Handling

<!-- GUIDANCE: Define how failed messages are handled after max retries. -->

**DLQ naming:** `{{topic}}.dlq` — e.g., `user.user.created.dlq`

**DLQ workflow:**
```
Event Published
    ↓
Consumer processes
    ↓ Fails
Retry (exp. backoff: 1s, 2s, 4s, 8s, 16s — max 5 retries)
    ↓ All retries exhausted
Move to DLQ
    ↓
Alert fires: PagerDuty P3
    ↓
On-call investigates
    ↓
Option A: Fix consumer bug → replay from DLQ
Option B: Skip message (data was invalid) → log + discard
```

**DLQ message format:**
```json
{
  "originalEvent": { /* original CloudEvent */ },
  "failureReason": "Consumer threw: Cannot read property 'id' of undefined",
  "attemptCount": 5,
  "firstFailedAt": "2024-01-15T10:30:00Z",
  "lastFailedAt": "2024-01-15T10:32:00Z",
  "consumerGroup": "notification-service-consumer"
}
```

**DLQ retention:** 14 days.
**DLQ alert threshold:** > 10 messages in DLQ within 5 minutes.

---

## 7. Event Versioning Strategy

<!-- GUIDANCE: Define how event schemas evolve without breaking consumers. -->

**Strategy:** Backward-compatible field addition + major version in event type.

**Rules:**
1. Adding optional fields: allowed without version bump
2. Removing fields: NOT allowed (use deprecation first, remove after all consumers updated)
3. Changing field types: NOT allowed (breaking change)
4. Adding required fields: requires version bump
5. Major breaking change: new event type `user.user.created.v2`

**Deprecation process:**
```
1. Mark field as deprecated in schema docs
2. Notify all consumer teams
3. Wait 2 sprint cycles (4 weeks minimum)
4. Remove field from schema
5. Update documentation
```

**Schema registry:** `{{Confluent Schema Registry | AWS Glue Schema Registry | Manual docs}}`
**Validation:** Consumer validates incoming events against pinned schema version.

---

## 8. Event Replay Capability

<!-- GUIDANCE: Define whether and how events can be replayed. -->

**Replay supported:** `{{Yes — Kafka log retention | No — events are ephemeral}}`

**Replay scenarios:**
- Bug in consumer → fix bug → replay affected time window
- New consumer onboarded → replay historical events to build initial state
- Data migration → replay events to new storage

**Replay procedure:**
1. Identify topic and time range to replay
2. Coordinate with all consumer teams (replay may cause duplicate side effects)
3. Ensure consumers are idempotent before replay
4. Set consumer offset to target timestamp: `kafka-consumer-groups --reset-offsets --to-datetime`
5. Restart consumer with temporary consumer group to avoid affecting production offset
6. Verify replayed state is correct
7. Switch production consumer to new state

**Retention periods by topic:** See topic configuration table in Section 2.

---

## 9. Monitoring & Observability

<!-- GUIDANCE: Define what is monitored in the event pipeline. -->

| Metric | Alert Threshold | Severity | Channel |
|--------|----------------|----------|---------|
| Consumer lag (per topic) | > 10,000 messages | P2 | Slack `#alerts` |
| DLQ depth | > 10 messages / 5min | P3 | Slack `#alerts` |
| Producer error rate | > 1% / 5min | P1 | PagerDuty |
| Consumer error rate | > 5% / 5min | P2 | PagerDuty |
| Event processing latency P99 | > 5 seconds | P3 | Slack `#alerts` |

**Dashboard:** `{{https://monitoring.domain.com/dashboards/events}}`
**Distributed tracing:** All events carry `traceparent` header (OpenTelemetry W3C Trace Context).

---

## 10. Testing Event-Driven Flows

<!-- GUIDANCE: Define the testing approach for event producers and consumers. -->

### Unit Tests

```ts
// Test producer: verify event shape
it('should publish user.created event with correct schema', async () => {
  await userService.create(createUserDto);

  expect(eventBus.publish).toHaveBeenCalledWith(
    expect.objectContaining({
      type: 'user.user.created',
      source: 'user-service',
      data: expect.objectContaining({
        userId: expect.any(String),
        email: createUserDto.email,
      }),
    })
  );
});

// Test consumer: verify handler idempotency
it('should not send welcome email twice for duplicate event', async () => {
  const event = buildUserCreatedEvent();
  await handler.handle(event);
  await handler.handle(event); // duplicate
  expect(emailService.send).toHaveBeenCalledTimes(1);
});
```

### Integration Tests

```ts
// Use real broker in integration tests (testcontainers)
const kafka = await new KafkaContainer('confluentinc/cp-kafka:7.5.0').start();
```

### E2E Tests

Test full event chain: API action → event published → consumer processes → side effect visible.

```
POST /users → poll for welcome email (SendGrid sandbox) → assert received within 5s
```

---

## Approval
| Role | Name | Date | Signature |
|------|------|------|-----------|
| Author | | | |
| Backend Lead | | | |
| Platform / Infrastructure Lead | | | |
| Architect | | | |