Towards Hexagonal Architecture - Identifying Concerns in Legacy Code
Any legacy application can be turned into Hexagonal Architecture if we know how to. Understanding and discerning the different concerns in any application is the first step towards that.
Framework-Specific Structure Fallacies
Any framework or language has a specific convention of how to structure code. If you work with Java and Spring Boot, the following package structure may look familiar:

There are many applications that follow exactly this structure and it can feel impossible to move towards a more business-centric architectural design like Hexagonal Architecture by Alistair Cockburn without the right approach.
This is why I will provide a step-by-step guide to achieve exactly that.
Because the entire topic is pretty large, I split it into a couple of articles that I will publish about once a week. You can follow along using my repository. Subscribe if you don’t want to miss it!
Before we can move towards Hexagonal Architecture, we need to be able to effectively identify different concerns. This is what I will look at in this article.
Typical Concerns in any Application
There are 3 to 4 concerns that any application has, which are:
Data Access: how we retrieve and store data. These are typically in-memory, file system, databases, or surrounding system calls.
Presentation: how data is presented to a user. This can involve JSON data structures, HTML pages, XML, PDF, or any other data and presentation logic that is used to deliver data from the user to the system and then back again by the system to the user.
Application/Business: the actual core of any software. Also referred to as “business rules”, this is how a company makes or saves money. While data access and presentation concerns may change frequently due to framework updates or exchange of technologies, the application concern should actually only change when business processes change. It’s best kept free of framework-specific code. This is sometimes impractical and there are exceptions to this rule, but the mindset when implementing this concern is to keep external dependencies to a minimum.
I said 3 to 4 concerns, so here’s the 4th concern which is not necessarily required but helps with emphasising the business view:
Domain: we can split off the domain concern from the application concern if we are interested in creating a rich domain model as described by Domain-Driven Design. Although not essential, it is highly advisable to create a richer domain model to capture business rules and decisions, especially if we are dealing in complex business domains.
If we split the domain concern off from the application concern, then the application concern is tasked with checking global validation, retrieving and storing domain entities, and invoking a domain entity’s business rules.
In the following, I will mainly focus on the 3 concerns presentation, data access, and application. Domain will become relevant in later articles when I will show how to move towards a more business-centric structure.
A Naive Approach to Identifying Concerns
After laying out the basic building blocks of an application, let’s investigate the Spring Boot app again. Where are the 3 concerns located? A naive interpretation could be the following:
The controller package is concerned with presentation logic. The repository package is concerned with data access. The service package contains application-specific service classes. And then we have the DTO package for JSON representations of the presentation concern, and a model package for the domain model.
Makes sense, right? Unfortunately no.
In reality, Spring Boot mixes up several different concepts from MVC and DDD and probably others as well. For example, let’s see what the @Service annotation defines:
public @interface Service
Indicates that an annotated class is a "Service", originally defined by Domain-Driven Design (Evans, 2003) as "an operation offered as an interface that stands alone in the model, with no encapsulated state."
May also indicate that a class is a "Business Service Facade" (in the Core J2EE patterns sense), or something similar. This annotation is a general-purpose stereotype and individual teams may narrow their semantics and use as appropriate.
We see that it can basically be anything a developer team may define as a “service”. This is not helpful but on the contrary, a great way to create chaos before even getting started, as everyone has another definition of what a “service” entails.
Conclusively, we cannot rely on framework-defined stereotypes to effectively identify concerns, a point I want to drive home even more with the following analysis.
Identifying Real Concerns in Code
Let’s take a look at the following code snippet you can find here. Can you easily spot what concern this class belongs to?
The class name suggests that it is an application-specific service. This means it should concern itself only with coordinating and applying business rules.
However, let’s look at the service method’s signature:
The return type indicates a REST HTTP response, which is part of the presentation concern. The same is true for the ParkingReservationRequest
, which is a Java DTO representation for a user-provided data structure, most likely JSON. It is directly passed from the controller to the service, as can be seen here:
Application-specific services should not use data structures from the presentation concern.
If we look a bit deeper into the service’s reserveParkingSpot
method, we find three guard clauses that need to be passed before the reservation request is processed any further:
This is actually a mix of business rules and presentation logic. The business rules are the checks on both start- and end time, which are taken directly from the presentation concern’s request DTO, and then the code returns a presentation-specific ResponseEntity
object with corresponding HTTP status codes. There is an awful lot of presentation logic going on in this business-application-specific service.
In the next couple of lines, presentation concerns and business rules are additionally entangled in data access concerns:
ParkingSpot
is a database entity, basically an object representation of a database table row in the case of SQL, and not a real business object, even though it is typically stored in the model
package.
Checking if the spot returned from the repository is null is an indirect and highly ambiguous way to represent the business rule that a spot needs to be free to be reservable.
In the case of making a parking spot reservation, we actually mix both business rules and data access concerns so smoothly together that we cannot simply distinguish the two visually, which is why I use a blue-green gradient to do so.
Reserving a parking spot means creating a ParkingReservation
database entity entry and setting the ParkingSpot’s
availability to false
.
However, this actually only represents how we store the information in the database and obscures the business rule “reserve parking spot”. We rely solely on the interpretation- and mental-mapping abilities of a programmer to understand its semantics. This may be acceptable in a simple example like the one presented here, but can quickly turn almost impossible to maintain in a more complex environment.
If we look closer into the query of the repository methods, we find additional business rules encoded:
Apparently, we get the next randomly available parking spot. We could decide to implement that differently, e.g. if we want to provide different categories of parking spots to customers. It’s a business decision to randomly select a free spot, so we mix business- with data access concerns once again.
In a last step, we map the data from the presentation request and the database entity’s ID to a presentation response DTO. None of this should happen directly within an application service, which should only handle business-application-specific concerns.

Conclusion
In this small example, I have demonstrated how framework-specific stereotypes and attributes like @Service are no guarantee at all to identify different concerns effectively.
On the contrary, because frameworks are typically expected to accommodate as many developer and team interpretations as possible, it’s easy to unintentionally mix different concerns without even realising it.
In the case of complex business logic, this mixing of concerns can heavily impact our ability to change software quickly and cheaply over time because the codebase deteriorates into a Big Ball of Mud.
Being able to quickly discern different concerns within our codebase is a vital skill needed to move towards a more business-centric application structure. In the upcoming articles, I will step-by-step illustrate how to transform the presented codebase into a Hexagonal Architecture / Ports and Adapters.
If you don’t want to miss it, just subscribe here:
Resources
This series of articles is an addition to the From Layered to Hexagonal Architecture in 2 steps article.
We touch on the same topics in our O’Reilly course “DDD, EventStorming, and Clean Architecture”. Check it out to learn how to use Classic TDD to implement DDD Aggregates in Clean Architecture.
If you want to learn more about effectively separating concerns, you could also have a look at our on-premise, remote or hybrid workshop Modern Software Architecture Design Patterns.