11. Event-Driven Architecture
Status: Accepted Date: 2025-07-06
Context
The Mercury trading system is a complex application with many interconnected components (consumers, services, controllers) that need to communicate and react to state changes. A traditional, synchronous, tightly-coupled communication style would lead to a brittle and unmaintainable monolith. For example, when a new trade signal is generated, multiple independent modules like risk management (Dike), position management, and logging need to react to it without being directly invoked by the signal generator.
Decision
We will implement an event-driven architecture using Node.js's native EventEmitter for in-process communication. A central event bus will be established for decoupled communication between components within a single service. Services will emit named events with payloads (e.g., signal.new, order.filled). Other services will subscribe to these events and execute their logic asynchronously. This decouples the event producer from the event consumers for non-critical, real-time, in-process notifications.
For critical, cross-service, or persistent eventing, the existing BullMQ message queue will be used.
Consequences
Positive:
- Decoupling: Services do not need direct knowledge of each other; they only need to be aware of the events. This reduces coupling and improves modularity.
- Scalability & Flexibility: It is easy to add new consumers for an event without changing the producer, making the system more flexible and extensible.
- Asynchronous Operations: Facilitates non-blocking workflows, which is essential for a high-performance system.
Negative:
- Traceability: The flow of control can be harder to trace compared to a synchronous system, potentially complicating debugging.
- "Event Hell": Without proper governance, an explosion in the number of events can lead to a system that is difficult to understand and manage.
- No Guaranteed Delivery: An in-memory
EventEmitterdoes not guarantee event delivery if the application crashes.
Mitigation:
- Traceability: Implement comprehensive logging with correlation IDs passed through events, allowing a request to be traced across multiple event handlers. Use monitoring tools to visualize event flows.
- Event Governance: Establish and enforce a clear, documented naming convention and a central registry for all events, their payloads, and their purpose.
- Guaranteed Delivery: Use the in-memory
EventEmitteronly for non-critical, in-process communication. For any critical operations that require guaranteed delivery (e.g., order execution), use the existing persistent message queue (BullMQ). This hybrid approach provides both lightweight in-process messaging and robust, reliable messaging where needed.