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:
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: 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.
canHandle method
informs, whichAggregate Classes
can be handled with thisRepository
. Return true, if saving specific aggregate is possible, false otherwise. - 2.
findBy method
returns if found, existingAggregate instance
, otherwise null. - 3.
save method
is reponsible for storing givenAggregate 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
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.
findBy method
returns previously created events for given aggregate. - 2.
save method
gets array of events to save returned byCommandHandler
after performing an action
When your implementation is ready simply mark it with
#[Repository]
attribute:#[Repository]
class DoctrineRepository implements StandardRepository
{
// implemented methods
}
Ecotone provides inbuilt repositories to get you started much quicker. This way you can just enable given repository and start implementing higher level code.
This provides integration with Eloquent ORM. Eloquent support is available out of the box after installing Laravel module.
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.
Ecotone provides inbuilt Event Sourcing Repository, which will set up Event Store and Event Streams. To enable it read Event Sourcing Section.
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]
.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
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.Last modified 24d ago