Hexagonal Architecture Is Powerful
Hexagonal Architecture divides work inside and outside of an app instead of into layers. This overview covers the benefits and uses of the Hexagonal style in your work.
Join the DZone community and get the full member experience.
Join For FreeContinuing our journey through various architectural styles, we’re now headed for Hexagonal Architecture, also known as Ports and Adapters.
What Is Hexagonal Architecture?
Hexagonal Architecture is an architectural style that moves a programmer’s focus from conceptual layers to a distinction between the software’s inside and outside parts. The inside part consists of what we would call application and domain layers in a Layered Architecture – its use cases and the domain model it’s built upon. The outside part consists of everything else – UI, database, messaging, and other stuff. The connection between the inside and the outside part of our application is realized via abstractions called ports and their implementation counterparts called adapters. For this reason, this architectural style is often called Ports and Adapters. The metaphor of a hexagon comes from the discreteness of the ports – each one is distinct and there will be a few of them, and for the visualization purpose – to avoid one-dimensional thinking about the architecture (remember that in Layered Architecture all dependencies go in one direction, right?).
Similarly as in Layered Architecture, there are some principles to be followed:
- The inside part knows nothing about the outside part.
- You should be able to use any adapter that fits a given port.
- No use case or domain logic goes to the outside part.
The Essence of Hexagonal Architecture
I see two important concepts that really make Hexagonal Architecture distinct and lead to its power: separating the core of your application and thinking in terms of ports and adapters.
By separating the core, I mean making the use case and domain logic free of any outside dependencies. We’re putting the application behavior and business rules in terms of abstract ports, making our application logic agnostic to the delivery model. Thanks to this, the most crucial logic of our application is pure and clean and can be understood without the clutter of different technologies used to make it work in the production environment. We can also test this “most crucial logic” without spinning up all those technologies.
The second important concept is thinking in terms of ports and adapters. In Layered Architecture, there is a significant distinction between the presentation part and the infrastructure part. The latter is “so important” that we allow the application and domain logic to depend directly on it. In Hexagonal Architecture this distinction is non-existent – both presentation and the infrastructure are just the outside. We simply provide abstract ports and implement adapters to them, regardless of the type of actor the inside is communicating with. This means we can swap out the UI, the same way we swap out the database. We can easily swap out both for the testing purposes and there won’t be any significant implementation differences.
We should also note here that Hexagonal Architecture does not make any assumptions when it comes to code organization and, similarly to Layered Architecture, it does not tell us much about system’s design itself.
Implementing Hexagonal Architecture
I don’t know if I described the concept of ports and adapters simply, but it actually is so. As most of you probably guessed or knew already, the natural counterpart of a port in Java is an interface and the adapter is the one described in the GoF book.
Basic Example
Let’s start small. Imagine we’re implementing a console Tic-Tac-Toe game and we want to ask the player for his next move. We could, of course, bash a Scanner somewhere and read it directly, but that would not be very testable. We would also have problems to swap out the human player with a computer – should we make the computer write to standard input? The Hexagonal approach would be to create a port for the communication with the player, as the input handling is the part of the outside and should not be mixed with our Tic-Tac-Toe inside logic:
public interface PlayerPort {
Coordinates nextMove();
}
Then, we would create an adapter to this port that actually does the job:
public class ConsolePlayerAdapter implements PlayerPort {
@Override
public Coordinates nextMove() {
// actual console reading stuff
}
}
Hexagonal Pet Clinic
Now that we understand the basics, we can move to implementing something more serious. Once again, we will take a look at the Spring Pet Clinic project and try to apply the newly learned architectural principles.
Look at the current packages and classes and try to identify what’s the inside and what’s the outside:
Obviously, the controllers and config classes are parts of the outside. Domain classes like Owner or Pet will be parts of the inside. The repositories are interfaces, which fits our idea of a port, but they can contain dependencies on Spring and use a query language in @Query annotations. This leads to an interesting question…
Are Spring, JPA, and Others Part of the Outside?
I believe you can answer this question both ways and still consider your architecture Hexagonal. As we already said, by hiding certain technical details behind a port, we remove some clutter from our use case/domain logic and gain more flexibility as the port can have multiple adapter implementations. The more appropriate questions are then:
- Does it clutter your business code too much?
- Do you want to have a flexibility of swapping stuff at the cost of adding extra indirections and thus complexity?
This seems like a black and white problem, but there are some shades of gray in there actually. One could e.g. accept annotations as a necessary evil but refuse to use framework’s utility classes directly and expose a port for each of them. The final choice is yours, no strong rules here (at least in my world).
Back to the Clinic
Let’s assume that we ignore framework dependencies for a moment and try to deal with the rest. The first step would be to separate the outside classes from the inside classes in the current structure:
As you can see, we just had to create a presentation layer, same as we did in the Layered Architecture article.
The second step towards more a Hexagonal Architecture would be to get the use case logic out of the controllers. Let’s do it for one of the use cases – scheduling a visit:
@RequestMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new", method = RequestMethod.POST)
public String processNewVisitForm(@Valid Visit visit, BindingResult result) {
if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm";
} else {
this.visits.save(visit);
return "redirect:/owners/{ownerId}";
}
}
From this method, we can derive that the application presents the owner page if Visit data is valid and prompts the user to correct the data otherwise. Let’s create a port that handles the UI in these cases:
public interface UserInterfacePort {
void promptForCorrectVisit();
void presentOwner();
}
With this port in place, we can abstract specific view details out of our use case logic:
public class VisitService {
private final VisitRepository visits;
public VisitService(VisitRepository visits) {
this.visits = visits;
}
public void scheduleNewVisit(Visit visit, BindingResult result, UserInterfacePort userInterfacePort) {
if (result.hasErrors()) {
userInterfacePort.promptForCorrectVisit();
} else {
this.visits.save(visit);
userInterfacePort.presentOwner();
}
}
}
The last step would be to create an adapter and use it in our controller:
public class ViewNameUiAdapter implements UserInterfacePort {
private String viewName;
@Override
public void promptForCorrectVisit() {
this.viewName = "pets/createOrUpdateVisitForm";
}
@Override
public void presentOwner() {
this.viewName = "redirect:/owners/{ownerId}";
}
String getViewName() {
return viewName;
}
}
@RequestMapping(value = "/owners/{ownerId}/pets/{petId}/visits/new", method = RequestMethod.POST)
public String processNewVisitForm(@Valid Visit visit, BindingResult result) {
ViewNameUiAdapter viewNameUiAdapter = new ViewNameUiAdapter();
visitService.scheduleNewVisit(visit, result, viewNameUiAdapter);
return viewNameUiAdapter.getViewName();
}
It’s easy to see how powerful the application service is and how stupid the controller is in the Hexagonal approach. We could literally implement that part of Pet Clinic to work in the console while saving the visits to a text file — and the service logic would remain untouched (as long as we keep using Spring). On the other hand, it’s even easier to see that we replaced a single small method in a single class with five methods in four classes.
Benefits of a Hexagonal Architecture
- Easy to learn – it’s just ports and adapters baby.
- Application and domain purity – the most important code in your application is not cluttered by technical details.
- Flexibility – you can swap out adapters easily.
- Testability – you can provide test replacements for the outside dependencies without using extra mocking tools.
Drawbacks of a Hexagonal Architecture
- Heaviness – it’s a lot of extra classes, especially when you have only one UI and one database to support.
- Indirections – you need to search for port’s implementations and be aware of your system’s state to follow the flow of control.
- Confusing to apply with frameworks – as we’ve seen, it’s not always clear what you should consider the outside.
- No code organization guidelines – you need to keep things tidy by yourself; it’s just ports and adapters baby.
When to Apply Hexagonal Architecture?
I’ve got mixed feelings for the Hexagonal Architecture. That’s because the benefits it builds upon do not require an all-in approach:
- Purity – most applications that I work with use request/response communication, which means no UI clutter and I’m free from other details by using patterns like Repository or Gateway.
- Flexibility – you can create interfaces when you really need to swap something out.
- Testability – with new frameworks, the concept of swapping something out for test purposes is less relevant than in the past. Also, I’m usually fine with using mocking tools for test doubles.
At the same time, I totally agree that we should strive to keep our business logic clutter-free and we should keep things as testable as possible. Therefore, use common sense to evaluate your situation. In general, I think that this might be more of a problem with monoliths that I’m happy not to work with right now.
Summary
To sum things up, Hexagonal Architecture is an approach that divides our software into an inside and an outside part. The former contains use case and domain logic, while the latter contains technical stuff like UI, databases and messaging. The two parts are connected using ports, exposed by the inside, and adapters, implemented by the outside. By applying this approach, we make our most important code free of unnecessary technical details and we gain flexibility and testability. Since going “full hexagonal” is not required to get the benefits it provides, I’d be skeptical to do that and instead apply interfaces and patterns selectively.
Published at DZone with permission of Grzegorz Ziemoński, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments