Command Buses Demystified: A Look at the Tactician Package

Share this article

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?

Key Takeaways

  • A Command Bus ensures the transport of a Command to its Handler, providing a consistent interface and defining the boundary between two layers, allowing for additional functionality to be added with decorators or middleware.
  • Commands are messages that capture the intent of the user and contain the input required to carry out a task, and are therefore immutable after creation.
  • Command Handlers interpret the intent of a specific Command and perform the expected behavior, maintaining a 1:1 relationship with Commands.
  • The Tactician package provides a flexible, straightforward interface for dispatching commands and handling them in PHP applications, supporting middleware for additional behavior and being framework-agnostic.
  • Utilizing a Command Bus provides advantages such as architectural boundary, decoupling from the framework, and separation of intent from interpretation, making the code more maintainable, easier to test, and improving code readability.

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?

Frequently Asked Questions (FAQs) about Command Buses and the Tactician Package

What is the main advantage of using a command bus in PHP?

The primary advantage of using a command bus in PHP is the separation of concerns it provides. It allows you to separate the code that performs an action from the code that requests the action. This separation makes your code more maintainable and easier to test. It also provides a clear and consistent structure for your application, making it easier to understand and navigate.

How does the Tactician package improve the implementation of a command bus?

The Tactician package is a simple, flexible command bus library for PHP. It provides a clean, straightforward interface for dispatching commands and handling them in your application. It also supports middleware, allowing you to add additional behavior to your command handling process, such as transaction management, event dispatching, and more. This makes it a powerful tool for implementing a command bus in your PHP applications.

Can I use the Tactician package with any PHP framework?

Yes, the Tactician package is framework-agnostic, meaning it can be used with any PHP framework. It’s designed to be simple and flexible, so it can easily be integrated into any application, regardless of the underlying framework. This makes it a versatile tool for implementing a command bus in any PHP application.

How does a command bus help with testing?

A command bus can greatly simplify the process of testing your application. By separating the code that performs an action from the code that requests the action, you can easily test each part in isolation. This makes your tests more focused and reliable, and it can also make them easier to write and maintain.

What is middleware in the context of a command bus?

In the context of a command bus, middleware is a type of software that provides additional behavior to the command handling process. This can include things like transaction management, event dispatching, logging, and more. Middleware is executed before and/or after the command handler, allowing you to add behavior to the command handling process without modifying the command or the handler itself.

How does the Tactician package handle exceptions?

The Tactician package provides a flexible system for handling exceptions. By default, it simply rethrows any exceptions that occur during command handling. However, you can also add middleware to handle exceptions in a custom way. This allows you to manage exceptions in a way that best suits your application’s needs.

Can I use multiple command buses in a single application?

Yes, you can use multiple command buses in a single application. This can be useful in larger applications where you want to separate different types of commands into different buses. Each bus can have its own set of handlers and middleware, allowing you to customize the command handling process for each type of command.

How does a command bus improve code readability?

A command bus improves code readability by providing a clear, consistent structure for your application. Each command represents a specific action, and each handler is responsible for performing that action. This makes it easy to understand what each part of your application does, and how they interact with each other.

Can I use the Tactician package in a non-CQRS architecture?

Yes, while the Tactician package is often used in a CQRS (Command Query Responsibility Segregation) architecture, it can also be used in a traditional CRUD (Create, Read, Update, Delete) architecture. The package is flexible and can be adapted to suit a variety of architectural styles.

How does the Tactician package handle command dispatching?

The Tactician package provides a simple, straightforward interface for dispatching commands. You simply create a command object and pass it to the command bus’s dispatch method. The bus then finds the appropriate handler for the command and executes it. This process can be customized with middleware, allowing you to add additional behavior to the command dispatching process.

Andrew CairnsAndrew Cairns
View Author

Andrew is passionate about Domain-Driven Design, Test-Driven Development, System Architecture and Agile Methodologies. He is Technical Lead at GatherContent.

BrunoScommandcommand busdesign patterndesign patternsOOPHPPHPtactician
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week