Hexagonal Architecture in PHP

  • 07/11/2016
  • 7 minuten leestijd

Hexagonal Architecture in PHP

What is hexagonal architecture? In short and simple, it is a way of coding your application without being dependent on any external libraries or frameworks. All application specific logic should be contained an not be tightly coupled. By defining interfaces at the boundaries of your application, you will allow external libraries to implement functionality hidden within your actual application. Confused? Read along!

Oi!

Recently my ex-colleague and buddy Marco sent me a link to this article, on hexagonal architecture. And although it's a very interesting article, it does leave me with some questions on how to actually implement it. Of course, the ways explained in this article are not set in stone, and one can just take parts of the architectural pattern. Let's find out what parts suit me, by coding a small application.

What is hexagonal architecture?

So what actually is hexagonal architecture (or ports & adapters, as it is also called)? As described by Alistair Cockburn, in short and simple, it is a way of coding your application without being dependent on any external libraries or frameworks. All application specific logic should be contained an not be tightly coupled. By defining interfaces at the boundaries of your application, you will allow external libraries to implement functionality hidden within your actual application. Confused? So was I at first.

Layers

Although not explicitly defined by Alistair Cockburn, I'd like to think of three layers. A domain layer, an application layer, and finally an infrastructure layer.

Domain

Your domain layer will contain all objects that define the domain. The models you create for it live in this layer, as do the interfaces/ports to retrieve data regarding your models. Also the exceptions that can be thrown by your interface implementors will reside in this layer.

Application

Although arguable, I'd like to store all actions that can be taken on the models from the domain layer in the application layer. Since actions are what your application actually is. Working in DDD (Domain Driven Development) and CQRS these will typically be Commands and Queries, as well as the Events that will be triggered after Commands are executed. Events will not be covered in this post.

Infrastructure

The infrastructure layer will be where all the actual implementations/adapters of interfaces/ports defined in the mentioned will be. For instance, if we have a Post model, with a PostRepositoryInterface defined in our Domain layer, we'll implement the repository here.

Creating the app

So how does this work in practice? We'll start out by just creating in an empty folder for the application, and creating a src folder in it for the actual source files. Since we'll be pushing this to GitHub, we'll also initialize git and add an ignore file.

$ mkdir app && cd app && mkdir src
$ git init
$ touch .gitignore

Next, we'll create the three folders for layers, since we what sources are in what layer.

$ mkdir src/Domain
$ mkdir src/Application
$ mkdir src/Infrastructure

Now we're finally going to do some coding, yay! Let's create a Post model, and define some basic ways to retrieve Posts. We want all Post related objects to be in the same namespace for convenience. So create a folder named Post in your domain folder, and create a Post.php file to store your model definition in. Since these files are all just for educational purposes, we'll keep 'em simple.

// src/Domain/Post/Post.php

class Post
{
    public $id;
    public $title;
    public $contents;
}

Now define the interface to retrieve instances of a post. This will be an interface and not the actual implementation of the retrieval from a data source. Our domain does not care where to store our data, it just cares about getting the data.

// src/Domain/Post/PostRepositoryInterface.php

interface PostRepositoryInterface
{
    public function create(Post $post);
}

For completeness, we'll also add a generic PostException. I'd recommend more specific exceptions, like PostNotFoundExceptionwhen you're actually going to use the code for more than education.

// src/Domain/Post/Exception/PostException.php

class PostException extends Exception {}

Next up, the Application layer! We're going to create one Command to create a Post (and obviously store it "somewhere"). Queries are similar to Commands in type of handling. The main distinct between the two, is that Commands should not return any output. Commands should be fired, and assumed successful unless there is an exception thrown. Queries are meant to return output, however, they should not alter state. Simply put, Commands are meant to write, and Queries are meant to read.

So there is going to be a generic way to execute commands. We'll want to create a CommandInterface so we're able to typehint them, a CommandBusInterface (remember, we don't care here how it executes those commands, we just want it to execute them), and finally a CommandHandlerInterface. The CommandHandlerInterface implementors will contain the logic that the Command describes.

// src/Application/Command/CommandBusInterface.php

interface CommandBusInterface
{
    public function execute(CommandInterface $command);
}

Now the simple CommandInterface,

// src/Application/Command/CommandInterface.php

interface CommandInterface {}

and the CommandHandlerInterface.

// src/Application/Command/CommandHandlerInterface.php

interface CommandHandlerInterface
{
    public function handle(CommandInterface $command);
}

Now we're going to create the CreatePostCommand and its CreatePostHandler. These will contain actual application logic. As you'll see, the CreatePostCommand will be nothing more than an immutable data object. It will not create a Post instance, just the scalar attributes needed to create the post. We do not want it to be an object, since objects are references, and commands should not be able to alter after creating them.

// src/Application/Command/Post/CreatePostCommand.php

class CreatePostCommand implements CommandInterface
{
    private $title;
    private $contents;

    public function __construct($title, $contents)
    {
        $this->title = $title;
        $this->contents = $contents;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function getContents()
    {
        return $this->contents;
    }
}
// src/Application/Command/Post/Handler/CreatePostHandler.php

class CreatePostHandler implements CommandHandlerInterface
{
    private $postRepository;

    // We'll use constructor injection to inject this handlers
    // dependencies. We'll typehint the interface since we do not
    // care where to store it at this point.
    public function __construct(PostRepositoryInterface $postRepository)
    {
        $this->postRepository = $postRepository;
    }

    // Since we are not permitted more specific types of the
    // CommandInterface, we'll have to check its type. 
    public function handle(CommandInterface $command)
    {
        if (!$command instanceof CreatePostCommand) {
            throw new Exception("CreatePostHandler can only handle CreatePostCommand");
        }

        $post = new Post;
        $post->id = uniqid();
        $post->title = $command->getTitle();
        $post->contents = $command->getContents();

        $this->postRepository->create($post);
    }
}

Out Application part is done for the moment. We've created the ports/interfaces to run Commands, which should be implemented in the Infrastructure layer. And we've created a Command that should be used to create a new Post and store it.

In our Infrastructure layer, we'll implement the CommandBusInterface, or "port", to our Application layer. This can be very simple, depending on your needs. As in this application we'll need direct feedback (through the throwing of exceptions) whether our Command is successful, we'll create a SynchronousCommandBus.

// src/Infrastructure/CommandBus/SynchronousCommandBus.php

class SynchronousCommandBus implements CommandBusInterface
{
    /** @var CommandHandlerInterface[] */
    private $handlers = [];

    public function execute(CommandInterface $command)
    {
        $commandName = get_class($command);

        // We'll need to check if the Command that's given
        // is actually registered to be handled here.
        if (!array_key_exists($commandName, $this->handlers) {
            throw new Exception("{$commandName} is not supported by the SynchronousCommandBus.");
        }

        return $this->handlers[$commandName]->handle($command);
    }

    // Now all we need is a function to register handlers
    public function register($commandName, CommandHandlerInterface $command)
    {
        $this->handlers[$commandName] = $handler;

        return $this;
    }
}

For testing purposes, we'll create a simple in-memory PostRepository.

// src/Infrastructure/Persistence/PostRepository.php

class PostRepository implements PostRepositoryInterface
{
    public $posts = [];

    public function create(Post $post)
    {
        $this->posts[] = $post;

        // Obviously, this is for testing purposes only
        echo "Post with id {$post->id} was created.";
    }
}

We're all set now! We should be able to test it by creating a SynchronousCommandBus, register our Command and Handler and try to execute it. It should run and output "Post with id ### was created.".

// test.php

$commandBus = new SynchronousCommandBus();

$postRepository = new PostRepository;
$commandHandler = new CreateUserHandler($postRepository);

$commandBus->register(CreatePostCommand::class, $commandHandler);

$command = new CreatePostCommand(
    "This is the post title", 
    "And this is the content"
);

$commandBus->execute($command);

That's all for now! I hope you enjoyed the basics of hexagonal architecture. If you have any questions or comments, please leave a comment below this post.

Yordi This article was first posted on yordipauptit.com.

References: