Adding Domain-Driven Design to Ports & Adapters - Improving Port Interfaces
Currently, I mainly use primitive data types in port interfaces. Let's improve them by introducing parameter objects with concern- and domain-specific names.
In the last article, I left the codebase with a structure that corresponds to the following diagram:

The port interfaces, as shown above using bright blue stickies, look as follows:
Primitive Types in Port Interfaces
The port interfaces, apart from the ParkingSpot class, currently are intentionally left primitive to emphasise the fact that hexagonal architecture does not prescribe any format, just that the outside world depends on the inside application and not the other way round.
I refer to primitive types not only for the classic low-level types like int, long, double, char, etc., but also to those types that are not associated with any of the different concerns. They could be used in any concern without creating a dependency between them.
This means that I also consider string- or collection types like lists, sets, or maps, or date-related types to be primitive types. They can be used in any generic context and do not convey any additional meaning beyond.

However, from a software design perspective, primitive types can cause issues.
Data passed back and fourth through adapters typically belongs together to some extent. Thus, they form a code smell called Data Clump, a special case of Primitive Obsession. Such primitive types lack validation and can therefore easily be passed in an invalid state, which makes an unchecked usage of them problematic.
Because of Data Clumps, the port signature is also longer than it should be, which is described by the Long Parameter List code smell. Having many primitive parameters of the same type can easily lead to a mix up, hard to read code, and code that has to be adapted in many places if the parameter list changes.
The immediate solution to these problems is to create a stronger type with a higher-level meaning. So let’s do exactly that. I try to use concern-specific names - this means that the names of types used within the hexagon, which contains only business concerns, will also get names that have a meaning in the real-world domain for which the system is built, moving the system towards Domain-Driven Design.
Introducing Stronger Types into Port Interfaces
I use IntelliJ to Introduce Parameter Objects. This refactoring automatically replaces the selected parameters with an object containing the parameters as fields.

Stronger Types in Inbound Ports
I start with the inbound port ForReservingParkingSpots. Initially, I simply summarise the 3 parameters startTime, endTime, and reservingMember into a wrapper class called ReservationDetails that I place into the application concern.
The presentation concern (the controller adapter) needs to translate the data to this input object to be able to communicate with the application. Importantly, ReservationDetails itself is a POJO free of any data-access- or presentation-concern structures like JSON or @Entity annotations, as this would violate the dependency rule. Only generic language extensions like Lombok (constructors, getters, setters) should be used for these classes.
Stronger Types in Outbound Ports
I can do the same with outbound port interfaces. For example, I can reuse the ReservationDetails object in the hasActivateReservation method like below:
And ForStoringReservations gets a Reservation class as its parameter:
Furthermore, I can also reuse the ReservationDetails class in Reservation’s constructor:
Additional Stronger Types
ReservationDetails can be further separated. For example, startTime and endTime always go together and there is no possibility to have a startTime without and endTime and vice versa. Thus, I add an additional class called ReservationPeriod that summarises the two.
Furthermore, the reservingMember string does not have any validation yet. By creating a new class ReservingMember, I can introduce a place that potentially covers that validation later and further improves expressiveness of that port.
Return Values
On the return-value side, we see that a Long value is returned in both ForStoringReservations and ForReservingParkingSpots. This is suboptimal because we can only guess its meaning from the interface signature. Having a stronger type ReservationId immediately reveals its intent.
Translating Data Structures Between Concerns
Of course, we also need to adapt the translation between the models in the adapters and service. This is the strong part of Ports and Adapters - we can build the internal model of the hexagon without having to directly consider the outside world. The adapters handle such changes and act as Anti-Corruption Layer, allowing the internal model to evolve on its own.

The current solution can be found on the branch stronger-types.
Conclusion
In this article, I have laid out why primitive types can hurt expressiveness, and how stronger types can make port interfaces much easier to read and comprehend without having to dive in deep into the actual implementation.
The introduced data structures are all part of the business concern and can therefore be used not only by that, but also by the adapters to translate between the internal and the external model.
However, there are some restrictions and considerations when it comes to exposing data structures from the business concern outside of the hexagon which I will explore in the next article.
Additionally, the types currently are simple data holders without any additional logic associated with them. In the next article, I will also discuss which data structures should remain such simple DTOs, and which on the other hand could be used to build up a rich domain model. Subscribe if you don’t want to miss it:
Resources
This article is part of a series moving towards a business-centric architectural design. See the following post to get started.
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 Hexagonal and 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.