PHP
Article

Command Buses Demystified: A Look at the Tactician Package

By Andrew Cairns

Command Buses have been getting a lot of community attention lately. The topic can be quite overwhelming at first when trying to understand all the concepts and terminology, but in essence – what a Command Bus does is actually incredibly simple.

In this article, we’ll take a closer look at variations of the Command Pattern; their components; what a Command Bus is; and an example application using the Tactician package.

Hero Image?

Overview

So, what exactly is a Command Bus?

Command Pattern

The role of the Command Bus is to ensure the transport of a Command to its Handler. The Command Bus receives a Command, which is nothing more than a message describing intent, and passes this onto a Handler which is then responsible for performing the expected behavior. This process can therefore be looked upon as a call to the Service Layer – where the Command Bus does the plumbing of the message in-between.

Before the introduction of the Command Bus, Service Layers were often a collection of classes without a standard way of being invoked. Command Buses solve this problem by providing a consistent interface and better defining the boundary between two layers. The standard interface also makes it possible for additional functionality to be added by wrapping with decorators, or by adding middleware.

Therefore, there can be more to a Command Bus than calling a Service Layer. Basic implementations may only locate a Handler based on naming conventions, but more complex configurations may pass a Command through a pipeline. Pipelines can perform additional behavior, such as: wrapping the behavior in a transaction; sending the Command over a network; or perhaps some queueing and logging.

Before we take a closer look at the advantages of using a Command Bus, let’s take a look at the individual components which make it possible.

Commands

The Command Pattern was one of the behavioral patterns described by The Gang of Four as a way for two objects to communicate.

To complicate things a little, the pattern has evolved with alternative designs. Architectural Patterns such as CQS (Command-query Separation) and CQRS (Command Query Responsibility Segregation) also use Commands, however in their context – a Command is simply a message.

Traditionally, a GoF Command would handle itself:

final class CreateDeck
{
    /**
       * @var string
     */
    private $id;

    /**
       * @param string $id
     */
    public function __construct($id)
    {
        $this->id = $id;
    }

    /**
       * @return Deck
     */
    public function execute()
    {
        // do things
    }
}

Since this approach to the Command Pattern contains the behavior, there is no message that must be routed to a Handler. There is no need for a Command Bus.

However, the Command Message Pattern suggests separating intent from interpretation, expressing actions within the Domain:

final class CreateDeck
{
    /**
       * @var string
     */
    private $id;

    /**
       * @param string $id
     */
    public function __construct($id)
    {
        $this->id = $id;
    }

    /**
       * @return DeckId
     */
    public function getId()
    {
        return DeckId::fromString($this->id);
    }
}

In this example, and throughout the rest of this article, we will use a Command as a message. It captures the intent of the user and contains the input required to carry out a task. It explicitly describes behavior the system can perform. Therefore, Commands are named imperatively, such as: CreateDeck, ShuffleDeck and DrawCard.

Commands are commonly referred to as DTO’s (Data Transfer Objects) as they are used to contain data being transported from one location to another. Commands are therefore immutable. After creation the data is not expected to change. You will notice that our CreateDeck example Command contains no setters, or any other way to alter the internal state. This ensures that it can not change during transit to the handler.

Command Handlers

Handlers interpret the intent of a specific Command and perform the expected behavior. They have a 1:1 relationship with Commands – meaning that for each Command, there is only ever one Handler.

final class CreateDeckHandler
{
    /**
       * @var DeckRepository
     */
    private $decks;

    /**
       * @param DeckRepository $decks
     */
    public function __construct(DeckRepository $decks)
    {
        $this->decks = $decks;
    }

    /**
       * @param CreateDeck $command
     */
    public function handle(CreateDeck $command)
    {
        $id = $command->getId();

        $deck = Deck::standard($id);

        $this->decks->add($deck);
    }
}

In our example above, a new Deck is being created. What’s also important to note is what isn’t happening. It does not populate a view; return a HTTP response code or write to the console. Commands can be executed from anywhere, therefore Handlers remain agnostic to the calling environment. This is extremely powerful when architecting boundaries between your application and the outside world.

The Command Bus

And finally, the Command Bus itself. As briefly explained above, the responsibility of a Command Bus is to pass a Command onto its Handler. Let’s look at an example.

Imagine we needed to expose a RESTful API endpoint to allow the creation of new Decks:

use Illuminate\Http\Request;

final class DeckApiController
{
    /**
       * @var CommandBus
     */
    private $bus;

    /**
       * @var CommandBus $bus
     */
    public function __construct(CommandBus $bus)
    {
        $this->bus = $bus;
    }

    /**
       * @var Request $request
     */
    public function create(Request $request)
    {
        $deckId = $request->input('id');

        $this->bus->execute(
            new CreateDeck($deckId)
        );

        return Response::make("", 202);
    }
}

Now imagine the need to also create new Decks from the console. We can handle this requirement by passing the same Command again through the Bus:

class CreateDeckConsole extends Console
{
    /**
       * @var string
     */
    protected $signature = 'deck';

    /**
       * @var string
     */
    protected $description = 'Create a new Deck';

    /**
       * @var CommandBus
     */
    private $bus;

    /**
       * @var CommandBus $bus
     */
    public function __construct(CommandBus $bus)
    {
        $this->bus = $bus;
    }

    public function handle()
    {
        $deckId = $this->argument('id');

        $this->bus->execute(
            new CreateDeck($deckId)
        );

        $this->comment("Created: " . $deckId);
    }
}

The examples are not concerned with the implementation details of creating Decks. Our Controller and Console Commands use a Command Bus to pass instructions to the Application allowing them to focus specifically on how their type of request should be answered. It also allows us to remove what could have potentially been a lot of duplicated logic.

Testing our Controller and Console Command is now a trivial task. All we need to do is assert that the Command passed to the Bus was wellformed based on the request.

Example

The example application considers a Deck of Cards for a domain. The application has a series of commands: CreateDeck, ShuffleDeck and DrawCard.

Up until this point CreateDeck has only been providing context when exploring the concepts. Next, we will set up Tactician as our Command Bus and execute our Command.

Configuring Tactician

Once you have installed Tactician, you’ll need to configure it somewhere within the bootstrapping of your application.

In our example, we have placed this within a bootstrap script, however you may wish to add this to something like a Dependency Injection Container.

Before we can create our instance of Tactician, we need to set up our pipeline. Tactician uses middleware to provide a pipeline and acts as the package’s plugin system. In fact, everything in Tactician is middleware – even the handling of a Command.

Middleware Pipeline

The only middleware we require within our pipeline is something to handle the execution of Commands. If this sounds complex, then you can very quickly hack together your own middleware. We will, however, use the CommandHandlerMiddleware that Tactician provides. The creation of this object has several dependencies.

public function __construct(
    CommandNameExtractor $commandNameExtractor,
    HandlerLocator $handlerLocator,
    MethodNameInflector $methodNameInflector
)

As we learned earlier, a Command Bus locates the Handler for a particular Command. Let’s take a closer look at how each of these dependencies handle their part of the task.

CommandNameExtractor

The role of CommandNameExtractor is to get the name of the Command. You’re probably thinking PHP’s get_class() function would do the job – and you’re correct! Therefore Tactician includes ClassNameExtractor which does exactly that.

HandlerLocator

We also need an instance of HandlerLocator which will find the correct Handler based on the name of the Command. Tactician provides two implementations of this interface: CallableLocator and InMemoryLocator.

Using CallableLocator is useful when resolving a Handler from a container. However, in our example, we’ll register our Handlers manually with an InMemoryLocator.

We now have all the dependencies required to get an instance of a Handler based on the name of the Command – but that isn’t enough.

MethodNameInflector

We still need to tell Tactician how to invoke the Handler. That’s where the MethodNameInflector comes into play. The inflector returns the name of the method that expects the Command passed to it.

Tactician helps us out again by providing implementations for several popular conventions. In our example, we will follow the handle($command) method convention implemented within HandleInflector.

Simple Tactician Configuration

Let’s take a look at the setup so far:

use League\Tactician\CommandBus;
use League\Tactician\Handler\Locator\InMemoryLocator;
use League\Tactician\Handler\CommandHandlerMiddleware;
use League\Tactician\Handler\MethodNameInflector\HandleInflector;
use League\Tactician\Handler\CommandNameExtractor\ClassNameExtractor;

$handlerMiddleware = new CommandHandlerMiddleware(
    new ClassNameExtractor,
    new InMemoryLocator([]),
    new HandleInflector
);

$bus = new CommandBus([
    $handlerMiddleware
]);

That looks good, but there is still one problem – the InMemoryLocator needs the Handlers to be registered so they can be found at runtime. Since this is only bootstrap code for a couple of examples, let’s store a reference to the locator for now so Handlers can be registered when needed later.

$locator = new InMemoryLocator([]);

$handlerMiddleware = new CommandHandlerMiddleware(
    new ClassNameExtractor,
    $locator,
    new HandleInflector
);

In a proper application, you’ll probably want to use a locator that can find a Command’s Handler based on a naming convention.

Tactician is now configured. Let’s use it to execute the CreateDeck Command.

Executing the Command

To create an instance of a Command, we fill out all the required constructor requirements:

<?php require 'bootstrap.php';

$deckId = DeckId::generate();

$newDeckCommand = new CreateDeck((string) $deckId);

The final task remaining before we can send our Command on it’s way through the bus is for us to register our Handler with the InMemoryLocator we stored a reference to from before:

$decks = new InMemoryDeckRepository;

$locator->addHandler(
    new CreateDeckHandler($decks),
    CreateDeck::class
);

Finally – we are ready to pass our Command into the Bus:

$bus->handle($newDeckCommand);

var_dump(
    $decks->findById($deckId)
);

And it really is that simple!

Advantages

There are many advantages when applying the Command Pattern or utilizing a Command Bus.

  1. Architectural Boundary

    One of the most important benefits is the architectural boundary that surrounds your Application. Upper layers, such as the User Interface, can send a Command to a lower layer, across the boundary and through the consistent interface provided by the Command Bus.

    The upper layer knows the context in which the Command is being issued, however, once the message has passed through the boundary – it’s just a message which could have been issued from any number of contexts: an HTTP request; a cron job; or something else entirely.

    Boundaries aid separation of concerns and free one side from worrying about the other. High-level layers no longer have the burden of knowing exactly how a task is completed – and lower layers do not need to worry about the context in which they are being used.

  2. Framework and Client Decoupling

    Applications surrounded with boundaries are agnostic to their framework. They do not deal with HTTP requests or cookies. All properties needed to perform behavior get passed along as part of the Command payload.

    Decoupling from your framework allows you to maintain your Application as a framework changes or updates – and makes it easier to test.

  3. Separates Intent from Interpretation

    The role of the Command Bus is to transport a Command to its Handler. This inherently means that the intention of conducting an action is separate from the execution, otherwise there would be no need for a Command Bus. Commands are named using the language of the business and explicitly describe how an application can be used.

    Serializing a Command becomes much easier when it does not need to know how to perform the behavior. In Distributed Systems, a message could be generated on one system – but performed on another, written in a different language on a different operating system.

    When all a unit of code does is produce a message, it is extremely easy to test. The Command Bus injected into the unit can be replaced and the message can be asserted.

So, what do you think? Is a Command Bus over-engineering, or a great tool for system architecture?

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

No Reader comments

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.