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.