Using command pattern with domain model

A simple command pattern library

Posted by Sami Tikka on December 29, 2013

I have been working on a library, which implements command dispatcher (or invoker), part of command pattern. One possible use of command pattern is to encapsulate business logic into separate classes, that communicate to developers/stakeholders the intent of the code and effectively hide the implementation details.

Sample use scenario

Let's say we have a controller, that handles de-serializing POSTed JSON into a model, that can be persisted with ORM. Controller action logic for that might look something like this:

$user = $serializer->deserialize('User', $json);
$user->setCreator($loggedInUser);
// ...some other pre-persist stuff
$orm->persist($user);
$orm->flush();
$emailService->sendPasswordCreationLink($user);

With Command Dispatcher, we encapsulate logic away into command classes:

$user = $dispatcher->handle(CreateUserCommand::create(array($json, $loggedInUser)));
$dispatcher->handleCommands(array(
    SaveUserCommand::create(array(($orm, $user)),
    EmailUserPasswordCreationLinkCommand::create(array($emailService, $user))
));

The latter code communicates itself considerably better, even non-developers can understand the program flow and inspect it's validity. We are now using the domain language to express the program flow.

Utilizing the dispatcher

One advantage of using a dispatcher is that it provides central place for handling pre- and post-execution logic. Let's say we also want to log our program flow for record keeping. We add a pre-command handler, which adds a log record before calling command's execute() -method:

$dispatcher->addPreCommandHandler(function($command) use ($logger) {
    $command->log($logger);
});

Now each command needs to implement log() -method.

class SampleCommand extends AbstractCommand implements CommandInterface
{
    // ...
    public function log($logger)
    {
         $logger->info('Executing Sample command...');
    }
}

Other uses for dispatcher could be queuing commands, storing execution history, adding undo logic for commands etc.