becklyn/ddd-core
DDD/CQRS/event-sourcing core building blocks for PHP: entity identities, domain events, command handling, transactions, and an event store workflow. Framework-agnostic abstractions with Symfony/Doctrine/SimpleBus bridge packages available.
becklyn/ddd-core is a set of components for developing software with domain-driven design, event sourcing and CQRS. Support is included for:
The components are designed to facilitate the following workflow:
becklyn/ddd-core provides the components mostly as abstract classes and interfaces to facilitate use independent of any specific technological infrastructure. Our choice is Symfony with Doctrine and SimpleBus, and we provide implementations tying everything together in the becklyn/ddd-doctrine-bridge and becklyn/ddd-symfony-bridge libraries. If you wish to use other technologies, you will need to provide your own implementations for the event store and event, transaction and command handling.
Here's a more detailed explanation on how to achieve the becklyn/ddd-core workflow:
CommandBus. Commands are plain PHP classes of your own creation, essentially DTOs, and they should contain all the data necessary to perform the desired action. At a minimum, they should contain the identity of the aggregate being manipulated.CommandBus should route the command to its corresponding handler. The implementation provided by becklyn/ddd-symfony-bridge does this automatically within a properly configured Symfony application. Otherwise, you will have to ensure this yourself.CommandHandler class:
CommandHandler::handleCommand.handleCommand will in turn call the abstract execute method in which you should implement your command handling logic:
EventProvider interface (we recommend doing so from a repository).EventProviderCapabilities or EventSourcedProviderCapabilities trait in the aggregate to facilitate this).execute method.CommandHandler will dequeue any events raised by the aggregate returned from execute, register them with the EventRegistry and commit the transaction through the TransactionManager.execute. In such cases, the service should use the EventRegistry to dequeue and register any events raised by the affected aggregate, and CommandHandler::execute should return null.TransactionManager::commit should take care of any persistence concerns and call EventManager::flush. Similarly, TransactionManager::rollback should discard and changes and call EventManager::clear. A Doctrine implementation is provided by becklyn/ddd-doctrine-bridge.EventManager collects all events registered by the EventRegistry and dispatches them through the EventBus. Clearing the EventManager simply discards all events.EventBus should dispatch the events to any subscribers subscribing to them. becklyn/ddd-symfony-bridge provides a Symfony/SimpleBus implementation that does this automatically within a properly configured Symfony application.Entities must implement the EventProvider interface, and they must raise a domain event for every change to their state. The EventProviderCapabilities can be used by entities to facilitate this. Each entity must also have its corresponding identity class implementing the EntityId interface. AbstractEntityId is provided for a default implementation.
One entity in every aggregate serves as the aggregate root. Any and all interaction with the aggregate is allowed only through this entity, and thus only aggregate roots should have repositories. Aggregate root identities must implement the AggregateId interface instead of just EntityId. AbstractAggregateId is provided for a default implementation. When dequeueEvents is called on an aggregate root, it should in turn collect all of the events from other entities in the aggregate and return them along the events raised by the aggregate root.
Events must implement the DomainEvent interface and may do so extending the AbstractDomainEvent class. A domain event records a state change and must contain all the data necesary for it to be replayed from the previous state.
If using event sourcing, aggregates should use the EventSourcedProviderCapabilities trait instead of EventProviderCapabilities. While it is possible to implement repositories using the EventStore and its getAggregateStream method directly, this will likely result in low performance for most implementations if a large number of aggregates is fetched at once. For such scenarios we recommend using projections instead.
We write our PHPUnit/Prophecy unit tests inspired by the BDD workflow of "given/when/then". To test the code interacting with components from this library, we have gathered various given/when/then helper methods into traits. You can find them within the Testing namespaces of individual subdomans present in the library, for example:
Becklyn\Ddd\Commands\Testing\CommandHandlerTestTraitBecklyn\Ddd\Events\Testing\DomainEventTestTraitHow can I help you explore Laravel packages today?