Hexagonal Architecture: Common pitfalls

Albert Llousas
6 min readDec 7, 2023

--

In my recent work experiences, I’ve been lucky enough to have the opportunity to work on many projects from different domains, teams, and microservice ecosystems. Hexagonal Architecture has almost always been the choice to architect the code bases.

In this article, I would like to share the pitfalls that I have encountered and seen over and over again throughout the different implementations but also provide the strategies that I try to use to tackle them.

Underestimate the learning curve

If you’re like me, getting your hands dirty into new patterns without fully understanding the basics is a familiar approach. However, at least for me, Hexagonal Architecture could be challenging. Spending time on understanding the fundamentals step by step will definitely help.

Strategies:

  • Master the fundamentals: Hexagonal heavily relies on the Dependency Inversion Principle, the last letter of SOLID patterns. Mastering this principle is essential for successfully implementing this code architecture. (Start with “A Little Architecture” article or this book)
  • Focus on one thing: Hexagonal is usually mixed with other tech trends like EDA, DDD, CQRS, FP, etc. If you are new into the topic, I would strongly suggest to focus only on understanding Hexagonal before exploring these other powerful approaches, otherwise it could be overwhelming.
  • Play with it: Before jumping into Hexagonal in your professional projects, hold yourself back. Experiment with pet projects. This hands-on approach will allow you to validate the concepts and put it in practice in a non risky environment.

Go by the book

Another mistake I found myself making, several times, was strictly following the rules (going by the book). This approach led me to overcomplicate the system right from the start, unnecessarily increasing the overall complexity.

Strategies:

  • Start simple: Begin with a minimalist and pragmatic mindset. Consider a thin layer of abstractions for outbound ports as a starting point. You can add additional components like inbound ports, DTOs between layers or extra packages later on.
  • Seek feedback: Share it with your teammates or friends, external perspectives can provide valuable insights, helping you identify areas of improvement, simplify and ensure your design choices are well-grounded.

Destructive decoupling

Similar to the previous pitfall, a classic, excessive decoupling — I tend to over-isolate the code, create endless abstractions or segregate modules into independent artefacts/subsystems/projects from the very beginning. However, this obsessive decoupling can unintentionally compromise an even more important quality in software: maintainability.

Strategies:

  • YAGNI: You Ain’t Gonna Need It, adhere to this principle, focusing on current requirements and emphasising the importance of not over engineering solutions for unclear future needs.
  • Continuous architecture review: Your code architecture is not set in stone, review it from time to time and change it as needed, always with consensus with the team, if any.

The hidden maze

I came up with this name some time ago when I was trying to follow the flow in which one use-case (application-service) was dependent on another, and so on, resulting in a tangled chain of calls where I got lost. Usually, this web of dependencies is an anti-pattern that hinders the clarity and simplicity that Hexagonal Architecture aims to provide.

Strategies:

  • Avoid use-case layer interdependencies: A single use case, in the application layer, should be an isolated orchestration of the domain and the infrastructure in order to execute a functional requirement. Avoiding interdependencies ensures that each use case remains self-contained and they can evolve independently. For these cases I found it useful to redesign my use-cases with the result of merging/removing some of them, moving some stuff to the domain or even to the infra side.
  • Event-Driven Architecture: If there is a real need (e.g. dual-writes), implementing an event-driven approach enables the asynchronous triggering of dependent use cases, preventing a direct and tightly coupled chain of calls.

External domain mirroring

External domain mirroring involves matching external domains within the application’s domain model. While it may seem intuitive, it could lead to a tight coupling, exposed to the ripple effect, as any modifications in external domains directly impact the internal domain model.

Strategies:

  • Design your domain first: Design your domain first and extract only the essential information needed from external systems. Prioritising the design of the internal domain allows you to maintain control over the structure and logic of your application, avoiding unnecessary dependencies on external structures.
  • Composite adapters: Shield the domain from the complexities of interacting with multiple external systems, create an infrastructure service if you need multiple external systems to fulfil your outgoing port interface.

Database-driven development

Designing the database schema as the initial step can lead to the application being mainly shaped by database considerations. This approach could easily hinder the loose coupling and separation of concerns advocated by Hexagonal Architecture and makes it challenging to modify business rules without affecting the database schema.

Strategies:

  • Delay database design: Refrain yourself from designing the database upfront. Instead, focus on implementing your user story by first defining the ports, use-cases, APIs, domain and business logic. Reserve the database adapter implementation and db-schema/structure as the final step.
  • Outside-in Test-Driven Development (TDD): Outside-In TDD helps a lot to delay decisions about the database schema until they are necessary.

Anemic domain models

Last but not least, an important pitfall, the anemic domain model. I was not even noticing this problem till I read about it some time ago; are your models just data holders and business logic is dispersed throughout the code base? Bingo! You are doing it. This silent pattern compromises the integrity of Hexagonal Architecture which advocates to place business logic at the heart of the domain layer but also harms the principles of Object-Oriented Programming (OOP) itself.

Strategies:

  • Centralize business logic within the domain: Enforce the centralization of business logic within the domain. Identify logic residing in the application service layer and shift it down to the domain. Candidates: Private methods, control flow statements (if, when, switch), error generation, and more.
  • Embrace Tell don’t ask pattern: This pattern encourages encapsulation of behaviour within the domain objects, empowering rich domains.
  • Apply Domain-Driven Design (DDD) Practices: Adopting Hexagonal Architecture naturally leads to exploring DDD which can help to prevent the lifting of business logic into other layers.

In conclusion, Hexagonal Architecture offers a strong architectural pattern to build testable, flexible and decoupled software systems. However, learning from my own experiences, we must approach it with caution, as certain pitfalls can significantly affect a more important aspect: maintainability. I hope these strategies are useful for you!

PD: Here some of my own implementations!

--

--