$30 off During Our Annual Pro Sale. View Details »

Time traveling with Event Sourcing

Time traveling with Event Sourcing

Introduction to Event Sourcing presented on PHPCon Poland 2016.

Olaf Gałązka

October 01, 2016
Tweet

More Decks by Olaf Gałązka

Other Decks in Programming

Transcript

  1. Time traveling

    with Event Sourcing
    Olaf Gałązka

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. Traditional way
    • CRUD
    • current state based

    View Slide

  6. Current state concept
    • saving only state of all objects
    • no ways to investigate what was happening

    View Slide

  7. Before
    After

    View Slide

  8. Before
    After

    View Slide

  9. Workaround?
    Saving history records into log

    View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. Good solution?

    View Slide

  14. View Slide

  15. Switch your mindset

    View Slide

  16. Focus on actions that
    happen in your
    application

    View Slide

  17. The domain events

    View Slide

  18. The domain event
    • a fact
    • something that happened in the past
    • a state transition

    View Slide

  19. Event = data
    • event keeps all information about itself
    • it’s the main and only source of information
    • think of all data needed

    View Slide

  20. Events naming
    • if events are facts from the past
    • then use verbs
    • and use past tense

    View Slide

  21. Events naming
    • ArticleWasPublished
    • ProductWasAddedToBasket
    • NewUserHasRegistered

    View Slide

  22. View Slide

  23. https://github.com/ulff/phpcon-demo

    View Slide


  24. class PostWasPublished implements DomainEvent

    {

    private $postId;


    private $title;


    private $content;


    private $publishingDate;


    public function __construct(

    PostId $postId,

    $title,

    $content,

    \DateTime $publishingDate

    )

    {

    $this->postId = $postId;

    $this->title = $title;

    $this->content = $content;

    $this->publishingDate = $publishingDate;

    }


    // ...

    }

    View Slide

  25. Storing events

    View Slide

  26. Event storage
    • persistence layer for events
    • append only
    • never ever delete nor update

    View Slide

  27. Stored event
    • event type
    • aggregate id
    • domain event metadata
    • optional useful information, e.g. date

    View Slide

  28. Aggregates
    • objects that are affected by events
    • it’s like a document / entity

    View Slide

  29. Post
    PostWasPublished PostWasUpdated
    Event
    Event
    Aggregate

    View Slide

  30. interface Aggregate

    {

    /**

    * @return DomainEvent[]

    */

    public function getEvents();


    /**

    * @return AggregateId

    */

    public function getAggregateId();


    /**

    * @return Aggregate

    */

    public static function reconstituteFrom(
    AggregateHistory $aggregateHistory
    );

    }

    View Slide

  31. class Post implements Aggregate

    {

    use EventSourced;


    private $postId;

    private $title;

    private $content;

    private $publishingDate;


    public function __construct(PostId $postId) {/**/}


    // getters/setters…

    public function getAggregateId() {/**/}

    public function update($title, $content) {/**/}

    private function applyPostWasPublished(PostWasPublished $event) {/**/}

    private function applyPostWasUpdated(PostWasUpdated $event) {/**/}

    public static function create($title, $content) {/**/}

    public static function reconstituteFrom(
    AggregateHistory $postAggregateHistory
    ) {/**/}

    }

    View Slide

  32. Product
    ProductWasAddedToBasket
    Basket
    What is an aggregate here?
    ?

    View Slide

  33. Product
    ProductWasAddedToBasket
    Basket
    Basket is the affected object

    View Slide

  34. Aggregate ID
    • every aggregate has defined its aggregateID
    • every event has defined aggregateID

    View Slide


  35. class PostWasPublished implements DomainEvent

    {

    private $postId;


    private $title;


    private $content;


    private $publishingDate;


    public function __construct(

    PostId $postId,

    $title,

    $content,

    \DateTime $publishingDate

    )

    {

    $this->postId = $postId;

    $this->title = $title;

    $this->content = $content;

    $this->publishingDate = $publishingDate;

    }


    // ...

    }

    View Slide

  36. class PostWasUpdated implements DomainEvent

    {

    private $postId;


    private $title;


    private $content;


    public function __construct(

    PostId $postId,

    $title,

    $content

    )

    {

    $this->postId = $postId;

    $this->title = $title;

    $this->content = $content;

    }


    // ...

    }

    View Slide

  37. Reconstituting aggregates
    • get aggregate history from the event storage
    • aggregate history is an ordered set of events
    affecting particular aggregate
    • reconstitute aggregate’s state:
    ‣ create new instance of the aggregate class
    ‣ replay events from aggregate history on it

    View Slide

  38. class Post implements Aggregate

    {

    public function __construct(PostId $postId)

    {

    $this->postId = $postId;

    } 


    // ...


    public static function reconstituteFrom(PostAggregateHistory
    $aggregateHistory)

    {

    $post = new self($aggregateHistory->getAggregateId());


    foreach ($aggregateHistory->getEvents() as $event) {

    $applyMethod = explode('\\', get_class($event));

    $applyMethod = 'apply' . end($applyMethod);

    $post->$applyMethod($event);

    }


    return $post;

    }

    }

    View Slide

  39. class Post implements Aggregate

    {

    // ...


    private function applyPostWasPublished(PostWasPublished $event)

    {

    $this->setTitle($event->getTitle());

    $this->setContent($event->getContent());

    $this->setPublishingDate($event->getPublishingDate());

    }


    private function applyPostWasUpdated(PostWasUpdated $event)

    {

    $this->setTitle($event->getTitle());

    $this->setContent($event->getContent());

    }


    // ... 

    }

    View Slide

  40. Reconstituted aggregate
    • use reconstituted object in your operations
    • never save it’s state
    • let the garbage collector remove it

    View Slide

  41. // class Domain\UseCase\UpdatePost

    public function execute(Command $command, Responder $responder)

    {

    $postAggregateHistory = new PostAggregateHistory(

    $command->getPostId(), 

    $this->eventStorage

    );

    $post = Post::reconstituteFrom($postAggregateHistory);


    $post->update(

    $command->getTitle(),

    $command->getContent()

    );


    try {

    $this->eventBus->dispatch($post->getEvents());

    $this->eventStorage->add($post);

    } catch(\Exception $e) {

    $responder->postUpdatingFailed($e);

    }


    $responder->postUpdatedSuccessfully($post);

    }

    View Slide

  42. // class Domain\UseCase\UpdatePost

    public function execute(Command $command, Responder $responder)

    {

    $postAggregateHistory = new PostAggregateHistory(

    $command->getPostId(), 

    $this->eventStorage

    );

    $post = Post::reconstituteFrom($postAggregateHistory);


    $post->update(

    $command->getTitle(),

    $command->getContent()

    );


    try {

    $this->eventBus->dispatch($post->getEvents());

    $this->eventStorage->add($post);

    } catch(\Exception $e) {

    $responder->postUpdatingFailed($e);

    }


    $responder->postUpdatedSuccessfully($post);

    }
    $postAggregateHistory = new PostAggregateHistory(

    $command->getPostId(), 

    $this->eventStorage

    );

    $post = Post::reconstituteFrom($postAggregateHistory);

    View Slide

  43. // class Domain\UseCase\UpdatePost

    public function execute(Command $command, Responder $responder)

    {

    $postAggregateHistory = new PostAggregateHistory(

    $command->getPostId(), 

    $this->eventStorage

    );

    $post = Post::reconstituteFrom($postAggregateHistory);


    $post->update(

    $command->getTitle(),

    $command->getContent()

    );


    try {

    $this->eventBus->dispatch($post->getEvents());

    $this->eventStorage->add($post);

    } catch(\Exception $e) {

    $responder->postUpdatingFailed($e);

    }


    $responder->postUpdatedSuccessfully($post);

    }
    $post->update(

    $command->getTitle(),

    $command->getContent()

    );

    View Slide

  44. class Post implements Aggregate

    {

    use EventSourced;


    // ...

    public function update($title, $content)

    {

    $this->recordThat($event = new PostWasUpdated(
    $this->getAggregateId(),
    $title,
    $content
    ));

    $this->apply($event);

    }


    private function applyPostWasUpdated(PostWasUpdated $event)

    {

    $this->setTitle($event->getTitle());

    $this->setContent($event->getContent());

    }

    // ...

    }

    View Slide

  45. trait EventSourced

    {

    protected $events = [];


    public function getEvents()

    {

    return $this->events;

    }


    protected function recordThat(DomainEvent $event)

    {

    $this->events[] = $event;

    }


    private function apply(DomainEvent $event)

    {

    $method = explode('\\', get_class($event));

    $method = 'apply' . end($method);

    $this->$method($event);

    }


    // ...

    }

    View Slide

  46. // class Domain\UseCase\UpdatePost

    public function execute(Command $command, Responder $responder)

    {

    $postAggregateHistory = new PostAggregateHistory(

    $command->getPostId(), 

    $this->eventStorage

    );

    $post = Post::reconstituteFrom($postAggregateHistory);


    $post->update(

    $command->getTitle(),

    $command->getContent()

    );


    try {

    $this->eventBus->dispatch($post->getEvents());

    $this->eventStorage->add($post->getEvents());

    } catch(\Exception $e) {

    $responder->postUpdatingFailed($e);

    }


    $responder->postUpdatedSuccessfully($post);

    }
    $this->eventBus->dispatch($post->getEvents());

    $this->eventStorage->add($post->getEvents());

    View Slide

  47. Dispatching events

    View Slide

  48. When event was produced
    • it should be saved in the event storage
    • it should be dispatched
    • so any interested listeners could use it

    View Slide

  49. class EventBus

    {

    private $listeners = [];


    public function registerListener(

    DomainEventListener $domainEventListener

    )

    {

    $this->listeners[] = $domainEventListener;

    }


    public function dispatch(array $events)

    {

    foreach ($events as $event) {

    foreach ($this->listeners as $listener) {

    $listener->when($event);

    }

    }

    }

    }

    View Slide

  50. interface DomainEventListener

    {

    /**

    * @param DomainEvent $event

    */

    public function when(DomainEvent $event);

    }

    View Slide

  51. abstract class AbstractDomainEventListener
    implements DomainEventListener

    {

    protected $projectionStorage;


    public function __construct(
    EventBus $eventBus,
    ProjectionStorage $projectionStorage
    )

    {

    $eventBus->registerListener($this);

    $this->projectionStorage = $projectionStorage;

    }


    public function when(DomainEvent $event)

    {

    $method = explode('\\', get_class($event));

    $method = 'on' . end($method);

    if (method_exists($this, $method)) {

    $this->$method($event);

    }

    }

    }

    View Slide

  52. abstract class AbstractDomainEventListener
    implements DomainEventListener

    {

    protected $projectionStorage;


    public function __construct(
    EventBus $eventBus,
    ProjectionStorage $projectionStorage
    )

    {

    $eventBus->registerListener($this);

    $this->projectionStorage = $projectionStorage;

    }


    public function when(DomainEvent $event)

    {

    $method = explode('\\', get_class($event));

    $method = 'on' . end($method);

    if (method_exists($this, $method)) {

    $this->$method($event);

    }

    }

    }
    $eventBus->registerListener($this);

    View Slide

  53. abstract class AbstractDomainEventListener
    implements DomainEventListener

    {

    protected $projectionStorage;


    public function __construct(
    EventBus $eventBus,
    ProjectionStorage $projectionStorage
    )

    {

    $eventBus->registerListener($this);

    $this->projectionStorage = $projectionStorage;

    }


    public function when(DomainEvent $event)

    {

    $method = explode('\\', get_class($event));

    $method = 'on' . end($method);

    if (method_exists($this, $method)) {

    $this->$method($event);

    }

    }

    }
    $this->projectionStorage = $projectionStorage;

    View Slide

  54. Read model

    View Slide

  55. Projecting
    Projecting is process of converting (or
    aggregating) a stream of events into a structural
    representation - persistent read model.

    View Slide

  56. Projections
    • data stored in the way it can be easily used for
    reading purposes
    • very simple structure
    • updated on every event affecting any of its data

    View Slide

  57. class PostListProjection implements Projection

    {

    const PROJECTION_NAME = 'post-list';

    private $postId;

    public $title;

    public $publishingDate;


    public function __construct(PostId $postId, $title, \DateTime
    $publishingDate)

    {

    $this->postId = $postId;

    $this->title = $title;

    $this->publishingDate = $publishingDate;

    }


    public function getProjectionName()

    {

    return self::PROJECTION_NAME;

    }


    public function getAggregateId()

    {

    return $this->postId;

    }

    // ...

    }

    View Slide

  58. Projections
    • persisted (db, files, cache)
    • can always be cleared
    • and then can be replayed from event stream

    View Slide

  59. Projections
    • projections are extremely fast to load
    • possible minimal latency of update
    • eventually consistent with event storage

    View Slide

  60. abstract class AbstractDomainEventListener
    implements DomainEventListener

    {

    protected $projectionStorage;


    public function __construct(
    EventBus $eventBus,
    ProjectionStorage $projectionStorage
    )

    {

    $eventBus->registerListener($this);

    $this->projectionStorage = $projectionStorage;

    }


    public function when(DomainEvent $event)

    {

    $method = explode('\\', get_class($event));

    $method = 'on' . end($method);

    if (method_exists($this, $method)) {

    $this->$method($event);

    }

    }

    }
    public function when(DomainEvent $event)

    {

    $method = explode('\\', get_class($event));

    $method = 'on' . end($method);

    if (method_exists($this, $method)) {

    $this->$method($event);

    }

    }

    View Slide

  61. class PostListListener 

    extends AbstractDomainEventListener 

    implements DomainEventListener

    {

    public function onPostWasPublished(PostWasPublished $event)

    {

    $postListProjection = new PostListProjection(

    $event->getAggregateId(),

    $event->getTitle(),

    $event->getPublishingDate()

    );

    $this->projectionStorage->save($postListProjection);

    }


    public function onPostWasUpdated(PostWasUpdated $event)

    {

    $postListProjection = $this->projectionStorage->findById(

    'post-list',

    $event->getAggregateId()

    );

    $postListProjection->title = $event->getTitle();

    $postListProjection->content = $event->getContent();

    $this->projectionStorage->save($postListProjection);

    }

    }

    View Slide

  62. CQRS
    Command Query Responsibility Segregation

    View Slide

  63. –Greg Young
    „You can use CQRS without event sourcing,
    but with event sourcing you must use CQRS.”

    View Slide

  64. CQRS in event sourcing
    UI
    Command (write) Query (read)
    Event Model
    Event
    Storage
    Projection
    Storage
    Event
    Bus
    save() dispatch()
    Read Model
    Listeners
    Transaction scope

    View Slide

  65. Performance

    View Slide

  66. Performance: read model
    • projections are extremely fast to read
    • possible minimal latency on updating
    • dispatching is an only risk in some cases

    View Slide

  67. Performance: event storage
    • append only, immutable
    • immutable data is very easy to scale up
    • can handle millions of events
    • but you can always make a snapshot

    View Slide

  68. Snapshots
    • allow reconstituting object not from zero state
    • to avoid processing large numbers of events

    View Slide

  69. 1 2 n n+1 n+2 n+3 n+4 n+5 n+6 n+7

    Reconstituting
    Snapshots

    View Slide

  70. 1 2 n n+1 n+2 n+3 n+4 n+5 n+6 n+7

    snapshot
    Reconstituting
    Snapshots

    View Slide

  71. View Slide

  72. View Slide

  73. Is event sourcing
    better model?

    View Slide

  74. View Slide

  75. Event sourcing
    • simplifies modelling
    • focuses on facts and actions that happen
    • gives better overview

    View Slide

  76. Better overview

    View Slide

  77. Event sourced
    application is more
    real-life like

    View Slide

  78. –Greg Young
    „A state transition is not Update table set
    column equals value. It’s a fact”

    View Slide

  79. Event source is used
    • in banking
    • in finance
    • in insurance
    • in bookkeeping

    View Slide

  80. With event sourcing you can
    • go back to any point of time
    • analyze and compare data from any periods
    • you can delete and replay any read model data

    View Slide

  81. With event sourcing you will
    avoid
    • deleting something accidentally
    • problems with missing data that could have
    been saved
    • hard investigations of what has happened

    View Slide

  82. View Slide

  83. View Slide

  84. BasketWasPickedUp
    ProductWasAddedToBasket
    ProductWasAddedToBasket
    ProductWasRemovedFromBasket
    BasketWasCheckedOut

    View Slide

  85. BasketWasPickedUp
    ProductWasAddedToBasket
    ProductWasAddedToBasket
    ProductWasRemovedFromBasket
    BasketWasCheckedOut
    BasketWasPickedUp
    ProductWasAddedToBasket
    BasketWasCheckedOut

    View Slide

  86. Software

    View Slide

  87. geteventstore.com
    • high performance event storage
    • a plenty of additional features
    • introduced in 2012 by Greg Young, still
    developed

    View Slide

  88. candidate-labs / broadway
    • PHP solution
    • https://github.com/qandidate-labs/broadway

    View Slide

  89. prooph
    • PHP solution
    • http://getprooph.org/
    • https://github.com/prooph

    View Slide

  90. TL;DR
    Event sourcing principles
    • do not store state of objects
    • store only facts that happened
    (events)
    • reconstitute state of aggregates
    from event storage
    • dispatch events to update
    projections
    • use projections for reading

    View Slide

  91. Questions?

    View Slide

  92. Thank you!
    Presented sources are part of demo application:
    https://github.com/ulff/phpcon-demo

    View Slide