Commands encapsulate requests to DO something in the system. Each command uses the imperative tense ("CreateWidget", "MakeCustomerHappy", "UseTheForce"). A command is an immutable value object with no behaviour of its own. Commands are processed by Command Handlers. Each command must have exactly one command handler.
class CreateWidgetCommand
{
function __construct($widgetColour, $widgetHeight, $widgetWidth){ ...}
function height()
{
return $this->widgetHeight;
}
}
Events are used to tell other parts of the system that a thing has happened. Events are named in the past tense (CustomerBecameHappyEvent, UsedTheForceEvent, WidgetCreatedEvent). Like commands, events are immutable and have no behaviour of their own. Events are handled by Event Handlers. An event may have any number of handlers, including zero.
Handlers coordinate the processing of a request in the system. Each handler has the same basic structure:
- Fetch current state from the database
- Call methods on domain objects to mutate state
- Commit state.
- Raise any events.
Commands are found and executed by a message processor. I suggest we use SimpleBus.
Unit tests are usually written against command handlers.
A data store. Repositories encapsulate the idea of data access. They should, ideally, have collection semantics. Repositories work within a Unit Of Work. Doctrine has the concept of an EntityRepository. I would suggest we create a simple interface, and implement using that.
interface Repository
{
public function add($entity);
public function get($entityId);
public function delete($entity);
}
class DoctrineRepository implements Repository
{
// wrap an EntityRepository
}
A unit of work encapsulates a set of operations against a datastore. A Unit of Work is committed or rolled back as a single atomic operation. Usually, a unit of work spans a single database transaction. Units of work track objects that are loaded within their scope, and automatically persist changes made to them.
Again, Doctine supports this concept with the EntityManager and UnitOfWork classes, so it's easy for us to implement our interface based on that.
// When inserting a new item, we need to call add.
class CreateWidgetHandler
{
function __construct($unitOfWorkManager){ ... }
function handle($cmd)
{
try
{
$uow = $this->unitOfWorkManager->start();
// $widgets is a repository exposed by the unit of work
$uow->widgets->add( $this->makeWidget($cmd) );
$uow->commit();
}
catch(...) { $uow->rollback(); }
}
}
// When updating an item, the unit of work will track changes automatically.
class SetWidgetSizeHandler
{
function __construct($unitOfWorkManager){ ... }
function handle($cmd)
{
try
{
$uow = $this->unitOfWorkManager->start();
$widget = $uow->widgets->get($cmd->widgetId);
$widget->setSize($cmd->height, $cmd->width);
$uow->commit();
}
catch(...) { $uow->rollback(); }
}
}
An adapter is responsible for talking to the outside world, and translating into our domain language. For this project the most important adapter is Symfony. Symfony handlers can be registered as services in the dependency injection container, which gives us a single composition root. Because all the logic is in the domain model, and orchestrated in unit-testable command handlers, we don't generally bother testing Adapters. They're very thin, very simple. We would usually write a single Acceptance Test that makes sure our controller is wired up correctly, and use unit tests for verifying all our behaviour.
class WidgetController
{
function __construct($bus)
{}
public function create($colour, $width, $height)
{
// validation goes here.
$cmd = new CreateWidgetCommand($colour, $width, $height);
$this->bus->send($cmd);
return new Response('<html><body>Created!</body></html>');
}
}