Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

Subscribe to the newsletter

Get new articles and course lessons delivered to your inbox. No spam, unsubscribe anytime.

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 58 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 27, 20264 min read

Event Sourcing Concepts: Building Immutable Data Streams in Laravel

Master event sourcing concepts in Laravel. Learn to move beyond CRUD by using immutable event streams and projections to reconstruct domain state reliably.

LaravelEvent SourcingDomain Driven DesignArchitecturePatternsphpbackend

Previously in this course, we explored Introduction to Laravel Events and Listeners for Clean Code, where we used events to trigger side effects like sending emails or updating secondary tables. In this lesson, we shift our perspective from using events as notifications to using them as the source of truth.

Understanding Event Sourcing Principles

In a standard CRUD (Create, Read, Update, Delete) application, you store the current state of an object. If you update a task's title, the old title is gone, overwritten in the database.

Event sourcing reverses this. Instead of storing the current state, you store a sequence of immutable events—the "Event Stream"—that describe every change that has ever occurred. To find the current state of a task, you "replay" these events from the beginning.

ConceptTraditional CRUDEvent Sourcing
StorageCurrent state onlySequence of events
HistoryUsually lost or requires logsAlways available (reconstructible)
ImmutabilityMutable rowsAppend-only events
ComplexityLowHigh (requires projections)

Defining Events and Streams

An event is a statement of fact about something that happened in the past. In our project board, instead of a tasks table with a status column, we might have events like TaskCreated, TaskStatusChanged, and TaskAssigned.

An event stream is the ordered list of all events associated with a specific aggregate (e.g., one specific Task).

Implementing a Simple Event Projection

A projection is the process of taking an event stream and "projecting" it into a read model. This is the state we actually display to the user.

Let's implement a basic projection for a Task entity.

PHP
#6A9955">// 1. The Event(Fact)
class TaskStatusChanged
{
    public function __construct(
        public readonly int $taskId,
        public readonly string $newStatus,
        public readonly DateTimeImmutable $occurredAt
    ) {}
}

#6A9955">// 2. The Projection(Reconstructing State)
class TaskProjector
{
    public function project(int $taskId, array $events): array
    {
        $state = ['status' => 'pending'];

        foreach ($events as $event) {
            if ($event instanceof TaskStatusChanged) {
                $state['status'] = $event->newStatus;
            }
        }

        return $state;
    }
}

By storing these events in a table (e.g., event_store), you can replay them to reconstruct the state of any task at any point in time. This is why API Architecture Audit Logs: Implementing Immutable Event Sourcing is a natural pattern here; the audit log is your database.

Hands-on Exercise: Projecting Task Completion

In your project board, create a new TaskCompleted event.

  1. Create a class TaskCompleted that holds the taskId and completedAt.
  2. Update the TaskProjector to handle this event: if a TaskCompleted event is encountered, set the status to 'completed' and record the completed_at timestamp in your $state array.
  3. Test this by passing an array of [TaskCreated, TaskStatusChanged, TaskCompleted] into your projector and asserting that the resulting array reflects the final completed status.

Common Pitfalls

  • Event Schema Evolution: If you rename a property in an event class, old events in your database will break your projector. Always keep your event classes stable or implement versioning/upcasting logic.
  • Performance: Replaying thousands of events every time you load a page is slow. Use "Snapshots"—periodically save the state of an aggregate to a table so you only have to replay events since the last snapshot.
  • Eventual Consistency: Since the read model is built from events, it may lag slightly behind the event store. Ensure your UI handles this, perhaps by optimistic updates.

Advancing the Running Project

For our project board, we will now prepare to store these events. Create a migration for an event_store table:

PHP
Schema::create('event_store', function (Blueprint $table) {
    $table->id();
    $table->string('aggregate_type'); #6A9955">// e.g., 'Task'
    $table->string('aggregate_id');
    $table->string('event_type');
    $table->json('payload');
    $table->timestamp('created_at');
});

This table will eventually hold the history of our tasks, providing the foundation for the complex features we'll build later.

Recap

We've moved from storing "what is" to "what happened." By using immutable events as the source of truth and projections to calculate current state, we gain an audit trail by default and a flexible way to change our business logic without losing historical context.

Up next: We will explore how to manage job chains to ensure our projections are updated reliably in the background, keeping our read models in sync with our event store.

Previous lessonSecure API Design
Back to Blog

Similar Posts

LaravelJune 28, 20263 min read

Handling Webhooks Securely: Validation and Queueing in Laravel

Learn to build production-ready integrations by validating webhook signatures and offloading processing to queues to ensure security and system reliability.

Read more
LaravelJune 28, 20264 min read

Advanced Database Migration Strategies for Laravel

Master non-breaking migrations and safe rollback procedures. Learn the expand-and-contract pattern to evolve your database schema without production downtime.

Part of the course

Intermediate Laravel: Real-World Application Patterns

intermediate · Lesson 58 of 58

  1. 1

    Architecting for Maintainability

    3 min
  2. 2

    Implementing the Service Layer

    3 min
  3. 3

    Repository Pattern Fundamentals

    3 min
Read more
LaravelJune 28, 20264 min read

Advanced API Versioning Strategies: Header-Based Routing in Laravel

Master API versioning and maintain backward compatibility in your distributed systems. Learn to implement header-based versioning for clean, scalable APIs.

Read more
  • 4

    Project Board Domain Modeling

    3 min
  • 5

    Advanced Eloquent Scopes and Accessors

    4 min
  • 6

    Service-Oriented Task Management

    3 min
  • 7

    REST API Fundamentals with Sanctum

    3 min
  • 8

    Resource Controllers and API Responses

    3 min
  • 9

    Handling API Validation and Form Requests

    3 min
  • 10

    Implementing Middleware for API Security

    4 min
  • 11

    Database Transactions for Data Integrity

    3 min
  • 12

    Error Handling and Global Exceptions

    3 min
  • 13

    Introduction to Laravel Events and Listeners

    3 min
  • 14

    Asynchronous Processing with Queues

    4 min
  • 15

    Job Chaining and Batching

    3 min
  • 16

    Feature Testing Fundamentals

    4 min
  • 17

    Mocking Services and Repositories in Tests

    3 min
  • 18

    Testing Events and Jobs

    3 min
  • 19

    Database Factories and Seeding

    3 min
  • 20

    API Versioning Strategies

    4 min
  • 21

    Advanced Request Filtering and Sorting

    3 min
  • 22

    Handling File Uploads in REST APIs

    3 min
  • 23

    Real-time Notifications with Broadcasting

    3 min
  • 24

    Using Observers for Model Lifecycle Hooks

    3 min
  • 25

    Implementing Policies for Authorization

    3 min
  • 26

    Customizing Authentication Guards

    3 min
  • 27

    Rate Limiting API Endpoints

    4 min
  • 28

    Eloquent Performance Optimization

    4 min
  • 29

    Caching Strategies for Performance

    4 min
  • 30

    Using Traits for Code Reuse

    3 min
  • 31

    Advanced Dependency Injection with Service Providers

    3 min
  • 32

    Command Line Tools with Artisan

    3 min
  • 33

    Scheduled Tasks and Cron Jobs

    3 min
  • 34

    Integrating Third-Party Services

    3 min
  • 35

    Handling Webhooks

    3 min
  • 36

    Logging and Monitoring

    3 min
  • 37

    Database Migrations Best Practices

    3 min
  • 38

    Advanced Testing: Integration Tests

    4 min
  • 39

    Testing API Authentication

    4 min
  • 40

    Code Quality and Static Analysis

    3 min
  • 41

    Project Structure for Large Applications

    3 min
  • 42

    Environment and Configuration Management

    3 min
  • 43

    Deploying Laravel Applications

    4 min
  • 44

    Database Indexing Strategies

    4 min
  • 45

    Using Value Objects

    4 min
  • 46

    Strategy Pattern for Business Rules

    3 min
  • 47

    Advanced Queue Monitoring

    3 min
  • 48

    Building a Search API

    3 min
  • 49

    Handling Concurrency and Race Conditions

    4 min
  • 50

    API Documentation with OpenAPI

    3 min
  • 51

    Testing with Test Doubles

    3 min
  • 52

    Implementing Multi-Tenancy

    4 min
  • 53

    Refactoring Legacy Code

    4 min
  • 54

    Using Middleware for Feature Flags

    3 min
  • 55

    Building Reusable Packages

    4 min
  • 56

    Performance Profiling

    3 min
  • 57

    Secure API Design

    3 min
  • 58

    Event Sourcing Concepts

    4 min
  • View full course