Developers Club geek daily blog

1 year, 8 months ago
Hexagonal architectureOn recent Laracon NYC I read the paper on hexagonal architecture. In spite of the fact that I received positive reaction of listeners, it seems to me that there were people who would like to gain a little more complete idea of what is it. Certainly, with examples. It is my attempt to expand that report.

  1. Video from the report
  2. Slides


In my opinion, this architecture is an excellent example of how the structure of the application has to be created. Moreover, when I wrote the projects on Laravel, I, even without knowing it, quite often used the ideas mortgaged at the heart of hexagonal architecture.

Being one of options puff architecture, hexagonal means separation of the application into the separate conceptual layers having a different zone of responsibility and also regulates how they are connected with each other. Dealing with this type of architecture, we can also understand as, why, and why at an application programming interfaces are used.

Hexagonal architecture, not new development approach using frameworks. On the contrary, it only generalization "the best the practician" — the practician new and old. I enveloped these words in quotes that people did not perceive them absolutely literally. The best practicians who work for me can not work for you — everything depends on a task and the pursued purposes.

This type of architecture adheres to classical ideas with which developers at application design come up: separation of an application code from a framework. Let our application form in itself, but not based on a framework, using the last only as the tool for a solution of some tasks of our application.

Introduction


The term "Hexagonal Architecture" was entered (as far as I know) by Alistair Kobern. It perfectly described the basic principles of this architecture on the website.

Basic purpose of this architecture:
Allows to interact with the application to both the user, and programs, automatic tests, scripts of batch processing. Also allows the application to be developed and ottestirovanny without any additional devices or databases.


Why Geksagon


In spite of the fact that the architecture is called hexagonal that has to indicate a figure with a certain quantity of edges, the main idea nevertheless is that there is a lot of edges at this figure. Each edge represents "port" of access to our application or its communication with the outside world.

The port can be provided as any conductor of incoming requests (or data) to our application. For example, through HTTP port (requests of the browser, API) HTTP requests which are converted into commands for the application come. Similarly different managers of queues or anything interacting with the application under the protocol of transfer of messages can act (for example AMQP). All this is only ports through which we can ask the application to make some actions. These edges make a set of "the entering ports". Other ports can be used for data access from the application, for example port of the database.

Architecture


Why we in general started talking about architecture? And all because we want that our application had two indicators:

  • high podderzhivayemost
  • low level of a technical debt


Actually, both of these indicators express same, we want that it was convenient to work with the application, and also it has to allow to make changes in the future quickly.

Hexagonal architecture

Podderzhivayemost


The Podderzhivayemost is defined by absence (or minimization) a technical debt. Supported will be that application at which change we will receive minimum possible level of a technical debt.

The Podderzhivayemost is the concept expected wide intervals of time. At early stages of development it is easy to work with the application — it is not up to the end created and developers create it proceeding from the initial solutions. At this stage adding of new functionality or libraries happens easily and quickly.

However eventually, it becomes more difficult to work with the application. Adding of new functionality can clash with already available. Bugs can testify to more common problem which solution will demand big changes in a code (and also simplifications of difficult code locations).

Mortgaging good architecture at early stages we can prevent these problems.

So after all, what type we want to receive a podderzhivayemost? What metrics can tell us that we have a high podderzhivayemost of our application?

  1. As little as possible other parts have to affect changes in one part of the application;
  2. Adding of new functionality should not demand any large-scale changes in a code;
  3. The organization of the new interface for interaction with the application has to lead to minimum possible changes of the application;
  4. Debugging has to include as little as possible makeshifts and hak;
  5. Testing has to happen rather easily;
  6. As there is no perfect code, the word "has to" it is worth perceiving as what it is necessary to aim but no more at. We try to create our application simpler in support, attempts to make it "ideal" will lead to dead times and mental energy.


If you think that you contracted from "the correct way" at a task solution, just finish it up to the end. Return to it later, or leave is able "works do not touch". In the world there are no applications with ideally written source code.


Technical debt


The technical debt, is a debt which we should pay for our (bad) solutions. And we pay this debt time and feeling of disappointment.
All applications include the initial level of a technical debt. We are forced to work within and taking into account restrictions of the mechanisms of data storage, programming languages, frameworks, commands and the organizations selected by us!

The bad architectural concepts made at early development stages in total will pour out to us the increasing and big problems.

Each bad solution as a rule leads to crutches and haka. And the fact that the solution bad, is not always obvious — we can just make a class which "does too much", or we will accidentally mix solutions of several problems.

The insignificant, but bad decisions made during development can also lead to problems. Fortunately, it is normal does not lead to large-scale problems to which can lead design errors at early design stages. The good base reduces the growth rate of a technical debt!

So, we want to minimize quantity of bad solutions, especially at early stages of the project.
We discuss architecture in order that we could concentrate on increase of a podderzhivayemost and reduction of a technical debt.

How we can create our application supported?

We simplify modification of the application.

What can we make to simplify modification of the application? We can define those parts of the application which can change and separate them from what will remain invariable.

We will return to this statement still few times.

Interfaces and implementation


Let's distract for a while and we will discuss something simple (as it can seem) from the world of OOP: Interfaces.
Not all languages (for example JavaScript, Python and Ruby) have explicit interfaces, however from the conceptual point of view of the aim which is pursued by use of it can be easily reached in these languages.

You can understand a certain contract which regulates that is necessary for the application as the interface. If the application can or has to contain several implementations, then we can use interfaces.

In other words we use interfaces every time when we plan several implementations of one interface.

For example if our application has an opportunity to send notifications to the user, then we can make SES the notifikator using Amazon SES, Mandrill the notifikator using Mandrill or other implementations the using different services for sending mail.
The interface guarantees that specific methods are available to use in our application, regardless of the implementation which is the cornerstone.

For example the interface of our notifikator can look so:

interface Notifier {

    public function notify(Message $message);
}


We know that any implementation of this interface has to contain a method notify. It allows us to define dependence on this interface in other parts of our application.

To the application all the same what implementation it uses. To it it is important that we have a method notify and we can use it.

class SomeClass {

    public function __construct(Notifier $notifier)
    {
        $this->notifier = $notifier;
    }

    public function doStuff()
    {
        $to = 'some@email.com';
        $body = 'This is a message';
        $message = new Message($to, $body);

        $this->notifier->notify($message);
    }
}


In this example implementation of a class SomeClass does not depend on specific implementation but only demands existence of a subclass Notifier. It allows us to use SES, Mandrill or any other implementation.

Thus, interfaces are the convenient tool for increase in a podderzhivayemost of your application. Interfaces allow us to change easily a method of sending notifications — for this purpose we need to add one more implementation and all.

class SesNotifier implements Notifier {

    public function __construct(SesClient $client)
    {
        $this->client = $client;
    }

    public function notify(Message $message)
    {
        $this->client->send([
            'to' => $message->to,
            'body' => $message->body]);
    }
}


In an example above we used the implementation using the service from Amazon which is called by Simple Email Service (SES). But what if we after all want to use Mandrill for sending notifications? And if we want to send notifications by the SMS through Twilio?

As we already showed earlier, we can easily add one more implementation and switch between them on our whim.

// Используя SES Notifier
$sesNotifier = new SesNotifier(...);
$someClass = new SomeClass($sesNotifier);

// Или мы можем использовать MandrillNotifier

$mandrillNotifier = new MandrillNotifier(...);
$someClass = new SomeClass($mandrillNotifier);

// Это все равно будет работать вне зависимости от реализации, которую мы используем
$someClass->doStuff();


By the same principle our frameworks use interfaces. In fact frameworks represent for us advantage just because may contain so many implementations how many for developers it is necessary. For example, different SQL servers, systems of sending email-, drivers for a caching and other services.

Frameworks use interfaces because it allows to increase a podderzhivayemost of frameworks — simpler to add new features, it is simpler to expand for us a framework depending on our needs.

Use of interfaces helps us to encapsulate changes correctly. We can just add that implementation which is necessary to us now!

We go further


And what if we suddenly want to add additional functionality within separate implementation (and maybe all)? For example we can have a need to log work of implementation of our SES of a notifikator, for example for debugging of some problem which at us periodically arises.

The most obvious method, of course, it to correct a code directly in implementation:

class SesNotifier implements Notifier {

    public function __construct(SesClient $client, Logger $logger)
    {
        $this->logger = $logger;
        $this->client = $client;
    }

    public function notify(Message $message)
    {
        $this->logger->logMessage($message);
        $this->client->send([...]);
    }
}


Use logging directly in specific implementation can be a normal solution, but now this implementation is engaged in two things instead of one — we mix duties. Moreover, that if we need to add logging to all implementations of our notifikator? As a result we will receive a similar code for each implementation that contradicts the principle of DRY. If we need to change a logging method, then we should change a code of all implementations. Whether there is easier way to add this functionality so that to save a code supported? Yes!
Whether you learn any of the principles of SOLID which are mentioned by this head?

To make a code is purer, we can use one of my favourite templates of design — the Decorator. This template uses interfaces "to envelop" our implementation for adding of new functionality. Let's review an example.

// Добавляем логирование при помощи декорации
class NotifierLogger implements Notifier {

    public function __construct(Notifier $next, Logger $logger)
    {
        $this->next = $next;
        $this->logger = $logger;
    }

    public function notify(Message $message)
    {
        $this->logger->logMessage($message);
        return $this->next->notify($message);
    }
}


As well as other our implementations of a notifikator, class NotifierLogger implements the interface Notifier. However, as you perhaps noticed, he also does not think to send someone notifications. Instead, it accepts other implementation of the Notifier interface in the designer. When we ask a notifikator someone to notify on something, ours NotifierLogger will add these messages to a log and then will ask to transfer the notification this implementation of our notifikator.

As you see, our decorator logs messages and transfers them further to a notifikator that that actually sent something. You can also unroll an order so that logging will be executed after sending the message. Thus we will be able to add to a log not only the message which we were going to send, but also result of sending.

So, NotifierLogger decorates our notifikator, introducing functionality of logging.

Convenience is here that to our client code (in our case SomeClass from an example is higher) it do not care that we transferred it the decorated object. The decorator implements the same interface on which ours depends SomeClass.

Also we can integrate several decorators in one chain. For example, we can envelop email a notifikator implementation for work with the SMS to duplicate messages. In this case we add additional functionality (SMS) over sending email-.

As sets an example with a logger, we are not limited to adding of specific implementations of a notifikator. For example, we can organize updating of the database, or collecting of some metrics. Opportunities are boundless!

Now we have an opportunity to add additional behavior, at the same time saving a zone of responsibility of each class without overloading it with excess work. Also we can freely add additional implementations of functionality that gives us additional flexibility. It becomes much simpler to change such code!
The decorator is only one of many templates of design which uses interfaces for encapsulation of changes. Actually, almost all classical templates of design use interfaces.

Moreover, almost all templates of design are urged to simplify modification. And it not coincidence. Studying of templates of design (and also where and when to apply them) is quite important step on the way to acceptance good architectural concepts. As help for studying of templates of design I recommend the book Head First Design Patterns.

I will repeat: Interfaces it is the main method of encapsulation of changes. We can add functionality by means of creation of new implementation, or we can add behavior to the existing implementation. And all this will not mention all other code!

Having provided good encapsulation, functionality can be more simply subjected to changes. Simplification of change of a code raises an application podderzhivayemost (them simpler to change) and reduces a technical debt (we invest less time to make changes).

Perhaps about interfaces enough. I hope it helped to clear some why interfaces are necessary, and also to try out some templates of design which we used to raise an application podderzhivayemost.

Ports and Adapters


Well, at last we can start acquaintance to Geksakonalny architecture.

The hexagonal architecture, is puff architecture, also sometimes called by architecture of ports and adapters. Call it so because within this architecture there is a concept of different ports which can be used (are adapted) for use with other layers.

For example, our framework uses "ports" for work with SQL under any number of different SQL servers with which our application can work. In the same way we can implement interfaces for some key things and implement them in other layers of the application. It allows us to have several implementations of these interfaces depending on our requirements or for needs of testing. Interfaces will be our main means for decrease in coherence of a code between layers.
For parts of the application which can change create the interface. Thus we encapsulate changes. We can create new implementation or add additional functionality over existing.

Before getting acquainted closer with the concept of ports and adapters, let's consider the layers which are available within Hexagonal Architecture.

Hexagonal architecture

Layers: Code


As I already told, within hexagonal architecture, the application breaks into several layers.

The purpose of such submission of the application in the form of separate layers, is an opportunity to separate the application into different areas of responsibility.

The code within layers (and on their borders) has to describe how there has to be their interaction. As layers work as ports and adapters for other layers surrounding them it is important to have rules of interaction between them.

Layers interact with each other using interfaces (ports) and implementations of these interfaces (adapters).

Each layer consists of two elements:

Here we understand as a code that you could think. It is just some code by means of which we do some things. Quite often this code acts only as the adapter for other layers, but it can be and just some useful code (business of the logician, some services).

Each layer also has the border separating it from the next layers. On border we can find our "ports". As we already spoke, ports are than other as interfaces which indicate to other layers how there will be an interaction. We will consider a question of interaction of layers a bit later, for a start we should get acquainted with the layers which are available for us.

Layer of data domain (Domain Layer)


In the center of our application the layer of data domain is located. This layer comprises implementation business of logic and defines how external layers can interact with it.

Business of the logician is heart of our application. It is possible to describe it the word "charter" — rules to which your code has to submit.

The layer of data domain, and business of the logician, implemented in it, define behavior and restrictions of your application. It is what distinguishes your application from others, what gives to the application value.

If your appendix is contained difficult by business logic, different options of behavior, then at you the rich layer of data domain will turn out. If your application is a small superstructure over the database (and many applications and are those), then this layer will "be thinner".

In additives to business to logic (a kernel of data domain or core domain), in a layer of data domain it is often possible to meet additional logic, for example events of data domain (domain events, events which are thrown out in key points business of logic) and "scenarios of use" or use-cases (determination of what has to do our application).

What contains a layer of data domain, is a subject for the whole book (or series of books) — especially if you are interested in subject-oriented design (DDD). This methodology describes much in more detail methods at which we can implement the application as it is possible closer to terms of data domain, and so business for processes which we want to transfer to a code.

Let's review a small example of the "main" logic from a kernel of data domain:

<?php  namespace Hex\Tickets;

class Ticket extends Model {

   public function assignStaffer(Staffer $staffer)
   {
       if( ! $staffer->categories->contains( $this->category ) )
       {
           throw new DomainException("Staffer can't be assigned to ".$this->category);
       }

       $this->staffer()->associate($staffer); // Set Relationship

       return $this;
   }

   public function setCategory(Category $category)
   {
       if( $this->staffer instanceof Staffer &&
           ! $this->staffer->categories->contains( $category ) )
       {
           // открепляем сотрудника, в случае 
           // если он не может быть закреплен за заявкой из этой категории
           $this->staffer = null;
       }

       $this->category()->associate($category); // устанавливаем отношения объектов

       return $this;
   }
}


In an example above we can see restriction in a method assignStaffer. If the employee (Stuffer) is not assigned to the same category (Category), as our request (Ticket), we throw an exception.

Also we can see a certain behavior. If we need to change category of the request to which some employee is already assigned, we try to assign it to him again. If it does not leave, then we simply unfasten a zavyaka from the employee. We do not throw an exception, instead we give the chance to assign to the request of other employee when changing category.

We reviewed both examples of execution of business logic. In one scenario, we added restriction, in case of not observance of which we throw an exception. In another — provided a certain behavior — as soon as we change category, we have to leave an opportunity to assign the request to that employee who can process requests from new category.

As we already spoke, the layer of data domain may contain also auxiliary business to the logician:

class RegisterUserCommand {

    protected $email;

    protected $password;

    public function __construct($email, $password)
    {
        // сеттеры для email/password
    }

    public function getEmail() { ... } // возвращаем $email

    public function getPassword() { ... }   // возвращаем $password
}

class UserCreatedEvent {

    public function __construct(User $user) { ... }
}


Above we have auxiliary (but very important) data domain logic. The first class is some kind of command (the scenario of use in UML terminology) which defines how our application can be used. In our case this command just takes necessary data to register the new user. How exactly? We will return to it a bit later.

The second class, is an example of an event of data domain (Domain Event) which our application can start up on processing after creates the user. It is important to note that those things which can occur within our data domain belong to a layer of data domain. These are not system events, it seems pre-dispatch huk which are often used by frameworks for expansion of their opportunities.

Layer of the application (Application Layer)


At once our layer of applications sits at a layer of data domain. This layer is engaged exclusively orkestratsiy the actions made over entities from a layer of data domain. Also this layer is the adapter of requests from a layer of a framework and separates it from a layer of data domain.

For example this layer may contain a class processor which executes some skid case. This class processor accepts the entering data which came to it from a framework layer and will perform over them some operations which are required for our execution a skid case.

Also he can send for processing of an event (domain events) which occurred in a layer of data domain.

It is an external layer of the code making our application.

Of course, you could notice that outside of a layer of the application there is still "a framework layer". This layer contains an auxiliary code of our application (perhaps, the accepting HTTP requests, or sending email-a), but it is not the application.

Layer of a framework (Framework Layer)


Our application is wrapped up in a framework layer (it also call an infrastructure layer, infrastructure layer). As it was already told above, this layer contains a code which uses your application, but at the same time it is not in itself part of the application. Usually this layer is provided by your framework, but of course can include any third-party libraries, SDK and any other code. Remember all libraries which you connected through composer (we will assume that all of us write for PHP). They are not part of your framework, but nevertheless they are united in one layer. All this layer is necessary only for one — to carry out different tasks for satisfaction of requirements of our application.

In this layer services which interfaces are declared in inside layers are implemented. For example, the interface can be implemented here Notificator for sending notifications on email or through the SMS. Our application just wants to send notifications to users, it does not need to know how exactly it occurs (through email or through the SMS or still somehow).

class SesEmailNotifier implements Notifier {

    public function __construct(SesClient $client) { ... }

    public function notify(Message $message)
    {
        $this->client->sendEmail([ ... ]); // Send email with SES particulars
    }
}


One more example is the manager of events (event dispatcher). In a layer of level of a framework the interface of it declared in an application layer can be implemented. Besides, the application can be necessary to process any events, but why to write the manager of events? Most likely your framework already has ready implementation. Well or as a last resort we can connect ready library and use it as a basis for implementation of our interface of the manager of events.

Our layer of level of a framework can also act as the HTTP adapter of requests to our application. For example, we can assign to his shoulders acceptance of HTTP of requests, collecting of the entering data and a marshutization of requests/data to the corresponding controller. Further the controller can already start the specific scenario of use from an application level layer (instead of processing everything directly in the controller).

Thus this layer separates our application from all external requests (for example, made of the browser). This layer is the adapter of requests of the application.

Interaction of layers: borders


Now, when we dealt with what is in volume or number a layer, let's talk about how they interact with each other.

As it was already told above, each layer regulates how other layers can interact with it. If is more specific, then each layer is responsible for determination of how each following external layer will interact with it.

As the tool for this purpose we will be served by interfaces. On border of each layer we will find interfaces. These interfaces are ports for the following layers in which adapters will be implemented.

We already considered as it approximately works in examples with notifikator and the manager of events.

For example, the layer of the application will contain implementation of the interfaces (adapters to ports) declared in a layer of data domain. Also this layer will contain a code for other things specific to our application.

Let's pass through borders of each layer to understand as they are connected with each other.

Layer of data domain


On border of a layer of data domain we will find the means provided to them for communication to an external layer (an application layer).

For example, our layer of data domain may contain commands (the scenario of use). Higher we already saw an example of command RegisterUserCommand (user registration). This command quite simple, we can perceive it as simple DTO (Data Transfer Object).

So, the layer of data domain defines scenarios of use, but on it all. Its task is to tell an external layer "as you can use me". It is care of a layer of level of the application to manage business logic for implementation of some task. So, we have a communication method through borders. The layer of data domain declares how it can be used, and already the layer of the application uses these determinations (as a component) for execution of the declared scenario of use.

However our layer still needs to tell applications how it can process command for user registration. As for this purpose these layers should communicate, "on border" a layer of data domain we will place the following interface:

interface CommandBus {

    public function execute($command);
}


By means of this interface we told an application layer how "to execute" commands using the program bus (CommandBus). The program bus is arranged very simply — it just has to contain a method execute, which will contain implementation of processing of command.

So, we added the interface CommandBus in our layer of data domain, and now we will be able to implement it in an application layer. I will repeat, the interface is a port, and its implementation is an adapter to this port.

So, what we made:

  • We learned that our commands can be processed by different methods
  • Follows from the previous point that we can have several implementations of the program bus
  • As we found out earlier, the layer of level of the application decides how our layer of data domain and consequently (or a set of implementations) program buses it is logical to place implementation there will be used (as exactly there is defined as we will process these commands defined in a layer of data domain).
  • We separated a layer of data domain from an application layer by means of interfaces. Our program bus has a certain method of start of commands for execution.


Application layer


So, in this layer we have to implement the program bus. There will be it in the center of this layer, as well as other implementations (adapters) to other layers. We are interested now border of layers so we will return to it a bit later.

The layer of the application needs also to interact with external layers somehow. For example, how to be if we need to send notifications when processing commands? As everything that for this purpose needs to be eaten in a framework level layer was already told earlier. Your framework most likely already contains tools for sending email-, or we can connect library for sending the SMS. In a layer of a framework it is the most convenient to place implementation of services for sending notifications.

So, we look through communication between two layers. How you think what we will make? Correctly! Let's add one more interface:

interface Notifier {

    public function notify(Message $message);
}


The layer of the application defines as it will interact with a framework layer. Even it is more, it defines how the framework layer will be used, at the same time without building direct dependence on it. Interfaces (ports) and their implementations (adapters) allow us to replace with ease one adapter with another. Thus we do not become attached tightly to a framework layer.

Interfaces certain "on border" a layer of the application regulate as it will interact with a framework layer.
It is only idea, you should not perceive it as any specific rule. If you have a question "and what if I am required to use third-party library from a framework layer in a layer of data domain", then nothing terrible.

If so it is necessary, then just add the interface and implement it where it is necessary and using that library which is required to be used. Our purpose nevertheless to force our code to work. We nearby will leave if we endure every time as we violate the "rules" which are thought up by some dude or chuvikhy on the Internet.

As the main idea serves separation of duties here (the hint: when we define the interface, we do it) so we will be able to replace/change a functional component later. You should not perceive written here as some doctrines. There are "not no correct" approaches. I will repeat: there are no wrong approaches. There are only different options of how itself can shoot a leg.

If all of you still worry concerning use of third-party libraries in a layer of data domain, then can read about it in more detail according to the given reference.


Framework layer


Meanwhile we considered border of layers of data domain and the application. Both of these layers interact with layers which are under our control. The layer of data domain interacts with an application layer. An application layer — with a framework layer. And with whom the framework layer interacts?

With the outside world, of course! With the world filled with different protocols. As a rule it is some protocols based on TCP (such as HTTP/HTTPS). Definitely the layer of a framework contains a lot of code (all libraries which we use). You should not forget about the code written by us, for example controllers and implementations of the interfaces declared in inside layers.

What is on border between a layer of a framework and the outside world? Well of course interfaces (and implementations of these interfaces)!

The majority of frameworks already contain a code which is responsible for communication with the outside world. For example, implementation of processing of HTTP of requests, different implementations of SQL drivers, transports for sending email, etc.

Fortunately, by and large we do not have need to care for the organization of border between this layer and the outside world. It is a duty of our framework whose authors generous took care of it.

In fact it is a main objective of frameworks. They provide us means for interaction with the outside world, at the same time removing from us need again and again to write all this code.

In the majority a case we do not need to add something on framework layer border, however such cases happen. For example, if we do API for our application, at us will be added cares at the level of HTTP. Normally it is implementation of CORS, HTTP a caching, HATEOAS and other problems, specific to processing of HTTP of requests. For our application all these things can be important, but not for a layer of data domain or even a layer of the application.

Scenarios of use/command


Earlier I mentioned "scenarios of use" in article and "command". Time to deal with them came.

Within hexagonal architecture not only interaction of layers at the micro level is considered (interfaces, implementation of adapters to ports). In it the concept of limit of the application at the macro-level is also considered.
This border separates all our applications from all the rest (both from a framework and from interaction with the outside world).


We can strictly regulate how the outside world will be able to interact with the application. For this purpose we have to create "scenarios of use" (it is also possible to call them "commands"). Normally it is some classes whose names contain the description of actions which the application can make. For example, our command RegisterUserCommand defines that the application can register the user. Command UpdateBillingCommand, judging by the name, defines an opportunity to update information on payments of the user.
The scenario of use (command), this determination of how we can use our application.


Defining the scenario of use we can observe interesting ghost effects. For example, we can see how the application "wants" that we interacted with it. It strictly follows business to logic which is mortgaged by our application. Scenarios of use are also useful to adding of clarity between members of team of developers. We can plan these scenarios beforehand, or add them as necessary, but it becomes already more difficult to add some strange logic out of our scenarios of use. We will see at once that this logic badly corresponds about business by logic of the application.

How we define the scenario of use?


We already saw some examples — for this purpose we created some object submitting the specific scenario of use. Let's call such object "command" (Command). These commands then can be processed by "program bus" (Command Bus) of our application which will ask specific "processor" (Handler). The processor in turn will be already engaged in an orkestration of process of execution of the scenario of use.

So, for processing of commands at us three characters serve:

  • Command
  • Program bus
  • Processor


Problem of the program bus is to accept command for execution in a method execute. Then the program bus has to find and initialize, due to some internal logic, the processor of our command. And at last, we call a method handle the processor in whom there is a management of processing of command.

class SimpleCommandBus implements CommandBus {

    // остальные методы убраны для краткости

    public function execute($command)
    {
        return $this->resolveHandler($command)->handle($command);
    }
}


It is necessary to notice that we place the coordinating logic which you can often see in controllers in the processor. It is good, thus we separate the application from a framework layer, as allows us to receive big protection against changes in it (to simplify support of the application). Also it allows us to start the same code in different contexts (CLI, API challenges, etc.).

An opportunity to pereispolzovat logic in different contexts (for example web, http api, cli, vorker of queues) is also primary benefit of use of scenarios of use.

For example, the code implementing user registration through WEB, HTTP or the CLI interfaces can be with bigger identical.

public function handleSomeRequest()
{
    try {
        $registerUserCommand = new RegisterUserCommand( 
            $this->request->username, $this->request->email, $this->request->password
        );

        $result = $this->commandBus->execute($registerUserCommand);

        return Redirect::to('/account')->with([ 'message' => 'success' ]);
    } catch( \Exception $e )
    {
        return Redirect::to('/user/add')->with( [ 'message' => $e->getMessage() ] );
    }
}


The only difference between different contexts is how the user enters data and transfers them to command, and also an error handling method. By and large, all this care of a framework. To our application it is not important whether use it via the WEB INTERFACE, through HTTP API or still somehow.

Summarizing, the potential of scenarios of use is quite so shown. We can use them in any context in which ours can be used applications (HTTP, CLI, API, AMQP or other protocol of message queue)! In additives to it we created strong border between a framework and our application. In fact our application can be used separately from our framework (that positively affects when testing).

We can still have a need for a framework for implementation of some tasks of the application. For example, validation, event routing, email delivery and other problems which are solved for us by a framework. The limit of the application constructed on scenarios of use is only one of aspects of hexagonal architecture.

Hexagonal architecture

Scenarios of use serve for bigger separation of the application from a framework. It gives us a certain protection against changes in a framework (when updating, etc.) and also that is not unimportant, simplifies testing.

If to get into extremes, you in principle can replace a framework needlessly completely to rewrite the application. However, I consider that it is the most extreme case which can only be thought up. It is not the realistic scenario, at least because in it there is not enough sense. Everything that it is necessary for us, so it is more convenient to work it with appendix A not to indulge some metrics connected with exceptional cases.

Examples of commands, processor of commands and program bus


For a start, our application needs commands. Then, the application needs the bus which will execute commands. And at last, the processor who will be engaged in an orkestration of process of command execution is necessary for us.

Commands in itself it is quite simple piece. They just have to be. The purpose of their existence is determination of how our application can be used. Data which they demand, speak to us about what data are necessary to make some action. So there is no need to create the command interface. It just the name (the description of what we want to make) and DTO (Data Transfer Object).

class RegisterUserCommand {

    public function __construct($username, $email, $password)
    {
        // устанавливаем данные тут
    }

    // геттеры здесь
}


So, our command for registration of the new user quite simple. With its help we defined one of methods of use of our application.

Our processor will be slightly more difficult. They are connected with commands as for their work data which the command contains are necessary. In this place we observe close binding. Changes in business to logic can lead to change of the processor, and as a result, to change of command. And with it all OK as both processors of commands, and commands are tied on business to the logician.

If commands it is simple DTO (containing different data), processors comprise behavior which is used by the program bus. Processors, being part of a layer of the application, direct use of entities from a layer of data domain for the purpose of execution of commands.

In order that our program bus had an opportunity to execute any command, it has to have an opportunity to cause any processor. On it we have to implement the interface of the processor that to our program bus always was with whom to work.

interface Handler {

    public function handle($command);
}


The processor has to have a method handle, but we leave selection of the command which it processes on conscience of specific implementation of this interface. Let's look at how our processor of command can look RegisterUserCommand:

class RegisterUserHandler {

    public function handle($command)
    {
        $user = new User;
        $user->username = $command->username;
        $user->email = $command->email;
        $user->password = $this->auth->hash($command->password);

        $user->save();

        $this->dispatcher->dispatch( $user->flushEvents() );

        // Подумайте о том, чтобы также вернуть DTO, 
        // вместо того, чтобы возвращать класс содержащий поведение
        // Таким образом наш слой "представления" (вне зависимости от контекста)
        // не сможет случайно повлиять на наше приложения.
        // Он просто будет читать данные из результата
        return $user->toArray(); 
    }
}


Here we see how our processor directs use of entities of data domain, how we appropriate data, we save them and we send events for processing (if of course in our entities there were any events).
Also as in our example with interfaces where we by means of the decorator added additional behavior to our service of notifications, let's think what additional behavior we can fasten to our program bus or to the processor.


Finally, let's consider the device perhaps of the most interesting participant — the program bus (Command Bus).

Our program bus can have a set of implementations. For example, we can implement the synchronous program bus (starting commands in process of receipt). Or we can organize batch processing of commands, putting them in queue and executing them in a crowd when they are gathered enough. And there can be we in general will want to organize the asynchronous program bus which will publish our commands in the manager of queues for their further execution out of the current request of the user.

As implementations there can be a set, let's create the interface of our program bus:

interface CommandBus {

    public function execute($command);
}


We already considered the simplest implementation of this interface. Let's consider in more detail:

class SimpleCommandBus implements CommandBus {

    public function __construct(Container $container, CommandInflector $inflector)
    {
        $this->container = $container;
        $this->inflector = $inflector;
    }

    public function execute($command)
    {
        return $this->resolveHandler($command)->handle($command);
    }

    public function resolveHandler($command)
    {
        return $this->container->make( $this->inflector->getHandlerClass($command) );
    }
}


We have a component CommandInflector, who can use any strategy connecting the processor and a specific class of command. For example, it is possible just to return str_replace('Command', 'Handler', get_class($command));. Everything that is required for implementation of such approach, so it to store everything processors and commands adhering to specific structure of directories (if you use PSR compatible automatic loading). As you implement this binding, to solve to you. Everything depends only on you and needs of your project.

What else implementations of the bus of command, in addition to stated above "idle time", we can want to use? Well let's rename for a start simple into the synchronous program bus (SynchronousCommandBus), as it also does it. Executes commands synchronously, in process of receipt. It follows from this that we can want to implement the asynchronous program bus (AsynchronousCommandBus). As we already spoke above, this implementation will add command to message queue for execution by processors in process of availability, out of the current request of the user.

In addition to different implementations of the program bus, we can also add more decorators for already existing implementations. For example, I find useful to envelop the program bus the decorator for the purpose of validation of the entering data before their processing.

class ValidationCommandBus implements CommandBus {

    public function __construct(CommandBus $bus, Container $container, CommandInflector $inflector) { ... }

    public function execute($command)
    {
        $this->validate($command);
        return $this->bus->execute($command);
    }

    public function validate($command)
    {
        $validator = $this->container->make($this->inflector->getValidatorClass($command));
        $validator->validate($command); // бросить исключение в случае невалидных данных
    }
}


Here ValidationCommandBus is a decorator. Its task — to provalidirovat command and then to send it to the following bus for processing if everything is good. The decorator can also be the following bus (can for logging? Or for example audit of actions), or this implementation which will already be engaged directly in processing.

So, having an opportunity to combine different versions of program buses, and also considering an opportunity adding of additional behavior over it, we receive quite flexible tool for processing of commands of our application (scenarios of use)!

And all these things are most isolated from external layers of our application. Our layer of a framework (and further) does not dictate in any way as we can use the application. On the contrary, the application dictates how it can use.

Dependences


Meanwhile we only slightly mentioned concept of dependences. Within hexagonal architecture we adhere to a one-sided flow of dependences: outside inside. The layer of data domain (the central layer) should not depend on outside layers. The layer of level of the application has to depend on a layer of data domain, but not on a framework layer. The layer of a framework has to depend on an application level layer, but not on external factors.

Earlier we said that interfaces it is the main engine of encapsulation of changes. They allow us to regulate as layers will communicate with each other, at the same time avoiding excessive coherence between them. If we look at dependences, then in fact we will come up with the same idea. Let's understand.

Hexagonal architecture

Dependences: we go inside


It is simpler to dependence to provide when we prokidyvat data/logic "inside". When HTTP request comes to the server, we have to have some code which will process it, otherwise nothing will occur. Therefore, external HTTP request depends on our layer of a framework which is necessary for interpretation of request. If the framework managed to interpret request and it is necessary to define a route to the controller to which it corresponds, then this controller something to perform operations. Without application level layer he has nothing to do. The layer of level of a framework depends on an application level layer. That a tear of level of the application was whom to direct, it needs a layer of data domain. It depends on it to have an opportunity to perform required operation. The layer of data domain, depends (generally) on the behavior and restrictions defined in it.

When we try to trace a way of the request made from the outside in our application, dependences are looked through very obviously. External layers depend from internal, but they can ignore existence of deeper layers of the application completely. Everything that the layer has to know, is what method to cause and what data to transfer there. Parts of implementation are safely encapsulated there where it is necessary. That as we used interfaces well it shows.

Dependences: we come to light (DI)


When we come to light, everything becomes a little more difficult. Let's understand that does our application in response to some request namely as there is a processing of request and forming of the answer. I will give examples:

Our layer of data domain most likely will be needs access to the database to load from there entities. Therefore the layer of data domain depends on some data storage.

The application level layer after end of some task has to send the notification to the user. If we use email-a for delivery of notifications, and at the same time for we use AWS SES, then we can tell that our layer of level of the application has dependence on SES for the organization of delivery of notifications.

As you could notice, in our concept dependence of inside layers on what contains in external is traced! As if to us to invert the direction of dependences?

I specially used the word "invert". It had to hint you at the principle of Dependency Inversion or the principle of an iversiya of dependences. A letter D in SOLID. And for this purpose we are come to the rescue by interfaces again.

Let's use interfaces for inversion of dependences. With their help we can regulate as our layers will interact. And to us it is not interesting at all as these interfaces in other layers will be implemented. Thus the specific layer dictates to external layers that it wants from them, without depending on implementation. It is also inversion of dependences.

Our layer of data domain can declare the interface of a repository of our entities. This interface then will be implemented in one of external layers (most likely in a framework level layer). However because we declared the interface, we separated ours business to the logician from a specific method of data storage. It gives us the chance to change a layer of storage of our models when testing, or to replace it if that is demanded by need (it will be healthy if our application needs flexible scaling).

With a notifikator (service of notifications) similar situation. Our layer of level of the application knows only that it needs something for sending the notification. For this purpose we also made the interface Notifier. The application does not need to know how we will send these notifications. It only defines how to interact with it. Thus, our interface Notifier it will be declared in a layer of level of the application and it is implemented in a framework level layer. At the expense of it the direction of dependence changed, we inverted it. We told external layers as we are going to use them. As we can replace implementation of interfaces when we come, we can say that our layers are separated.

So, we will sum up the result. We use interfaces for designation of the direction of a flow of logic inside and outside. We used inversion of dependences to save the identical direction of dependences. We separated inside layers from external, and at the same time we continue by them to use!

Conclusion


We covered a lot of material! This article is result of quite volume research of architecture of a code. Not to focus attention on a reality, many things were generalized. All of us deal with the concept, but not with rules in the spirit of "do only this way!".

Summing up the result, the hexagonal architecture describes "good" practicians of writing of a code. It is not some specific approach to writing of a code of applications. It well is suitable as for the people using a specific framework and for the people preferring separate libraries to frameworks.

The hexagonal architecture allows on new to look at the same old principles with which developers get acquainted studying rules of creation of architecture of applications.

The request to send all comments on a transfer design (a la grammar, a punctuation, creation of the sentence is not pleasant) in a pm. I will be very grateful.

This article is a translation of the original post at habrahabr.ru/post/267125/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus