ADR-013: Event-Driven Architecture
Status: Accepted Owner: @bilal @deen Date: 2025-01-13
Context
This ADR presents a simpler alternative to full Event Sourcing (ADR-012). Both address the same problems: incomplete event coverage, no external publishing, AI context service needs, and automation triggers.
The Question
Event Sourcing (ADR-012) makes events the source of truth and rebuilds state from them. Event-Driven (this ADR) keeps Prisma as the source of truth but emits events after mutations succeed.
Decision
Event-Driven Architecture as a pragmatic middle ground — 80% of the benefits with 20% of the complexity.
- Prisma remains the source of truth
- Events emitted after mutations — for audit and external systems
- Events stored in unified
domain_eventstable — replaces issue-specificissue_events - Events published to subscribers — n8n, AI service, etc.
Why Not Full Event Sourcing?
| Factor | Event Sourcing | Event-Driven |
|---|---|---|
| Complexity | High | Medium |
| Migration effort | 3-4 weeks | 1-2 weeks |
| Debugging | Event replay | Standard DB queries |
| Eventual consistency | Yes | No (sync by default) |
| Team familiarity | Low | High |
Can always upgrade to Event Sourcing later if needed.
Architecture
Dashboard (Next.js)
GraphQL Mutations → Prisma Write → Emit Event → Response
↓ ↓
[Source DB] [Events Table]
↓
EVENT DISPATCHER
↙ ↓ ↘
n8n AI Context Future
Workflows Service Services
Unified Events Table
CREATE TABLE domain_events (
id UUID PRIMARY KEY,
entity_type TEXT NOT NULL, -- 'Issue', 'Property', 'Tenant', etc.
entity_id UUID NOT NULL,
event_type TEXT NOT NULL, -- 'IssueCreated', 'PropertyUpdated', etc.
data JSONB NOT NULL,
organisation_id UUID NOT NULL,
user_id UUID,
correlation_id UUID,
source TEXT NOT NULL DEFAULT 'dashboard',
published_at TIMESTAMPTZ,
publish_error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);Event Emitter Service
Singleton EventEmitter class that stores events in DB and publishes to registered publishers (n8n webhook, AI context service) asynchronously. Retry job handles failed publishes.
Event Types
Covers all entities: Issue (created, updated, status changed, deleted, vendor assigned, note added), Property (CRUD), Tenant (CRUD, moved property), Document (CRUD, expiring), Attachment (CRUD), Conversation (started, message received, resolved).
Migration Strategy
- Phase 1 (Day 1-2): Add infrastructure (table, Prisma model, emitter service)
- Phase 2 (Day 3-4): Dual write — Issue mutations emit events, keep old table
- Phase 3 (Day 5-7): Extend to all entities
- Phase 4 (Day 8-10): Connect external systems (n8n, AI context)
- Phase 5 (Day 11-14): Migrate old data, drop
issue_events, update UI
Consequences
Positive
- Complete event coverage for all entities
- External publishing for n8n and AI service
- Unified event model
- Simpler than CQRS, easy migration path
- Full audit trail
Negative
- Events not source of truth (derived from state)
- Potential drift if emit fails (mitigated by retry)
- Extra latency (~5-10ms per mutation)
- Storage growth (needs retention policy)
Related
- ADR-003 Audit Logging Strategy
- ADR-010 Tenant Engine
- ADR-012 Event Sourcing CQRS (superseded alternative)