ADR-002: Replacement of CQRS/CommandBus with Orchestrator¶
Status¶
Accepted
Date¶
2025-01-15
Context¶
The original backend implemented the CQRS (Command Query Responsibility Segregation) pattern with a CommandBus to process user actions.
Identified problems:¶
- Over-engineering: For a system with ~10 commands, CQRS overhead was disproportionate
- Excessive indirection: Command → CommandBus → Handler → Result → Response
- Boilerplate: Each new feature required creating Command DTO + Handler + registering in bus
- Complex debugging: Deep stack traces obscured the real flow
- Verbose testing: CommandBus mocks in every test
Requirements:¶
- Maintain separation of concerns (don't couple API to infrastructure)
- Simplify control flow
- Reduce boilerplate for new features
- Facilitate testing and debugging
Decision¶
Replace CQRS/CommandBus with a central Orchestrator that coordinates workflow directly.
Implementation:¶
class Orchestrator:
async def toggle(self) -> ToggleResponse: ...
async def start(self) -> ToggleResponse: ...
async def stop(self) -> ToggleResponse: ...
The Orchestrator:
- Exposes direct methods for each operation
- Coordinates adapters (AudioRecorder, WhisperAdapter, LLMProvider)
- Maintains system state
- Emits events to WebSocket clients
Consequences¶
Positive¶
- ✅ Explicit flow: API → Orchestrator → Adapters (3 layers vs 6+)
- ✅ Less code: Eliminated ~500 LOC of CommandBus infrastructure
- ✅ Simple debugging: Clear and short stack traces
- ✅ Direct testing: Mock adapters, not abstract buses
- ✅ Onboarding: New devs understand the system in minutes
Negative¶
- ⚠️ Less extensible: Adding a global "middleware" is less trivial
- ⚠️ Orchestrator "god object": Risk of growing too large (mitigated with composition)
Alternatives Considered¶
Keep simplified CQRS¶
- Rejected: Even simplified, the conceptual overhead wasn't justified.
Event Sourcing¶
- Rejected: Even greater over-engineering for current use case.
Simple Functions (no class)¶
- Rejected: Lost state management and lifecycle.