A good architect maximises the number of decisions not made
— Robert C. Martin in Clean Architecture
Most web services I worked with use a MVC-style architecture, with a
handlers package and, if at all, a
While this may be great for small services, the
introduces a big problem: It mixes transport logic with business logic.
This makes refactoring hard (just imagine switching your HTTP framework)
and therefore forces you to make decisions about these kind of things
before even starting the project.
So when I started a new project recently, I decided to use the hexagonal
architecture (aka Ports and Adapters) and so far I’m really happy.
Here’s what I like about it:
All dependencies point inward
All outer layers like transport, storage or logging depend on the business logic, but never the other way around. The business logic is agnostic of any other layer, it doesn’t care, how it’s served or how data is stored, it’s pure code. This makes changing it super easy.
You can defer decisions
You can defer many decisions about technologies used until you really need
You could, for example, start out with an
inmem package for storage and
only decide which database to use when you really need persistence.
Refactorings are easy
Since everything is contained in it’s domain, refactoring the transport package, for example, is just refactoring transport code. There is pure separation of concerns and everything has a clear place.
Testing business logic is easy
Since you only have pure code without layer dependencies, you can easily
inmem package as storage for example.
No need for mocking complex database structs, which just cost time.
Why a hexagon
It actually doesn’t matter how many sides the shape has. The point is, that there are many. The center represents the business logic and every side represents a port into or out of our application (that’s why it’s also called Ports and Adapters).
A real-world example
For this post let’s just assume that we’re building an API to manage an inventory of some sort – it’s still similar to the application I’m building. You should be able to list all items in the inventory and logged-in users should have basic CRUD access.
To set things up, I created these packages:
user: Domain logic for user management (login, logout, validate)
inventory: Domain logic for inventory (list, create, read, update, delete)
inmem: In-memory storage for user and inventory
http: HTTP transport logic, does de-/encoding and request handling
inventory packages define an interface for the storage
This way we can inject any struct that implements that interface and keep
the separation of concerns.
Here’s how our
main.go would look like, simplified:
In real-world applications, we always have to make compromises and fight for clear separation of concerns. This is a list of problems I ran into while building the application.
Let’s say we want to implement a token-based authentication and we need to
protect some routes of the
Where should we get the token-validation function from?
Getting the user service via our
NewService constructor would result in
What I ended up doing was a
http.Guard(userService) function, which
returned a http middleware (
func (next http.Handler) http.Handler) which
parses the token and validates with
The middleware is then passed into the
transport.InventoryHandler and wrapped
around methods that needed protection. This way, there are no new
Another problem are Go tags (read up).
Let’s say we need some for transport (e.g.
json:"id") and database
db:"first_name"), but they’re attached to the model, which lies in
the domain logic.
A fix would be having the model structs duplicated in the other packages
with the needed tags, but this introduces a lot of duplicate code and
unnecessary complexity, so I decided to leave it as-is right now until
I found a better solution.
I’m really happy with my application and I don’t think I’ll go back to
MVC-style applications any time soon.
I encourage you to try and build a service using a hexagonal architecture and share your experience – obviously doesn’t have to be Go. If you want to read more on hexagonal architecture, I recommend checking out go-kit. There is also a GopherCon 2018 talk by Matt King, here is the script until the videos are up. Also if you have comments or suggestions, hit me up 🤙🏻.