Hexagonal Architecture and GenAI: A good Combination for Modern Software Development
Hexagonal Architecture and GenAI: A good Combination for Modern Software Development
Introduction
Some time ago, I had the pleasure of leading a workshop on “Hexagonal Architecture and AI Tools for Modern Software Development.” For those who couldn’t attend, I’d like to summarize the key insights here – particularly why Hexagonal Architecture harmonizes so well with GenAI tooling.
Hexagonal Architecture, also known as Ports-and-Adapters Architecture, is an architectural pattern that emphasizes separation of concerns. Without going too deep into details: it’s about isolating core business logic (the “Domain”) from external systems and technical details by using clearly defined interfaces (“Ports”) and interchangeable implementations (“Adapters”).
But why is this architecture also particularly advantageous when working with GenAI tools like rooCode? That’s what I’d like to explain in this post.
Benefits of Hexagonal Architecture for GenAI Development
Clear Separation of Concerns
Like human developers, AI also benefits from clear context:
- The distinct structure shows where specific code can be found. This is ideally already evident from the directory listing with descriptive filenames.
- The business logic is well-described in the domain with strong types
- AI can focus on specific parts without needing to understand the entire system. That keeps the context small.
- Changes to one part don’t affect others (reducing the risk of AI-introduced errors)
Directory Structure
Let’s have a look at what an example of the directory structure might look like:
src/
├── domain/
│ ├── model/ # Domain entities and value objects
│ ├── service/ # Domain business logic
│ └── ports/
│ ├── in/ # Input ports (application APIs)
│ └── out/ # Output ports (repositories, etc.)
├── adapters/
│ ├── in/ # Input adapters (controllers, UI, etc.)
│ └── out/ # Output adapters (database, external services)
That is a standard directory structure of a ports and adapters architecture that is also known to most coding models. That way the agent natuarly knows where to look for files. File names are also very important they should use the same terms that are used in the business logic.
Well-defined Interfaces (Ports)
Ports are clearly defined interfaces that help AI, and also humans, understand expected behavior. Type safety guides AI to generate compatible code. That’s why I encourage to use a strongly typed Language and use expressive types like Domain Entities or Enums over basic types like e.g. `String`.
Interface types serve as documentation for AI. They define the building blocks that are supposed to be used to build new functionality. When using Ports it’s easy to break development tasks down into manageable tasks. You can first write the business logic of a new feature against new or existing ports, and then generate the adapter implementations in a second step.
Here’s an example from our workshop example codebase of how a port definition might look like:
// Example: Database interface
export interface Database extends Adapter {
findById(id: MMSI): Vessel | undefined
saveVessel(vessel: Vessel): void
findAllVessels(): Vessel[];
savePosition(id: MMSI, position: Position): void
findHistory(id: MMSI): VesselHistory | undefined
}
With this clear interface definition, AI knows exactly which operations are available, which parameters are expected, and which return types to expect. This makes it much easier for AI to generate correct code.
Swappable and Mockable Implementations (Adapters)
Adapters are implementations of ports that connect the domain with external systems. They may be swapped with other implementations or mocks in tests. This interchangeability offers several advantages for working with GenAI tools:
AI-generated code can be tested in isolationk. New adapters can be developed in parallel with existing ones to ensure backward compatibility. So if you ask AI to implement a new adapter, you can test it without affecting the rest of the system. If it doesn’t work as expected, you can simply revert to the previous adapter. In our definition functional external dependencies may only be used in Adapter implementations. That way we ensure that our business logic stays pure. This can also be enforced by architecture tests.
Practical Example: Vessel Tracking Application
In our workshop, we worked with an application for tracking vessel positions. This application tracks ships via AIS (Automatic Identification System), stores position data and vessel information, and displays current positions and history on a map.

In this application:
- The Domain contains the core business logic and entities such as Vessel, Position, and VesselHistory
- The Ports define interfaces such as DataAccessPort, AisPort, and Database
- The Adapters implement these interfaces to connect the domain with external systems, such as WebAppAdapter, MqttAdapter, and InMemoryDatabase
This clear structure makes it easier for GenAI tools to understand and extend the application.
Practical Workflow with GenAI Tools
During the workshop, we developed a practical workflow for working with GenAI tools in a hexagonal architecture. First, create an implementation plan, then implement this plan step by step and Continuously test and refine the outcome.
Division of Labor Between Developers and AI
You can take over yourself whenever you see fit. One strategy might be:
- You as a developer define the domain and ports
- AI implements business functions on the defined types
- You define testcases and let the test code generate
- AI creates adapter implementations
That way you stay in control and there is at now point in time too much code generated so that it would be too much to review.
Test-Driven Development
Hexagonal architecture nicely supports a test-driven approach. You can define test cases for the expected functionality in prosa text. Then you let the AI implement the code that fulfills these tests. Because of the Architecture you can always test implementations in isolation before integrating them into the overall system. You can mock your Ports for extensive coverage of your business logic. That way you’ll get a good confidence that the resulting system does what it should. RooCode can run unit and architecture tests independently and thus correct errors directly from within the chat.
Conclusion
In my eyes Hexagonal Architecture provides an ideal foundation for working with GenAI tools like rooCode:
- Clear separation of concerns allows AI to understand and modify specific parts of the code without affecting others.
- Well-defined interfaces make it easier for AI to understand expected behavior and generate compatible code.
- Swappable implementations enable testing AI-generated code in isolation before it’s integrated.
By applying this architecture, development teams can use GenAI tools more effectively to accelerate development, reduce errors, and improve code quality. The overhead of implementing abstraction layers is not such a burden when using GenAI tools. The clear boundaries and contracts provided by the architecture help AI generate correct and compatible code, while the isolation of components reduces the risk of AI-introduced errors affecting the entire system. Thus your code base becomes manageable even over lots of iterations.
I’d like to hear about your expeciences. Did you try something similar?