"Your database is a ledger, not a scrapbook."
Most apps treat data like a blackboard—constantly erasing and rewriting. CRUD (Create, Read, Update, Delete) is simple… until:
- You need to audit changes ("Who set this price to $0?")
- You want to replay history ("How did the system behave last Tuesday?")
- You accidentally overwrite data with no recovery path.
Event Sourcing flips the script: Instead of storing state, you store events that led to that state.
But when does it actually matter? Let’s compare.
1. The Core Difference
CRUD (Traditional Rails)
# Update a user’s balance directly
user.update!(balance: 100)
# Problem: History is lost
# What was the balance before? Who changed it? Why?
Event Sourcing
# Record an event instead
event = BalanceAdjusted.new(
user_id: user.id,
old_balance: user.balance,
new_balance: 100,
reason: "Manual admin adjustment"
)
EventStore.publish(event)
# Rebuild state anytime
current_balance = EventStore.for(user.id).sum(&:amount)
Key Insight:
- CRUD = "What is the balance?"
- Event Sourcing = "Why is the balance 100?"
2. When Event Sourcing Shines
Use Case 1: Financial Systems
- Requirement: Every cent must be auditable.
-
CRUD Risk: An
UPDATE
erases history. - Event Fix: Immutable ledger of all transactions.
Use Case 2: Legal Compliance
- Requirement: Prove data state at any past time.
- CRUD Risk: Hard to reconstruct history.
- Event Fix: Replay events to any timestamp.
Use Case 3: Complex Workflows
- Requirement: Undo/redo actions (e.g., order cancellations).
- CRUD Risk: Rollbacks require manual fixes.
- Event Fix: Reverse events to "rewind" state.
3. When CRUD Wins
Use Case 1: Simple Apps
- Example: Todo list, blog CMS.
- Why Events Overkill? No need for audit trails or replayability.
Use Case 2: High-Speed Writes
- Example: Real-time analytics (thousands of writes/sec).
- Why Events Struggle? Rebuilding state adds latency.
Use Case 3: Static Data
- Example: Product catalog (rarely changes).
- Why Events Wasteful? No value in tracking minor edits.
4. Hybrid Approach: The Best of Both Worlds
Pattern 1: CRUD with Event Logging
class User < ApplicationRecord
after_update :log_balance_change
private
def log_balance_change
if balance_previously_changed?
AuditLog.create!(
event: "balance_change",
old_value: balance_previous_change[0],
new_value: balance
)
end
end
end
Best for: Apps needing lightweight auditing.
Pattern 2: Event Sourcing for Critical Paths
- Example: Use events for payments, CRUD for user profiles. Best for: Reducing complexity where it’s not needed.
Pattern 3: CQRS + Events
- Example: Event-sourced writes, materialized views for reads. Best for: High-scale systems (e.g., e-commerce).
5. Decision Framework
Ask:
- Do we need an audit trail? (Yes → Events)
- Is replayability valuable? (Yes → Events)
- Are writes high-frequency & low-value? (Yes → CRUD)
- Can we tolerate eventual consistency? (Yes → Events)
Rule of Thumb:
Use Event Sourcing when history is part of the business logic, not just a "nice-to-have."
"But ActiveRecord Is So Easy!"
It is—until it isn’t. You don’t have to go all-in:
- Start by event-sourcing one critical workflow (e.g., payments).
- Keep CRUD for low-value data (e.g., user preferences).
- Measure tradeoffs before expanding.
Have you tried mixing CRUD and events? Share your lessons below.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.