DEV Community

Alex Aslam
Alex Aslam

Posted on

Event Sourcing vs. CRUD: When 1000 Database Writes Don’t Matter

"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?
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Do we need an audit trail? (Yes → Events)
  2. Is replayability valuable? (Yes → Events)
  3. Are writes high-frequency & low-value? (Yes → CRUD)
  4. 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:

  1. Start by event-sourcing one critical workflow (e.g., payments).
  2. Keep CRUD for low-value data (e.g., user preferences).
  3. 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.