Hexagonal Architecture

Also known as the Ports and Adapters architecture. This architecture clearly separates core logic from the input and output infrastructure, dividing the system into loosely-coupled interchangeable components. Throughout the years, it grew in popularity to become one of the most widely recognised types of software architecture.


What is Hexagonal Architecture?

Introduced by Alistair Cockburn in 2005 as an alternative to the traditional layered architecture. He states this architectural pattern will:

"Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases."

The Principle

Domain layer is the heart of the software, it handles the business logic of the system. The goal is to have domain code completely technology-agnostic. It doesn't need to know about the system interactions or implementations (databases, GUIs, file systems, web APIs, CLI etc) and as such should not hold any references to frameworks, technologies and real-world devices.

Also known as the Adapters and Ports architecture. External sources are adapted by classes which adhere to interfaces (ports). The adapters are used to transport information (e.g a database or a file system) to and from the domain or use the domain to achieve a goal.


Domain Layer

The Domain layer is protected by a boundary (command, command bus and handler) within the Application layer which sits outside of the Domain layer. Say you want a simple app which creates posts. You'd create a Post model (aka an entity object) in your domain namespace.


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

Once we've done that, we'll need a define an interface to retrieve post instances. Our domain does not care where to store our data, it just cares about getting the data.


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


Application Layer

The Application Layer sits outside of the Domain layer. It manages events from outside layers and the inner core (Domain layer).

Within the Application layer we'll create a command which creates a post, along with its interface. It's important to note that the command is only the message detail, it implies change and expresses intention. The command handler uses the command.


interface CommandInterface 
{
    public function getTitle(): string;
    public function getContents(): string;
}

class CreatePostCommand implements CommandInterface
{
    public function __construct(private string $title, private string $contents)
    {
        
    }

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

    public function getContents(): string
    {
        return $this->contents;
    }
}


Next up, we'll create a handler and its interface.


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

class CreatePostHandler implements CommandHandlerInterface
{
    public function __construct(private PostRepositoryInterface $postRepository)
    {

    }

    public function handle(CommandInterface $command): string
    {
        $post = new Post;
        $post->id = uniqid();
        $post->title = $command->getTitle();
        $post->contents = $command->getContents();

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

        return $this->title;
    }
}


FInally, we've got a command bus. A command bus takes a command and matches it to a handler.


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

class SynchronousCommandBus implements CommandBusInterface
{
    private array $handlers = [];

    public function execute(CommandInterface $command): string
    {
        $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 SynchronousCommandBus");
        }

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

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

        return $this;
    }

}


Adapter & Port example

PostRepository is an Adapter. PostRepositoryInterface is the port. The adapter must adhere to the port. In theory several adapters could implement the same port (i.e various database services). The advantage of using this architecture means we can easily switch out PostRepository with another adapter which implements the PostRepositoryInterface interface (port). All this without changing our Domain code at all.


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

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

        // Obviously, this is for testing purposes only but it could go off to a database here or another web service.
        echo "Post with id {$post->id} was created";
    }

}


Test Code

$postRepository = new PostRepository; // Example of an Adapter

// Application Layer (contains the Domain layer boundary of the command bus, handler and command)
$commandBus = new SynchronousCommandBus();
$commandHandler = new CreatePostHandler($postRepository); // Dependency Inject the Adapter
$commandBus->register(CreatePostCommand::class, $commandHandler);

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

// Command bus executes the commands handler.
// The handler (in this example) accesses the Domain layer and uses the injected Adapter dependency.
$commandBus->execute($command);