Links

Repositories and Fetching Aggregates

Repository PHP
Repositories are used for retrieving and saving the aggregate to persistent storage.
Aggregate need to be loaded in order to call method on it. Normally the flow for calling aggregate method, would looks like below. Which involves having handling service with access to repository.
class CloseTicketHandler
{
private TicketRepository $ticketRepository;
#[CommandHandler]
public function handle(CreateTicketCommand $command) : void
{
$ticket = $this->ticketRepository->findBy($command->getTicketId());
$ticket->assignWorker($command);
$this->ticketRepository->save($ticket);
}
}
class Ticket
{
public function assignWorker(CreateTicketCommand $command)
{
// do something with assignation
}
}
Ecotone provides possibility to mark Ticket Aggregate methods as CommandHandler directly. In that situation, Ecotone retrievies identifiers from Command message, pass them to Repository, calls the method on aggregate instance and saves it. In short it does code from CreateTicketCommand Handler for you.
class Ticket
{
#[CommandHandler]
public function assignWorker(CreateTicketCommand $command)
{
// do something with assignation
}
}
As Ecotone does not try to impose specific solutions, you are free to choose, which fits you best in specific context. Above example is based on External Command Handlers.
To get more details how to implement Aggregate, go to previous pages:

How to implement Repository

There are two types of repositories. One for storing State-Stored Aggregate and another one for storing Event Sourcing Aggregate.
Based on which interface is implemented, Ecotone knows which Aggregate type was selected. The interface informs, if specific Repository can handle given Aggregate class. You may implement:

Repository for State-Stored Aggregate

interface StandardRepository
{
1 public function canHandle(string $aggregateClassName): bool;
2 public function findBy(string $aggregateClassName, array $identifiers) : ?object;
3 public function save(array $identifiers, object $aggregate, array $metadata, ?int $expectedVersion): void;
}
  1. 1.
    canHandle method informs, which Aggregate Classes can be handled with this Repository. Return true, if saving specific aggregate is possible, false otherwise.
  2. 2.
    findBy method returns if found, existing Aggregate instance, otherwise null.
  3. 3.
    save method is reponsible for storing given Aggregate instance.
  • $identifiers are array of @AggregateIdentifier defined within aggregate.
  • $aggregate is instance of aggregate
  • $metadata is array of extra information, that can be passed with Command
  • $expectedVersion if version locking by @Version is enabled it will carry currently expected version

Repository for Event Sourced Aggregate

interface EventSourcedRepository
{
public function canHandle(string $aggregateClassName): bool;
1 public function findBy(string $aggregateClassName, array $identifiers) : EventStream;
2 public function save(array $identifiers, string $aggregateClassName, array $events, array $metadata, int $versionBeforeHandling): void;
}
Event Sourced Repository instead of working with aggregate instance, works with events.
  1. 1.
    findBy method returns previously created events for given aggregate.
  2. 2.
    save method gets array of events to save returned by CommandHandler after performing an action

Set up your own Implementation

When your implementation is ready simply mark it with #[Repository] attribute:
#[Repository]
class DoctrineRepository implements StandardRepository
{
// implemented methods
}

Inbuilt Repositories

Ecotone provides inbuilt repositories to get you started much quicker. This way you can just enable given repository and start implementing higher level code.

- Doctrine ORM Support

This provides integration with Doctrine ORM. To enable it read more in Dbal Module Section.

- Laravel Eloquent Support

This provides integration with Eloquent ORM. Eloquent support is available out of the box after installing Laravel module.

- Document Store Repository

This provides integration Document Store using relational databases. It will serialize your aggregate to json and deserialize on load using Converters. To enable it read in Dbal Module Section.

- Event Sourcing Repository

Ecotone provides inbuilt Event Sourcing Repository, which will set up Event Store and Event Streams. To enable it read Event Sourcing Section.

Fetching and storing Aggregate directly

If you want to make use of Repositories directly in your code and want to avoid using above implementation, you may create Interface to decouple from the the Framework completely. We are doing it by marking method with #[Repository].

For State-Stored Aggregate

interface OrderRepository
{
#[Repository]
public function getOrder(string $twitId): Order;
#[Repository]
public function findOrder(string $twitId): ?Order;
#[Repository]
public function save(Twitter $twitter): void;
}
Ecotone will read type hint to understand, which Aggregate you would like to fetch or save.
Implementation will be delivered by Framework. All you need to do is to define the interface and it's available to use in your Dependency Container

For Event Sourced Aggregate

interface OrderRepository
{
#[Repository]
public function getOrder(string $twitId): Order;
#[Repository]
public function findOrder(string $twitId): ?Order;
#[Repository]
#[RelatedAggregate(Order::class)]
public function save(string $aggregateId, int $currentVersion, array $events): void;
}
The difference is in save method, you need to provide aggregate id, current aggregate's version and array of events you would like to store.

Demo

You can check demo to see how to work with Aggregate Interface Repositories or Business Interfaces.