Skip to main content
  1. Articles/

Practical DDD in Golang: Module

·2203 words·11 mins· loading · loading ·
Marko Milojevic
Author
Marko Milojevic
Software engineer and architect. Golang and LLM enthusiast. Awful chess player, gym rat, harmonica newbie and cat lover.
DDD in Golang - This article is part of a series.
Part 5: This Article

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.

  1. 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.

  2. The customer Module is responsible for managing customer-related information, including customer profiles and addresses. Customers are entities that can place Orders, and a single user may have multiple Customer profiles for deliveries.

  3. The shopping Module is more complex and handles the entire shopping process. It involves creating and managing ShoppingBaskets, Order creation, and interaction with both the access and customer 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.

Module Dependencies
Module Dependencies

The diagram provided illustrates the dependencies between different Modules in the application. Here’s a summary of the dependencies and how the Modules interact:

  1. The shopping Module: This Module depends on both the customer and access Modules. It relies on the customer Module to determine the owner of an Order and to access delivery address information. Additionally, it depends on the access Module to check access rights for specific shopping-related actions, such as managing Baskets and Items.

  2. The customer Module: The customer Module has a dependency on the access Module. It uses the access Module to access user session information and to determine which Customers are associated with a User. This information is used to decide where to send an Order.

  3. The access Module: The access Module is a foundational Module that other Modules depend on. It provides user session management, authorization, and access control. Both the shopping and customer Modules depend on the access 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.

Useful Resources
#

DDD in Golang - This article is part of a series.
Part 5: This Article

Related

Practical DDD in Golang: Domain Event

·1907 words·9 mins· loading · loading
In many cases, Entities are the most effective means of representing elements in Domain-Driven Design. Together with Value Objects, they can provide a precise reflection of our Problem Domain. However, sometimes, the most apt way to depict a Problem Domain is by employing events that transpire within it. In my experience, I increasingly attempt to identify events and then discern the Entities associated with them. Although Eric Evans didn’t cover the Domain Event pattern in the first edition of his book, today, it’s challenging to fully develop the domain layer without incorporating events. The Domain Event pattern serves as a representation of such occurrences within our code. We employ it to elucidate any real-world event that holds relevance for our business logic. In the contemporary business landscape, virtually everything is connected to some form of event. It can be anything # Domain Events can encompass a wide range of occurrences, but they must adhere to certain rules. The first rule is that they are immutable. To support this characteristic, I consistently utilize private fields within Event structs, even though I’m not particularly fond of private fields and getters in Go. However, Events typically don’t require many getters. Additionally, a specific Event can only occur once. This implies that we can create an Order Entity with a particular Identity only once, and consequently, our code can only trigger the Event that describes the creation of that Order once. Any other Event related to that Order would be a different type of Event, pertaining to a distinct Order. Each Event essentially narrates something that has already taken place, representing the past. This means we trigger the OrderCreated Event after we have already created the Order, not before. Global Events type Event interface { Name() string } type GeneralError string func (e GeneralError) Name() string { return "event.general.error" } Order Event type OrderEvent interface { Event OrderID() uuid.UUID } type OrderDispatched struct { orderID uuid.UUID } func (e OrderDispatched) Name() string { return "event.order.dispatched" } func (e OrderDispatched) OrderID() uuid.UUID { return e.orderID } type OrderDelivered struct { orderID uuid.UUID } func (e OrderDelivered) Name() string { return "event.order.delivery.success" } func (e OrderDelivered) OrderID() uuid.UUID { return e.orderID } type OrderDeliveryFailed struct { orderID uuid.UUID } func (e OrderDeliveryFailed) Name() string { return "event.order.delivery.failed" } func (e OrderDeliveryFailed) OrderID() uuid.UUID { return e.orderID } The code example provided above demonstrates simple Domain Events. This code represents just one of countless ways to implement them in Go. In certain situations, such as with GeneralError, I have employed straightforward strings as Event representations. However, there are instances when I’ve utilized more complex objects or extended the base Event interface with a more specific one to introduce additional methods, as seen with OrderEvent.

Practical DDD in Golang: Domain Service

·1802 words·9 mins· loading · loading
After discussing Entity and Value Objects, I will now introduce the third member of the group of Domain-Modeling patterns in this article: Domain Service. Domain Service is perhaps the most misunderstood DDD pattern, with confusion stemming from various web frameworks. In many frameworks, a Service takes on a multitude of roles. It’s responsible for managing business logic, creating UI components such as form fields, handling sessions and HTTP requests, and sometimes even serving as a catch-all “utils” class or housing code that could belong to the simplest Value Object. However, almost none of the aforementioned examples should be a part of a Domain Service. In this article, I will strive to provide a clearer understanding of its purpose and proper usage. Stateless # A critical rule for Domain Services is that they must NOT maintain any state. Additionally, a Domain Service must NOT possess any fields that have a state. While this rule may seem obvious, it’s worth emphasizing because it’s not always followed. Depending on a developer’s background, they may have experience in web development with languages that run isolated processes for each request. In such cases, it may not have been a concern if a Service contained state. However, when working with Go, it’s common to use a single instance of a Domain Service for the entire application. Therefore, it’s essential to consider the consequences when multiple clients access the same value in memory. Use State in Entity type Account struct { ID uint Person Person Wallets []Wallet } Use State in Value Object type Money struct { Amount int Currency Currency } DON’T use State in Domain Service type DefaultExchangeRateService struct { repository *ExchangeRateRepository useForceRefresh bool } type CasinoService struct { bonusRepository BonusRepository bonusFactory BonusFactory accountService AccountService } As evident in the example above, both Entity and Value Object retain states. An Entity can modify its state during runtime, while Value Objects always maintain the same state. When we require a new instance of a Value Object, we create a fresh one. In contrast, a Domain Service does not house any stateful objects. It solely contains other stateless structures, such as Repositories, other Services, Factories, and configuration values. While it can initiate the creation or persistence of a state, it does not retain that state itself.

Practical DDD in Golang: Entity

·2074 words·10 mins· loading · loading
In the previous article, I attempted to provide insights into the Value Object design pattern and how we should apply it in Go. In this article, the narrative continues with the introduction of a design pattern called Entity. Many developers have heard about Entity countless times, even if they’ve never used the DDD approach. Examples can be found in PHP frameworks and Java. However, its role in DDD differs from its use elsewhere. Discovering its purpose in DDD marked a significant turning point for me. It seemed a bit unconventional, especially for someone with a background in PHP MVC frameworks, but today, the DDD approach appears more logical. It is not part of ORM # As demonstrated in the examples for PHP and Java frameworks, the Entity often assumes the roles of various building blocks, ranging from Row Data Gateway to Active Record. Due to this, the Entity pattern is frequently misused. Its intended purpose is not to mirror the database schema but to encapsulate essential business logic. When I work on an application, my Entities do not necessarily replicate the database structure. In terms of implementation, my first step is always to establish the domain layer. Here, I aim to consolidate the entire business logic, organized within Entities, Value Objects, and Services. Once I’ve completed and unit-tested the business logic, I proceed to create an infrastructural layer, incorporating technical details like database connections. As illustrated in the example below, we separate the Entity from its representation in the database. Objects that mirror database schemas are distinct, often resembling Data Transfer Objects or Data Access Objects. Entity inside the Domain Layer type BankAccount struct { ID uint IsLocked bool Wallet Wallet Person Person } Repository interface inside the Domain Layer // Repository interface inside domain layer type BankAccountRepository interface { Get(ctx context.Context, ID uint) (*BankAccount, error) } Data Access Object inside the Infrastructure Layer type BankAccountGorm struct { ID uint `gorm:"primaryKey;column:id"` IsLocked bool `gorm:"column:is_locked"` Amount int `gorm:"column:amount"` CurrencyID uint `gorm:"column:currency_id"` Currency CurrencyGorm `gorm:"foreignKey:CurrencyID"` PersonID uint `gorm:"column:person_id"` Person PersonGorm `gorm:"foreignKey:PersonID"` } Concrete Repository inside the Infrastructure Layer type BankAccountRepository struct { // // some fields // } func (r *BankAccountRepository) Get(ctx context.Context, ID uint) (*domain.BankAccount, error) { var dto BankAccountGorm // // some code // return &BankAccount{ ID: dto.ID, IsLocked: dto.IsLocked, Wallet: domain.Wallet{ Amount: dto.Amount, Currency: dto.Currency.ToEntity(), }, Person: dto.Person.ToEntity(), }, nil } The example shown above is just one of the many variations we can implement. While the structure of both the Entity and DTO can vary depending on the specific business case (such as having multiple Wallets per BankAccount), the core concept remains consistent.