description |
---|
Repository PHP |
Read Aggregate Introduction sections first to get more details about Aggregates.
Repositories are used for retrieving and saving the aggregate to persistent storage.
Typical flow for calling aggregate method would looks like below:
class AssignWorkerHandler
{
private TicketRepository $ticketRepository;
#[CommandHandler]
public function handle(AssignWorkerCommand $command) : void
{
// fetch the aggregate from repository
$ticket = $this->ticketRepository->findBy($command->getTicketId());
// call action method
$ticket->assignWorker($command);
// store the aggregate in repository
$this->ticketRepository->save($ticket);
}
}
$this->commandBus->send(
new AssignWorkerCommand(
$ticketId, $workerId,
)
);
By setting up Repository
we provide Ecotone with functionality to fetch and store the Aggregate , so we don't need to do it on our own.
If our class is defined as Aggregate, Ecotone will use Repository in order fetch and store it, whenever the Command
is sent via Command Bus
.
#[Aggregate]
class Ticket
{
#[Identifier]
private string $ticketId;
#[CommandHandler]
public function assignWorker(AssignWorkerCommand $command)
{
// do something with assignation
}
}
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.
State-Stored Aggregate are normal Aggregates
, which are not Event Sourced
.
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;
}
canHandle method
informs, whichAggregate Classes
can be handled with thisRepository
. Return true, if saving specific aggregate is possible, false otherwise.findBy method
returns if found, existingAggregate instance
, otherwise null.save method
is responsible for storing givenAggregate instance
.
$identifiers
are array of#[Identifier]
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
When your implementation is ready simply mark it with #[Repository]
attribute:
#[Repository]
class DoctrineRepository implements StandardRepository
{
// implemented methods
}
This is example implementation of Standard Repository using Doctrine ORM.
Repository:
final class EcotoneTicketRepository implements StandardRepository
{
public function __construct(private readonly EntityManagerInterface $entityManager)
{
}
public function canHandle(string $aggregateClassName): bool
{
return $aggregateClassName === Ticket::class;
}
public function findBy(string $aggregateClassName, array $identifiers): ?object
{
return $this->entityManager->getRepository(Ticket::class)
// Array of identifiers for given Aggregate
->find($identifiers['ticketId']);
}
public function save(array $identifiers, object $aggregate, array $metadata, ?int $versionBeforeHandling): void
{
$this->entityManager->persist($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.
findBy method
returns previously created events for given aggregate.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 EventSourcedRepository
{
// implemented methods
}
Ecotone provides inbuilt repositories to get you started quicker. This way you can enable given repository and start implementing higher level code without worrying about infrastructure part.
This provides integration with Doctrine ORM. To enable it read more in Symfony Module Section.
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.
By default Ecotone when we have only one Standard and Event Sourcing Repository registered, Ecotone will use them for our Aggregate by default.
This comes from simplification, as if there is only one Repository of given type, then there is nothing else to be choose from.
However, if we register multiple Repositories, then we need to take over the process and tell which Repository will be used for which Aggregate.
- In case of Custom Repositories we do it using canHandle method.
- In case of inbuilt Repositories, we should follow configuration section for given type