ADR-0002. In-process WebSocket-only eventing, no Kafka
| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-05-01 |
| Deciders | outreach-team |
| Supersedes | — |
Context
- Most BANA services emit Kafka events for CDC and cross-service choreography.
- Outreach captures inquiries and subscriptions from the public site. The only downstream consumer is the BO dashboard, which wants a real-time toast when a new inquiry arrives.
- No other service needs to react to an inquiry or subscription as a domain event. The durable record is the row in PostgreSQL.
Decision
Outreach ships no Kafka producer and no Kafka consumer, and no BullMQ worker. Its sole async surface is a Redis-backed WebSocketEmitter (outreach-ws-emitter) that broadcasts INQUIRY_SUBMITTED to observation rooms when an inquiry is submitted.
The emission is fire-and-forget: the inquiry is committed to the database first, then OutreachSocketEventService.notifyInquirySubmitted() runs without being awaited, guarded by isReady() and using Promise.allSettled across rooms. A dropped WS event never loses data — the BO can re-fetch via GET /inquiries.
Consequences
| Pros | Cons |
|---|---|
| Minimal infra: no Kafka topic, schema, consumer group, or DLQ to operate | If a future service needs inquiry events, it must poll REST or a Kafka seam must be added |
| Real-time BO UX via Redis fan-out (works across replicas) | WS delivery is best-effort, not durable |
| Simpler service: fewer failure modes, smaller blast radius | Inconsistent with the Kafka-CDC norm elsewhere in the platform |
| Database remains the single source of truth | Analytics on inquiries must read the DB, not an event stream |
Alternatives Considered
| Option | Pros | Cons | Why rejected |
|---|---|---|---|
Emit Kafka inquiry.submitted | Durable, fan-out to any consumer, fits platform norm | Operational weight (topic, consumer, DLQ) for one in-house consumer | No durable consumer exists today |
Debezium CDC on the Inquiry table | Zero app code; captures all writes | Heavy infra for a low-traffic table; couples consumers to schema | Disproportionate to need |
| Await the WS notify in the request | Guarantees notify before 201 | Couples submit latency/availability to Redis health | Submit must succeed even if Redis is down |
References
packages/outreach/src/components/websocket/component.ts(ApplicationWebSocketComponent)packages/outreach/src/components/websocket/socket-event.service.ts(notifyInquirySubmitted)packages/outreach/src/application.ts(no Kafka/BullMQ components configured)- API Events