Leveraging Hexagonal Architectures with Vertical Slice Architecture and Feature Toggles

Bruno Alves
Level Up Coding
Published in
8 min readMar 16, 2024

--

Porto-Leixões Cruise Terminal Building Facade

In the realm of software engineering, architecture plays a pivotal role, but it is important to keep in mind there is no rule of thumb when choosing an architecture. Your choice depends on various factors, but if modifiability is one of them, you definitely should give a chance to hexagonal architecture.

There are multiple reasons to consider hexagonal architecture, especially when developing software product lines (SPLs) where adaptability and flexibility are essential to assembling features and shipping software products with different variants.

Thus, this article aims to focus on how to optimize SPLs by combining a hexagonal architecture with a vertical slice architecture (VSA) enhanced by feature toggles (FT).

Hexagonal Architecture

When Alistair Cockburn introduced the hexagonal architecture in a blog article in 2005 [1] his main motivation was to avoid infiltration of business logic in other layers of the software in such a way that was not possible to test it through automated tests and isolated from its runtime environment.

Several authors raised similar concerns and have proposed similar solutions like Clean Architecture, Onion Architecture, etc. The common ground between all of them is that — software components with similar responsibilities must be grouped into the same layers — the isolation of these layers will leverage the high cohesion and low coupling of the software.

Although, the wide majority of the software, very likely influenced by layered architecture and software patterns, has organized the software components (classes) around technical terms. For sure, project structures like the following one are very well-known to all of us:

Figure 1 — Horizontal layers

However, when projects follow this layering, they tend to end up in a big ball of mud, and it becomes difficult to keep our attention across such wide scopes.

Figure 3 — Big ball of mud

The problem is superficially referred by Martin Fowler in his Presentation Domain Data Layering article [2]:

“As an application grows, each layer can get sufficiently complex on its own that you need to modularize further. When this happens it’s usually not best to use presentation-domain-data as the higher level of modules. (…) once any of these layers gets too big you should split your top level into domain oriented modules which are internally layered.” [2].

In the next section, the proposed solution is analysed from a practical point of view.

Vertical Slice Architecture

The main concept of VSA says that we should split the code into domain-oriented layers.

Figure 2 — Vertical layer

But will this solve all the problems? Let’s suppose that our software engineering team is working on a project that follows VSA, and a new joiner will start working on an extension of a feature. Is there anything that prevents him from creating a dependency between the controller and the repository?

Figure 4 — Crossing layers

No, it isn’t. When putting all components inside the same package, there is no way to deter the young developer from creating a dependency between the presentation and persistence layer, even if those components strictly followed the encapsulation by making public only the interfaces of each layer.

In addition to this day-to-day problem, the solution illustrated in Figure 4 is unable to manage the direction between components, so it is weak in terms of dependency management. Let’s focus on a concrete scenario, suppose that we have an application dependent on multiple data sources with different SQL and NoSQL engines, or its API is being exposed through different architectural styles like REST or gRPC, when all the dependencies are placed together into the same module that brings limitations because it is very likely that some dependencies have conflict.

Therefore, the proposed solution is somehow in the middle of horizontal and vertical packaging, i.e. keep project modules organized by horizontal layers and packages organized by vertical feature layers.

Figure 5 — Horizontal layers using modules and vertical layers using packages

Multi-module projects offer multiple advantages:

  • Dependency Management: dependencies are easily managed and kept isolated from other modules;
  • Modularity: code organized into smaller modules with different concerns makes it easy to be replaced;
  • Testability: each module can be independently tested and more focused on the different concerns;
  • Builds: large projects organized into smaller modules can significantly speed up the build process using parallelization.

Feature Toggles

The main goal of this topic is not to dissect the multiple contexts in which feature toggles (FT) can be used, but rather how combining VSA and FT can leverage the modifiability of a software product line (SPL), especially when using hexagonal architectures.

The Software Engineering Institute, Carnegie Mellon University, defines an SPL as:

“A software product line (SPL) is a set of software-intensive systems that share a common, managed set of features satisfying the specific needs of a particular market segment or mission and that are developed from a common set of core assets in a prescribed way” [3].

This approach allows software companies to design products focused on reusing and assembling those core assets with built-in variation points.

As the costs of SPLs are mainly focused on the development process, architectures enhanced by modifiability strategies play an important role, since the production process has few production costs once new copies are made at practically no cost [4].

Therefore, FTs are one of the variation mechanisms that allow an SPL to be shipped with different runtime behaviours to satisfy the particular needs of a customer.

Figure 6 — Software Product Line (SPL)

Furthermore, FTs offer good advantages from a development point of view, it may look weird to release unfinished features, but it is a great way to:

  • Expose integration problems right from the beginning
  • Minimize code branches and promote smaller pull requests
  • Let teams work in parallel features
  • Train in-progress features through the CI/CD process
Figure 7 — SPL release management

There are multiple categories of FTs, however, we would like to concentrate our analysis on static configuration rather than dynamic routing. The main advantages of a static configuration are that it is managed via source control, moves through the Continuous Delivery pipeline, and is safely managed in highly scaled environments.

Since this article aims to provide a practical example of how to do it in a hexagonal architecture coded in Java and Spring, you might be interested in knowing more about FT. If that’s the case, I suggest you read the following Feature Toggle article by Martin Fowler [5].

Case Study

The current case study intends to present a practical example of a hexagonal architecture leveraged by VSA and FT. Our example represents two features, and in the following diagram, you can see their generic built-in components and allocations for each one of the hexagon layers.

Figure 8 — Hexagonal architectural and components allocation

Based on these components, we can unfold them into the following project structure, relying on modules for horizontal layers and packages for vertical.

my-project/

├── api/
│ ├── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ ├── feature1/
│ │ │ └── api/
│ │ │ └── Feature1Controller.java
│ │ └── feature2/
│ │ └── api/
│ │ └── Feature2Controller.java
│ └── pom.xml

├── domain/
│ ├── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ └── feature1/
│ │ │ └── domain/
│ │ │ ├── model/
│ │ │ ├── repository/
│ │ │ │ └── Feature1Repository.java
│ │ │ └── service/
│ │ │ ├── impl
│ │ │ │ └── Feature1ServiceImpl.java
│ │ │ └── Feature1Service.java
│ │ └── feature2/
│ │ └── domain/
│ │ ├── model/
│ │ ├── repository/
│ │ │ └── Feature2Repository.java
│ │ └── service/
│ │ ├── impl
│ │ │ └── Feature2ServiceImpl.java
│ │ └── Feature2Service.java
│ └── pom.xml

├── launcher/
│ ├── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ └── configuration/
│ │ ├── Feature1Configuration.java
│ │ └── Feature2Configuration.java
│ └── pom.xml

├── persistence/
│ ├── src/
│ │ └── main/
│ │ └── java/
│ │ └── com/
│ │ └── example/
│ │ ├── feature1/
│ │ │ └── data/
│ │ │ └── repository/
│ │ │ └── impl
│ │ │ └── Feature1RepositoryImpl.java
│ │ └── feature2/
│ │ └── data/
│ │ └── repository/
│ │ └── impl
│ │ └── Feature1RepositoryImpl.java
│ └── pom.xml

└── pom.xml

When using Spring, the components painted in red in Figure 8 must be annotated with the following stereotypes so that they are treated as managed components as indicated in the code snippet below:

@Controller
public class Feature1Controller {
}

@Service
public class Feature1ServiceImpl {
}

@Repository
public class Feature1RepositoryImpl {
}

Then a configuration for each feature must be created to automatically detect and register the feature beans during the component scan within the feature package, but only when the profile representing this feature is enabled as shown in the following code snippet:

@Profile("feature1")
@Configuration
@ComponentScan("com.example.feature1")
public class Feature1Configuration {
}

Finally, our application can be run by specifying the features that must be enabled as a command-line argument:

java -jar -Dspring.profiles.active=feature1,feature2 my-project.jar

Conclusion

In conclusion, when hexagonal architectures are enhanced with VSA and FT they can offer a powerful framework that will enable not just development teams to build modular software, but also product teams that can ship the same product to multiple customers, customized with different features and variations.

In essence, the proposed architectural approach puts a great effort into development while keeping the production process lightweight. By embracing these principles and techniques, software development teams can foster a culture of continuous improvement, driving value creation and competitive advantage in today’s fast-paced digital landscape.

References

[1] A. Cockburn, “Hexagonal Architecture,” Alistair Cockburn’s Hexagonal Architecture Blog, [Online]. Available: https://alistair.cockburn.us/hexagonal-architecture/. Accessed on: March 15, 2024.

[2] M. Fowler, “Presentation-Domain-Data Layering,” Martin Fowler’s Website, [Online]. Available: https://martinfowler.com/bliki/PresentationDomainDataLayering.html. Accessed on: March 15, 2024.

[3] L. Bass, P. Clements, R. Kazman, and M. Klein, “Software Architecture in Practice (SEI Series in Software Engineering),” Addison-Wesley, 2012.

[4] P. Runeson and E. Engström, “Regression Testing in Software Product Line Engineering,” in Advances in Computers, A. Hurson and A. Memon, Eds. Elsevier, vol. 86, 2012.

[5] M. Fowler, “Feature Toggles,” Martin Fowler’s Website, [Online]. Available: https://martinfowler.com/articles/feature-toggles.html. Accessed on: March 15, 2024.

--

--

Software engineer at Fanduel with a passion for crafting robust software architectures.