Repository
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
}

Own Repository Interface

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.
Export as PDF
Copy link
On this page
How to implement Repository
Repository for State-Stored Aggregate
Repository for Event Sourced Aggregate
Set up your own Implementation
Own Repository Interface