Skip to content

malogajski/todo-stream

Repository files navigation

TodoStream - Event Sourcing Demo with Laravel

A fully interactive Laravel application demonstrating Event Sourcing using Spatie Event Sourcing package. All Event Sourcing functionality is accessible through the browser interface - no terminal commands needed for everyday use.

Features

  • Interactive Livewire UI - All functionality available through browser
  • Event Sourcing - All state changes stored as immutable events
  • Event Replay - Rebuild entire application state from event history (via browser button)
  • Time Travel - Navigate through event history step-by-step and view historical application states
  • Event History Viewer - View all events for any todo item
  • Clear Projection - Test event replay by clearing and rebuilding the read model
  • IP-based Limits - Maximum 3 active todos per IP address per hour
  • Real-time Statistics - Track number of events and todos in real-time
  • Restore Deleted Todos - Soft delete with ability to restore
  • Modern UI - Clean Tailwind CSS interface
  • Code Quality Tools - Built-in Pint and Larastan integration via php artisan qa

Tech Stack

  • Laravel 12
  • Livewire 3
  • Spatie Event Sourcing
  • SQLite
  • Tailwind CSS

Installation

Quick Setup

# Install dependencies (if needed)
composer install

# Setup database
php artisan migrate:fresh --seed

# Build read model from events
php artisan todo:replay

# Start server
php artisan serve

Open browser at: http://127.0.0.1:8000

Usage

Creating Todos

  1. Enter todo title in the input field
  2. Click "Add Todo" button
  3. Todo is created and TodoCreated event is stored

Completing Todos

  1. Click "Complete" button on any pending todo
  2. Status changes to completed
  3. TodoCompleted event is stored

Reopening Todos

  1. Click "Reopen" button on any completed todo
  2. Status reverts to pending
  3. TodoReopened event is stored

Deleting Todos

  1. Click "Delete" button on any todo
  2. Confirm deletion
  3. Status changes to deleted (soft delete)
  4. TodoDeleted event is stored

Restoring Deleted Todos

  1. Deleted todos appear in trash section
  2. Click "Restore" button on any deleted todo
  3. Status reverts to its previous state (pending/completed)
  4. TodoRestored event is stored

Viewing Event History

  1. Click "Event History" link on any todo
  2. Modal opens showing all events for that todo
  3. View event types, timestamps, and JSON data

Event Replay (Key Feature)

  1. Click "Replay All Events" button in sidebar
  2. Application performs:
    • Truncates the todos table (read model)
    • Loads all events from stored_events table
    • Replays them through the projector
    • Reconstructs complete application state

This demonstrates the power of Event Sourcing - you can rebuild your entire application state from the event log at any time.

Clear Projection (Testing)

  1. Click "Clear Projection" button
  2. All todos disappear from the read model
  3. Events remain safe in the event store
  4. Use "Replay Events" to restore everything

Time Travel Mode

  1. Click "Time Travel" button in sidebar
  2. Modal opens with slider control
  3. Drag slider to navigate through event history
  4. Watch todos change to reflect historical state
  5. Use step forward/backward buttons for precise navigation
  6. Application enters read-only mode during time travel
  7. Click "Exit Time Travel" to return to live state
  8. All modifications are blocked while viewing historical snapshots

Time Travel Features:

  • Slider control for navigating all events
  • Real-time preview of historical state
  • Read-only mode prevents accidental modifications
  • Visual indicator showing current event position
  • Step-by-step navigation buttons

Architecture

Event Sourcing Components

Events

  • TodoCreated - New todo created with title and IP
  • TodoCompleted - Todo marked as completed
  • TodoReopened - Completed todo reopened
  • TodoDeleted - Todo soft deleted
  • TodoRestored - Deleted todo restored to previous state

Aggregate

  • TodoAggregate - Aggregate Root handling business logic and recording events

Projector

  • TodoProjector - Listens to events and updates the todos read model table

Commands

  • CreateTodo - Creates new todo and records event
  • CompleteTodo - Marks todo complete and records event
  • ReopenTodo - Reopens todo and records event
  • DeleteTodo - Deletes todo and records event
  • RestoreTodo - Restores deleted todo and records event

Read Model

  • Todo - Eloquent model representing the projection (todos table)

Enum

  • TodoStatus - pending, completed, deleted

Data Flow

User Action (Browser)
    ↓
Livewire Component Method
    ↓
Command Handler
    ↓
Aggregate Root (records event)
    ↓
Event Store (stored_events table)
    ↓
Projector (handles event)
    ↓
Read Model (todos table)
    ↓
Livewire Re-render
    ↓
Browser Update

Project Structure

app/
├── Aggregates/
│   └── TodoAggregate.php          # Aggregate Root with business logic
├── Commands/
│   ├── CreateTodo.php
│   ├── CompleteTodo.php
│   ├── ReopenTodo.php
│   ├── DeleteTodo.php
│   └── RestoreTodo.php
├── Console/Commands/
│   ├── TodoReplayCommand.php      # php artisan todo:replay
│   ├── TodoRollbackCommand.php    # php artisan todo:rollback {uuid}
│   └── CheckCodeQualityCommand.php # php artisan qa
├── Enums/
│   └── TodoStatus.php             # pending | completed | deleted
├── Events/
│   ├── TodoCreated.php
│   ├── TodoCompleted.php
│   ├── TodoReopened.php
│   ├── TodoDeleted.php
│   └── TodoRestored.php
├── Livewire/
│   └── TodoList.php               # Main Livewire component
├── Models/
│   └── Todo.php                   # Read model (projection)
└── Projectors/
    └── TodoProjector.php          # Event handlers updating read model

database/
├── migrations/
│   ├── create_todos_table.php
│   ├── create_stored_events_table.php
│   └── create_snapshots_table.php
└── seeders/
    └── TodoSeeder.php             # Sample data (5 todos, 7 events)

Database Schema

todos (Read Model / Projection)

  • id - Primary key
  • uuid - Aggregate UUID (links to events)
  • title - Todo title
  • status - Current status (pending/completed/deleted)
  • ip - IP address of creator
  • timestamps

stored_events (Event Store)

  • id - Auto increment
  • aggregate_uuid - Links to todos.uuid
  • event_class - Fully qualified event class name
  • event_properties - JSON serialized event data
  • meta_data - Additional metadata
  • created_at - Event timestamp

Artisan Commands

Replay All Events

php artisan todo:replay

Truncates the todos table and rebuilds it from all stored events. Demonstrates event replay capability.

Rollback Last Event

php artisan todo:rollback {uuid}

Removes the last event for a specific todo UUID and replays remaining events to restore correct state.

Code Quality Check

php artisan qa

Runs Laravel Pint (code style fixer) and Larastan (static analysis) to ensure code quality. This command:

  • Automatically fixes code style issues with Pint
  • Runs PHPStan/Larastan static analysis
  • Returns success/failure status

Testing Event Sourcing

Scenario 1: Normal Usage

  1. Create several todos through the UI
  2. Complete some of them
  3. Click "Event History" on any todo
  4. Observe all events with timestamps

Scenario 2: Event Replay

  1. Create multiple todos with different statuses
  2. Note the current state
  3. Click "Clear Projection" - all todos disappear
  4. Click "Replay All Events" - everything returns exactly as it was
  5. This proves the event store is the source of truth

Scenario 3: IP Limiting

  1. Create 3 todos
  2. Try to create a 4th todo within the same hour
  3. Receive error: "Maximum 3 todos per IP per hour allowed"
  4. Wait one hour OR delete one todo
  5. Now you can create another

Scenario 4: Time Travel

  1. Create multiple todos and perform various actions (complete, reopen, delete)
  2. Click "Time Travel" button
  3. Drag slider to earlier event (e.g., event #3)
  4. Observe state exactly as it was at that point in time
  5. Try to create a new todo - blocked with read-only message
  6. Navigate forward/backward through events
  7. Click "Exit Time Travel" to return to live mode

Scenario 5: Restore Deleted Todos

  1. Delete a todo
  2. Todo moves to trash section
  3. Click "Restore" button
  4. Todo returns to its previous state (pending or completed)
  5. View event history to see TodoRestored event recorded

Key Event Sourcing Concepts Demonstrated

Immutability

  • Events are never modified or deleted
  • New events are added to represent state changes
  • Complete audit trail of all changes

Event Replay

  • Read model can be completely rebuilt from events
  • Enables time travel and state reconstruction at any point in history
  • Step-by-step navigation through all historical states
  • Protects against data corruption

CQRS (Command Query Responsibility Segregation)

  • Write model: Commands → Aggregates → Events
  • Read model: Events → Projectors → Database Table
  • Separate concerns for writing and reading data

Aggregates

  • TodoAggregate enforces business rules
  • Records events representing state changes
  • Prevents invalid state transitions

Projections

  • TodoProjector builds queryable read model
  • Can have multiple projections from same events
  • Optimized for specific query patterns

UI Features

The browser interface provides:

  • Todo list with status badges (Pending/Completed/Deleted)
  • Add, Complete, Reopen, Delete, and Restore buttons
  • Trash section showing deleted todos with restore capability
  • Event History modal for each todo showing all events with timestamps
  • Time Travel interface with:
    • Interactive slider for navigating event history
    • Step forward/backward buttons
    • Read-only mode indicator during time travel
    • Current event position display
  • Event Sourcing control panel with:
    • Time Travel button
    • Replay All Events button
    • Clear Projection button
    • Real-time statistics (event count, todo count)
  • Success/error flash messages
  • IP address display
  • Total events counter

Development

Adding New Events

  1. Create event class in app/Events
  2. Add method to TodoAggregate to record it
  3. Add handler method to TodoProjector
  4. Create command class if needed
  5. Wire up in Livewire component

Customizing Projections

The TodoProjector can be modified to:

  • Update multiple tables
  • Trigger notifications
  • Update search indexes
  • Generate analytics

Advanced Features

Event Rollback

While the browser interface provides event replay, you can also rollback individual todos using the console:

php artisan todo:rollback {uuid}

This removes the last event for the specified todo and replays remaining events.

API Access

The application includes fully functional API routes with rate limiting:

  • GET /api/todos - List todos
  • POST /api/todos - Create todo
  • POST /api/todos/{uuid}/toggle - Toggle status
  • DELETE /api/todos/{uuid} - Delete todo

Rate limit: 10 requests per minute

All API endpoints support both JSON and standard responses. Example:

curl -X POST http://127.0.0.1:8000/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "My new todo"}'

Troubleshooting

No todos appear after seed

php artisan todo:replay

Events not projecting Check that TodoProjector method names match event class names:

  • handleTodoCreated for TodoCreated event
  • handleTodoCompleted for TodoCompleted event
  • handleTodoReopened for TodoReopened event
  • handleTodoDeleted for TodoDeleted event
  • handleTodoRestored for TodoRestored event

Database errors

php artisan migrate:fresh --seed
php artisan todo:replay

Code quality issues

php artisan qa

This will automatically fix code style issues and run static analysis.

Credits

Built with Spatie Laravel Event Sourcing

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages