≡ Menu

Hexagonal Architecture: Scaling Across Multiple Concurrent Teams

One of the most important patterns to come from modern web development is the idea of Model-View-Controller – in effect, separating out our data logic from the view of said data, as well as the separation of how that data and view gets wired up. This works really well when you have a single team, or group of teams, working cross-functionally in the code. As teams begin to scale, some organizations opt to split teams among components that will be assembled together, usually via service calls. This idea of a Service-Oriented Architecture has allowed some amazing advances in the ability for large organizations and teams to be surprisingly nimble in how they build software.

However, sometimes teams are split into component teams which may not work at the same pace as the consuming teams. Or the service to be consumed may come from an external vendor or contractor. Worse, the data may not end up matching the expected API, or there may be data quality problems. These problems can drastically slow down the agility of software teams, especially if they have to wait for fixes from vendors or downstream teams.

Luckily, Alistair Cockburn, in his article on Hexagonal Architecture has provided the vision of a solution for this problem. By providing adapters, we can separate out the coupling to components external to us. Further, we can provide stubbed data, and when we are ready to connect to a real data source, we can use a Remote Facade or Service Layer to connect to it. Let’s take a look at how this would work to enable agility in concurrent teams working on various components of a system.

In this picture, you see a pretty basic three-tier setup. We have a Front-End, such as a web page, that gets data from a Data Services layer, which gets its data from several different data sources, some of which they have control over, and some which they don’t. In this example, we’ll have Team Grasshopper who works on the front-end, Team Sloths who provide the data services, and Vendor Valley who provides the vendor data sources.

The teams have been struggling to rapidly deliver software. Team Grasshopper requires the data to be sent to them to know how to communicate to the end-users. Team Sloths work as quickly as they can to provide those services, but have several other teams to provide for, and find that the data they attempt to pull from the vendor and public data sources isn’t always reliable or correct. Can hexagonal architecture help here?

A trick I learned from Alan Shalloway and Scott Bain was that if you needed an API, but were having trouble getting it, you simply built the API you needed. Since both teams are struggling to get the data they need, let’s start there. Both teams create thin API layers for the information they need to retrieve. These layers are usually known as Domain Models, with a famous implementation being Rails’ Active Record. In the diagram to the right, you can see that Team Grasshopper created an API layer for the communication to the services, and Team Sloths have created three separate API layers for each of the data sources they talk to.

With these APIs in place, each team can hook up a stubbed data provider to the other side, and build away without having to worry about the data on the other side yet. This is the central point of Hexagonal Architecture – once the port to the data is extracted, then multiple ways of providing data can be inserted without the calling system having to change.

However, it doesn’t end there for our teams, and this is where the power of this architecture really comes into play. As mentioned above, both teams have problems where the downstream services don’t provide the information they need. Team Sloths discovered that the information coming from the vendor data has extra characters prepended to the data, while Team Grasshopper discovered that the service which was supposed to provide 50 records at a time was only providing 1 record per call. They could, of course, go back to the calling systems and have them fix the problems, but that would slow down development, since they are dependent upon those changes. So what should they do?

The answer lies in the original name of Hexagonal Architecture: Ports and Adapters. In the Design Pattern world, an Adapter is used to make the interface of an existing class work with other interfaces without modifying either’s source code. For our teams, that means that if the API we expected is not present, we simply write a very thin adapter to wire it to the API we put into place. This can be seen in the design to the left. By using these adapters, Team Sloths can strip the extra characters coming from the vendor’s data store before passing the data up through their API. Team Grasshopper could use the adapter to loop the single call 50 times, aggregate the data, and pass it up through the API layer while waiting for Team Sloths to be able to modify the original service call. And once the native call is corrected, the adapter can simply be passthrough the call to the original service.

While the overall design may look complicated, each of the layers are designed to be extremely thin and provide only the separation absolutely required. While they are drawn as separate abstract concepts, the boundary does not (and probably should not) be web service calls or other heavyweight communication mechanisms. The overall goal is to segregate our dependencies and be able to respond to them while keeping a stable API for internal use.

An additional advantage comes from a testing perspective. Team Grasshopper can write end-to-end integration tests that pass with the adapters and mock data in place – and continue to pass as the mocks are removed and the real data is wired up – all the way down. This can help provide an early confidence boost of having the tests in place, and knowing that they can help catch any errors on the way down.

So if you have concurrent teams which are struggling with waiting on information and data from other teams, you might find that a little adapting can go a long way.