Domain Driven Design Golang Kata #1 : hexagonal architecture
Define a code organisation for your micro-services
Edited march 2023: @Figaro Classifieds we periodically review, re-challenge, upgrade our IT standards and guidelines. This initial golang DDD Kata presented in 2020 has been strongly simplified and enriched. Savor it :)
Since Golang language is well suited for micro-services development, we can apply once again Domain Driven Design (DDD) patterns to our Golang applications.
DDD books explain very well theory, intentions and patterns of DDD. I recommend reading Vaughn Vernon and Eric Evans books explaining deeply ‘the DDD’.
But we can notice a lack of true, up-to-date and complete examples of application that illustrate DDD. So we will browse here a complete example of an HTTP Rest API. Architecture presented here has been implemented by feature teams and is pushed in production every day.
Our application presents a service to manage a Todo list.
Complete Source code of application
The worthwhile part you’re looking for is just there : https://github.com/GermainSIGETY/golang-ddd-kata
Pay attention to readme.md ; explanation to build the app, run it, use it and launch automated tests.
Why hexagonal architecture ?
As development team, we have to define a structure of micro-services code that fulfills :
- a common choice of architecture among all micro-services (that could be hexagonal, N tiers, Onion, CQRS)
- single Responsibility Principle
- efficient testability with automatic test : everywhere it matters (test pyramid paradigm)
- efficient maintainability : languages upgrades, framework upgrades etc
- easy switch of client interfaces : GRPc vs HTTP/JSON vs CLI
- easy switch of persistence technology : among SQL vendors, buckets GCS, NoSQL
- easy switch of tiers service : mails senders, push notification, synchronization from/to other systems/domains etc
- no over-engineering
Decision to organize our IT with micro-services is taken, so we have to establish :
a common choice of architecture among all micro-services in order to speedup control by developers among different projects
Hexagonal architecture is very suitable for concerns described above, and follow two very fundamental principles in software development :
- DIP
- SRP
Dependency Inversion Principle (DIP)
Robert Martin (aka Uncle Bob) :
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
dependency without DIP
dependency with DIP
Single responsibility Principle (SRP)
DDD is an application of single-responsibility principle (SRP).
Robert Martin (again) summarizes it in that way:
Every piece of your code has only one reason to change
The corollary with SRP, every piece of your code can be easily unit tested because it has just one responsibility. We will dive immediately into application’s architecture and responsibilities of each parts.
Architecture
Our ‘Todos’ application is made of three main part:
UI
- Listen, receive, de-serialize, decode, read requests…
- … then send requests to domain…
- …. and receives responses from domain and send back responses (serialization)
⚠️ no validations of any fields of requests. Not any business rule !
=> This part should be as thin as possible
🔧 Current implementation is Gin Framework
Domain
The hexagon
- Validation rules of users inputs ; mandatory information for requests, format of fields and so on => it validates objects entering into the domain (entering in to the system, once inside it’s valid)
- business logic, orchestration ; the application persists mys todos, call external systems for notifications, for stats etc.
- domain objects : entities, with behavior. When a new Todo entity is created, Todo determines by itself and set to itself a creation date.
- transaction boundaries : manage database transactions ; decide whether actions results should be persisted or roll-backed.
dependencies : (almost) Nothing. Your domain must not be coupled with any web/UI framework nor persistence/infrastructure Framework: #SRP.
Infrastructure
contains implementation of means for hexagon to communicate with the outside world ; infrastructure or external services on which it depends
- here infra is responsible of one major topic: to manage persistence of Todos within a persistent storage. This is implement by a repository object : TodosRepository
- But it could be many others responsibilities: sending emails, SMS, push notifications, read configuration info etc (We will see in another post)
dependencies: domain, and many Golang persistence libraries stuff : GORM, SQL drivers etc.
🔧 Current implementation is Gorm
Dependencies
Application presents intentionally three go modules in order to enforce theses dependencies :
We want to be sure that:
- The domain does not depend on anything
- Infra and UI depend on domain ; they serve domain
Focus on dependencies directions
How domain can receive requests from UI without having a dependency on Requests data incoming from UI ?
DIP usage; domain defines some abstraction for a request in domain/port package :
Then this abstraction is implemented in UI :
How domain can order CRUD operations to infra without having a dependency on it ?
With DIP again Domain module has an ITodosRepository Interface in domain/port package, that defines all CRUD operations :
Infra module has a TodoRepositoryImpl struct with all methods defined in ITodoRepository :
At application startup, a Bootstraps instantiates a TodoRepositoryImpl, and inject to domain when instantiating it. Standard usage of interfaces.
Next Katas
- Test automation in our hexagonal architecture ; this topic is so important that it requires a whole story
- Fault Tolerance and eventual consistency
Happy DDDesign !