Event Sourcing for CRUD
We implemented event sourcing for a basic CRUD app because "it's the right way." Spent 6 months on infrastructure instead of features.
The app requirements:
- User management CRUD
- 500 daily users
- No audit requirements
- No time-travel needed
- Standard business logic
What we built:
- Event store on Kafka + PostgreSQL — itself a questionable call: Kafka as an event store gives you no per-aggregate reads and awkward retention semantics, which cost us extra workarounds
- Event replay system
- Projections (read models)
- Snapshotting
- Versioned events
- Saga orchestration
Time spent:
- Infrastructure: 4 months
- Event schema design: 1 month
- Debugging eventual consistency: 1 month
- Actual business features: 2 weeks
The consistency-debugging month, in one example: a user changed their email, the confirmation screen still showed the old address because the read model lagged, they retried — and we spent two days tracing duplicate EmailChanged events through a projection rebuild. For a form that updates one row.
What we needed:
class User:
def update(self, data):
self.name = data.name
self.email = data.email
db.save(self)
When event sourcing makes sense:
- Regulatory audit trail requirements
- Financial transactions
- Time-travel debugging
- Complex domain with many state transitions
Lesson: Event sourcing is powerful. It's also expensive. Match complexity to requirements — the same instinct that leads small teams into microservices they don't need.