Practical DDD in Golang: Module
- 11 minutes read - 2195 wordsThe discussion about DDD in Go now leads us to explore a cluster of highly cohesive structures known as Modules.

At first glance, Modules may not seem like a typical software development pattern, especially when we often associate patterns with specific code structures or behaviors. This can be particularly confusing when considering Go Modules. These modules consist of closely related Go Packages, are versioned, and released together, serving as a form of dependency management in Go. Since both Go Modules and Packages impact the project’s structure, it raises the question of their relationship with the DDD pattern known as Module. Indeed, there is a connection between them.
The Structure
In Go, we use Packages to organize and group our code. Packages are closely tied to the folder structure within our projects, although there can be variations in naming. These variations arise because we have the flexibility to name our package differently than the actual folder it resides in.
Folder pkg/access/domain/model
package access_model
import (
"github.com/google/uuid"
)
type User struct {
ID uuid.UUID
//
// some fields
//
}
Folder pkg/access/domain/service
package access_service
import (
"project/pkg/access/domain/model"
)
type UserService interface {
Create(user access_model.User) error
//
// some methods
//
}
In the example above, you can observe slight differences between folder and package naming. In some cases, when
dealing with multiple model
packages, I add prefixes from my DDD Modules to facilitate referencing these
packages within the same file. Now, we can start to gain a better understanding of what a DDD Module would be in the
previous example. In this context, the Module encompasses the access
package along with all its child packages.
project
├── cmd
│ ├── main.go
├── internal
│ ├── module1
│ │ ├── infrastructure
│ │ ├── presentation
│ │ ├── application
│ │ ├── domain
│ │ │ ├── service
│ │ │ ├── factory
│ │ │ ├── repository
│ │ │ └── model
│ │ └── module1.go
│ ├── module2
│ │ └── ...
│ └── ...
├── pkg
│ ├── module3
│ │ └── ...
│ ├── module4
│ │ └── ...
│ └── ...
├── go.mod
└── ...
The folder structure in the diagram above represents my preferred project structure for implementing Domain-Driven Design in Go. While I may create different variations of certain folders, I strive to maintain consistent DDD Modules.
In my projects, each Module typically consists of no more than four base packages: infrastructure
, presentation
,
application
, and domain
. As you can see, I adhere to the principles of
Hexagonal Architecture.
In this structure, I place the infrastructure
package at the top. This is because, by following
Uncle Bob’s
Dependency Inversion Principle,
my low-level Services from the infrastructure
layer implement high-level interfaces from other layers. With this
approach, I ensure that I define a Port
, such as the UserRepository
interface, in the domain
layer, while the
actual implementation resides in the infrastructure
layer. There can be multiple Adapters
for this implementation,
like UserDBRepository
or UserFakeRepository
.
Folder pkg/access/domain/repository
package acces_repository
import (
"project/pkg/access/domain/model"
)
type UserRepository interface {
Create(user access_model.User) error
}
Folder pkg/access/infrastructure/database
package database
type UserDBRepository struct {
//
// some fields
//
}
func (r *UserDBRepository) Create(user access_model.User) error {
//
// some code
//
return nil
}
Folder pkg/access/infrastructure/fake
type UserFakeRepository struct {
//
// some fields
//
}
func (r *UserFakeRepository) Create(user access_model.User) error {
//
// some code
//
return nil
}
The concept of Ports
and Adapters
is not new, and it is part of the principles of Hexagonal Architecture.
It is one of the principles I use when designing my DDD Modules, and in my opinion, it is a crucial one. Returning
to the package structure within the Module, in this design, each layer has knowledge of all the layers below it,
but no knowledge of the layers above it. This means that the infrastructure
layer can depend on all other layers,
while the domain
layer depends on none. Just below the infrastructure
layer is the presentation
layer, which
we can also refer to as the interface
layer (although “interface” is a reserved word in Go, so “presentation” is
a suitable alternative). Finally, situated between the presentation and domain layers is the application
layer.
The advantage of this layering approach in Go is that it helps us avoid cyclic dependencies, which can lead to
compile-time issues in our code. By adhering to these layering rules and dependency directions, we can save ourselves
from the headaches of complex code refactoring. You may have also noticed some folders (or packages) within the
domain
layer, such as model
and service
. I include them on occasion to keep my packages as straightforward
as possible.
The Logical Cluster
A DDD Module isn’t just a random collection of files and folders grouped together. The code contained within those files and folders should form a cohesive and logical structure. Furthermore, two different Modules should be designed to be loosely coupled, with minimal dependencies between them.
This approach helps in keeping the codebase organized, maintainable, and modular. It also facilitates the separation of concerns and allows for better isolation of different parts of the system. Each Module should have a clear purpose and responsibility, making it easier to understand and maintain the codebase as it grows.
project
├── ...
├── pkg
│ ├── access
│ │ ├── infrastructure
│ │ │ └── ...
│ │ ├── presentation
│ │ │ └── ...
│ │ ├── application
│ │ │ └── service
│ │ │ ├── authorization.go
│ │ │ └── registration.go
│ │ ├── domain
│ │ │ ├── repository
│ │ │ │ ├── user.go
│ │ │ │ ├── group.go
│ │ │ │ └── role.go
│ │ │ └── model
│ │ │ ├── user.go
│ │ │ ├── group.go
│ │ │ └── role.go
│ │ └── access.go
│ ├── shopping
│ │ ├── infrastructure
│ │ │ └── ...
│ │ ├── presentation
│ │ │ └── ...
│ │ ├── application
│ │ │ └── service
│ │ │ └── session_basket.go
│ │ ├── domain
│ │ │ ├── service
│ │ │ │ └── shopping.go
│ │ │ ├── factory
│ │ │ │ └── basket.go
│ │ │ ├── repository
│ │ │ │ └── order.go
│ │ │ └── model
│ │ │ ├── order.go
│ │ │ └── basket.go
│ │ └── shopping.go
│ ├── customer
│ │ ├── infrastructure
│ │ │ └── ...
│ │ ├── presentation
│ │ │ └── ...
│ │ ├── application
│ │ │ └── ...
│ │ ├── domain
│ │ │ ├── repository
│ │ │ │ ├── customer.go
│ │ │ │ └── address.go
│ │ │ └── model
│ │ │ ├── customer.go
│ │ │ └── address.go
│ │ └── customer.go
│ └── ...
└── ...
The provided folder structure is a straightforward example of DDD Modules. In this structure, there are three Modules:
access
, shopping
, and customer
, and potentially more. Each Module is organized into layers and sublayers,
and they each serve specific purposes within the application.
-
The
access
Module deals with authorization, registration, and user session management. It handles user access rights and determines whether users can access particular objects or perform specific actions. -
The
customer
Module is responsible for managing customer-related information, including customer profiles and addresses.Customers
are entities that can placeOrders
, and a single user may have multipleCustomer
profiles for deliveries. -
The
shopping
Module is more complex and handles the entire shopping process. It involves creating and managingShoppingBaskets
,Order
creation, and interaction with both theaccess
andcustomer
Modules. It depends on both of these Modules to function correctly.
It’s crucial to manage dependencies between Modules to ensure that they are one-directional. This helps prevent circular dependencies, which can lead to compilation errors and make the codebase harder to maintain. Properly structured Modules enhance code organization and maintainability, making it easier to understand and extend the application.
The diagram provided illustrates the dependencies between different Modules in the application. Here’s a summary of the dependencies and how the Modules interact:
-
The
shopping
Module: This Module depends on both thecustomer
andaccess
Modules. It relies on thecustomer
Module to determine the owner of anOrder
and to access delivery address information. Additionally, it depends on theaccess
Module to check access rights for specific shopping-related actions, such as managingBaskets
andItems
. -
The
customer
Module: Thecustomer
Module has a dependency on theaccess
Module. It uses theaccess
Module to access user session information and to determine whichCustomers
are associated with aUser
. This information is used to decide where to send anOrder
. -
The
access
Module: Theaccess
Module is a foundational Module that other Modules depend on. It provides user session management, authorization, and access control. Both theshopping
andcustomer
Modules depend on theaccess
Module to handle user-related functionality.
It’s important to note that a single Module does not necessarily correspond to one
Bounded Context. In this example, the access
Module could potentially be considered as a candidate for a separate Bounded Context, and future architectural
decisions might involve moving it elsewhere. The decision to split the shopping
and customer
Modules was based on
their distinct functionalities and the ability to work with them independently without affecting each other.
This modular approach allows for flexibility and maintainability in the application’s architecture, making it easier to manage and extend over time.
The Naming
Discussing naming might seem surprising, but it’s actually quite important. In my experience, I’ve encountered poorly chosen names for DDD Modules, and I’ve even made some bad naming choices myself.
project
├── ...
├── pkg
│ ├── shoppingAndCustomer
│ │ └── ...
│ ├── utils
│ │ └── ...
│ ├── events
│ │ └── ...
│ ├── strategy
│ │ └── ...
│ └── ...
└── ...
The example above includes several poor names. I always avoid using the word “and” in Module names, as seen here in
shoppingAndCustomer
. If I can’t avoid using the word “and,” it probably indicates two separate Modules.
The term utils
is one of the worst names in software development, and I avoid using it for struct names, file names,
function names, packages, or Module names. Naming a Module garbageCollector
might describe the contents of the
utils
Module accurately.
Creating a Module that contains bits and pieces from all over the codebase is also unhelpful. The events
Module is
an example of this, as it holds Domain Events from the entire application. Naming a Module after a design pattern,
like the strategy
Module, is not ideal either. We may use the Strategy pattern in various parts of our application,
so it doesn’t make sense to have multiple strategy Modules. Instead, our Modules should have names from the real
business world, be a part of the Ubiquitous Language,
and describe a unique cluster of business logic with a term that belongs to both the business and software
development worlds.
Dependency Injection
You may have noticed that the first project structure introduced separate Go files in the roots of each DDD Module.
I always name these files either module.go
or the same as the Module itself.
These files are where I define dependencies within my Module and different Adapters
for my Ports
when I have them.
In many cases, I create simple Go containers that store objects that I use in the application.
Module File
type AccessModule struct {
repository acces_repository.UserRepository
service access_service.UserService
}
func NewAccessModule(useDatabase bool) *AccessModule{
var repository acces_repository.UserRepository
if useDatabase {
repository = &database.UserDBRepository{}
} else {
repository = &fake.UserFakeRepository{}
}
var service access_service.UserService
//
// some code
//
return &AccessModule{
repository: repository,
service: service,
}
}
func (m *AccessModule) GetRepository() acces_repository.UserRepository {
return m.repository
}
func (m *AccessModule) GetService() access_service.UserService {
return m.service
}
In the example above, I’ve created the AccessModule
struct. During initialization, it accepts configuration that
defines whether it should rely on the database or some fake implementation for UserRepository
. Later, all other
Modules can use this container to obtain their dependencies.
We can also address Dependency Injection in Go by utilizing one of the many available frameworks. One of the most commonly used libraries is Wire, but my personal favorite is Dingo. The Dingo library utilizes reflection, which can be a challenging topic for many Go developers. However, despite my reservations about reflection in Go, Dingo has proven to be an easy and stable solution in my experience, offering a range of useful features.
Using Dingo library
package example
type BillingModule struct {}
func (module *BillingModule) Configure(injector *dingo.Injector) {
// This tells Dingo that whenever it sees a dependency on a TransactionLog,
// it should satisfy the dependency using a DatabaseTransactionLog.
injector.Bind(new(TransactionLog)).To(DatabaseTransactionLog{})
// Similarly, this binding tells Dingo that when CreditCardProcessor is used in
// a dependency, that should be satisfied with a PaypalCreditCardProcessor.
injector.Bind(new(CreditCardProcessor)).To(PaypalCreditCardProcessor{})
}
Conclusion
DDD Module is a logical cluster for our code, bringing together various structures into a cohesive group that shares specific business rules. Within Modules, we can introduce different layers. It’s important to ensure that both layers and Modules maintain one-directional communication to prevent cyclic dependencies. Additionally, Modules should be named using terminology from the business world to promote clarity and understanding.