Event-Driven Architecture¶
Background and asynchronous work in dichit-backend is event/queue-driven rather than blocking the request lifecycle.
What runs asynchronously¶
| Concern | Mechanism |
|---|---|
| Email / SMS / push notifications | BullMQ queue processors → mail, SMS, FCM providers |
| Document/PDF rendering | Queue (see src/application/queue/processors) |
| Payment webhook handling | HTTP webhooks (pg/zwitch, sentry, whatsapp) + queues |
| Cross-instance real-time fan-out | Redis Pub/Sub (ws:global) |
| Program/cycle state changes | Queue processors orchestrating side effects |
Queues (BullMQ on Redis)¶
flowchart LR
H["Request handler / webhook"] -->|"add job"| BQ["BullMQ queue (Redis)"]
BQ --> W["Worker (same process, app bootstrap)"]
W --> A["Processor (application/queue/processors)"]
A --> EXT["S3 / mail / SMS / FCM / PG"] - Queue processors and services live in
src/application/queue. - Workers are started from the main app at boot (
app.ts→registerQueueProcessors), unless disabled in tests. - Bull Board is available for monitoring at
/admin/queuesby default (see infrastructure/monitoring.md).
WebSocket pub/sub¶
Real-time events are published through a Redis pub/sub adapter and delivered by every WS instance (see websocket-architecture.md):
sequenceDiagram
participant A as Instance A (publisher)
participant R as Redis (ws:global)
participant B as Instance B (subscriber)
A->>R: publish(event)
R-->>B: deliver event
B->>B: deliverFromPubSub → socket rooms Why async¶
- Keeps HTTP/WS responses fast (< 200 ms target).
- Retry semantics via BullMQ job attempts.
- Decouples providers: mail/SMS/FCM can fail without failing the request.
Best practices¶
- Prefer queues for anything not needed in the response.
- Keep processors idempotent and re-runnable.
- Never log sensitive payloads (OTPs, tokens) in jobs.
- Use the existing queue infrastructure instead of adding schedulers/spawns.
- When a state transition has side effects, put them in a processor, not a controller.