DEV Community

Cover image for Stop Overwriting Your Data: Event Sourcing in Laravel 🕰️
Prajapati Paresh
Prajapati Paresh

Posted on • Originally published at smarttechdevs.in

Stop Overwriting Your Data: Event Sourcing in Laravel 🕰️

The Problem with Traditional State

Most Laravel applications rely on standard CRUD (Create, Read, Update, Delete) architecture. When a user changes their account balance, the old balance in the database is overwritten. But what if you need to know why the balance changed? What if you need to audit the system to find a bug introduced three months ago?

Traditional databases only store current state. They suffer from data amnesia. At Smart Tech Devs, when we build financial ledgers, audit-heavy platforms, or complex enterprise systems, we abandon state-based storage and implement Event Sourcing.

What is Event Sourcing?

Instead of storing the current state of a model, Event Sourcing stores every change that has ever occurred to that model as an immutable sequence of events. The current state is simply calculated by playing all those events from the beginning of time.

Step 1: Storing the Events

Instead of an accounts table with a balance column, we have an events table. When money is deposited, we don't update a balance; we record a MoneyDeposited event.


// ❌ Traditional CRUD Pattern
$account = Account::find(1);
$account->balance += 500;
$account->save();

// ✅ Event Sourced Pattern
$accountAggregate = AccountAggregateRoot::retrieve($uuid);
$accountAggregate->depositMoney(500, 'Salary deposit')
                 ->persist();

Step 2: Projections (The Read Model)

Calculating the balance on the fly by reading thousands of events would be too slow for an API response. This is where Projections come in. A projector listens for events as they happen and updates a fast, read-optimized database table.


namespace App\Projectors;

use App\Events\MoneyDeposited;
use App\Models\AccountProjection;
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;

class AccountBalanceProjector extends Projector
{
    public function onMoneyDeposited(MoneyDeposited $event)
    {
        // This table is purely for fast reading. 
        // The source of truth remains the events table.
        $account = AccountProjection::firstOrCreate(['uuid' => $event->aggregateRootUuid()]);
        $account->balance += $event->amount;
        $account->save();
    }
}

The Engineering ROI

If your projection table gets corrupted, you simply delete it and replay your events to rebuild it perfectly. You gain a 100% accurate audit log, the ability to time-travel your application state to any point in the past, and decoupled read/write architectures (CQRS). It introduces complexity, but for the right domain, it is a superpower.

Top comments (0)