CodefyPHP FrameworkCodefyPHP Framework
  • Home
  • Community
    • Forum
    • Github
    • YouTube
  • News
  • Home
  • Community
    • Forum
    • Github
    • YouTube
  • News
home/Knowledge Base/Aggregates/Records Events
Popular Search:installation, codex, mail

Records Events

23 views 0

Written by Joshua
August 14, 2024

Domain events describe what has happened, but they originate from an aggregate or domain model. With our value objects and domain events in place, we are now ready to build out our aggregate.

TDD

Let’s do a simple test by using TDD.

<?php

declare(strict_types=1);

use App\Domain\Post\Events\PostWasCreated;
use App\Domain\Post\Events\TitleWasChanged;
use App\Domain\Post\Post;
use App\Domain\Post\ValueObject\PostId;
use App\Domain\Post\ValueObject\Title;
use App\Domain\Post\ValueObject\Content;
use Codefy\Domain\Aggregate\AggregateId;
use Codefy\Domain\Aggregate\RecordsEvents;
use Codefy\Domain\EventSourcing\DomainEvent;
use Codefy\Domain\EventSourcing\DomainEvents;
use PHPUnit\Framework\Assert;

use function expect;
use function it;

it('should have recorded 2 events.', function () {
    $post = Post::createPost(
        postId: new PostId(value: '760b7c16-b28e-4d31-9f93-7a2f0d3a1c51'),
        title: new Title(value: 'New Post Title'),
        content: new Content(value: 'This is short form content.')
    );
    $post->changeTitle(title: new Title(value: 'Updated Post Title'));
    $events = $post->getRecordedEvents();

    Assert::assertTrue(condition: $post->hasRecordedEvents());

    expect(value: PostId::fromString(postId: '760b7c16-b28e-4d31-9f93-7a2f0d3a1c51'))
        ->toEqual(expected: $post->aggregateId())
        ->and(value: 2)->toEqual(expected: $post->playhead());

    Assert::assertCount(expectedCount: 2, haystack: $events);

    $post->clearRecordedEvents();

    Assert::assertFalse(condition: $post->hasRecordedEvents());
    Assert::assertCount(expectedCount: 0, haystack: $post->getRecordedEvents());
});

In our test, a new post is created, and the title has been changed which equals 2 events.

$post->clearRecordedEvents() clears the events so that next time we call the createPost method on Post, we don’t retrieve a list of old data mixed with new data in our results.

Post Implementation

Now we are at the point where we can create our Post aggregate or domain model. We will keep it simple in order to satisfy the tests. Let’s take it one step/method at a time:

<?php

final class Post implements RecordsEvents
{
}

Reading the class in natural language we have a Post that records events.

Private Constructor

<?php

private function __construct(private PostId $postId)
{
}

The constructor is private to make it immutable. So we will have to instantiate the class by using a named constructor.

createPost Method

<?php

public static function createPost(PostId $postId, Title $title, Content $content): Post
{
    $post = new Post(postID: $postId);

    $post->record(
        event: PostWasCreated::withData($postId, $title, $content)
    );

    return $post;
}

Our named constructor also records an event.

Record that a post was created.

changeTitle Method

<?php

public function changeTitle(Title $title): void
{
    $this->record(
        event: TitleWasChanged::withData(postId: $this->postId, title: $title)
    );
}

Our second method also records an event.

Record that a post title was changed.

record and Other Methods

Now let’s implement a record method along with some other methods implemented from RecordsEvents (the record method is not part of the RecordsEvents interface). It depends on how you want to implement these methods. They can be implemented in the aggregate or via a trait.

<?php

private function record(DomainEvent $event)
{
    $this->recordedEvents[] = $event;
}

public function aggregateId(): PostId|AggregateId
{
    return $this->postId;
}

public function hasRecordedEvents(): bool
{
    return !empty($this->recordedEvents);
}

public function getRecordedEvents(): DomainEvents
{
    return DomainEvents::fromArray(events: $this->recordedEvents);
}

public function clearRecordedEvents()
{
    $this->recordedEvents = [];
}

recordedEvents keeps a list of newly created events since the last clearRecordedEvents().

aggregateId() is the unique identifier of the Post aggregate.

We can use hasRecordedEvents() to check if there are any uncommitted events since the last time it was cleared or persisted.

getRecordedEvents() is a list of domain events that were recorded since the last time they were cleared or persisted.

DomainEvents is an immutable array or DomainEvent objects.

So, let’s put all the pieces together to make up our Post aggregate/domain model:

<?php

final class Post implements RecordsEvents
{
    protected array $recordedEvents = [];

    private function __construct(private PostId $postId)
    {
    }

    public static function createPost(PostId $postId, Title $title, Content $content): Post
    {
        $post = new Post(postID: $postId);

        $post->record(
            event: PostWasCreated::withData($postId, $title, $content)
        );

        return $post;
    }

    public function changeTitle(Title $title): void
    {
        $this->record(
            event: TitleWasChanged::withData(postId: $this->postId, title: $title)
        );
    }

    private function record(DomainEvent $event)
    {
        $this->recordedEvents[] = $event;
    }

    public function hasRecordedEvents(): bool
    {
        return !empty($this->recordedEvents);
    }

    public function getRecordedEvents(): DomainEvents
    {
        return DomainEvents::fromArray(events: $this->recordedEvents);
    }

    public function clearRecordedEvents()
    {
        $this->recordedEvents = [];
    }
}

Now that the Post class is fully defined, our test should run as expected.

Forum

If you have any questions or issues, please feel free to post to the Documentation Forum.

SLA Support

If you are needing more hands on support, needing consultation, or help with setup, support me on Github at $60 or more. Once you've sponsored me, you will receive an email on the best way to contact me to start your support.

Edit on Github

Last Updated on August 14, 2024 by Joshua

Related Articles
  • Aggregate Repository
  • Event Store
  • Event Sourcing
  • Protecting Invariants
  • Value Objects
  • Identifies Aggregate

Didn't find your answer? Check out the Forum

  Domain Event

Protecting Invariants  

  • Copyright 2025 CodefyPHP.com. All Rights Reserved

Popular Search:installation, codex, mail