Skip to main content
  1. Articles/

Practical DDD in Golang: Factory

·1023 words·5 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 7: This Article

When I wrote the title of this article, I was trying to remember the first design pattern I had learned from “The Gang of Four”. I think it was one of the following: Factory Method, Singleton, or Decorator. I am sure that other software engineers have a similar story. When they started learning design patterns, either Factory Method or Abstract Factory was one of the first three they encountered. Today, any derivative of the Factory pattern is essential in Domain-Driven Design, and its purpose remains the same, even after many decades.

Complex Creations
#

We use the Factory pattern for any complex object creation or to isolate the creation process from other business logic. Having a dedicated place in the code for such scenarios makes it much easier to test separately. In most cases, when I provide a Factory, it is part of the domain layer, allowing me to use it throughout the application. Below, you can see a simple example of a Factory.

Simple example

type Loan struct {
	ID uuid.UUID
	//
	// some fields
	//
}

type LoanFactory interface {
	CreateShortTermLoan(specification LoanSpecification) Loan
	CreateLongTermLoan(specification LoanSpecification) Loan
}

The Factory pattern goes hand-in-hand with the Specification pattern. Here, we have a small example with LoanFactory, LoanSpecification, and Loan. LoanFactory represents the Factory pattern in DDD, and more specifically, the Factory Method. It is responsible for creating and returning new instances of Loan that can vary depending on the payment period.

Variations
#

As mentioned, we can implement the Factory pattern in many different ways. The most common form, at least for me, is the Factory Method. In this case, we provide some creational methods to our Factory struct.

Loan Entity

const (
	LongTerm = iota
	ShortTerm
)

type Loan struct {
	ID            	      uuid.UUID
	Type          	      int
	BankAccountID 	      uuid.UUID
	Amount        	      Money
	RequiredLifeInsurance bool
}

Loan Factory

type LoanFactory struct{}

func (f *LoanFactory) CreateShortTermLoan(bankAccountID uuid.UUID, amount Money) Loan {
	return Loan{
		Type:          ShortTerm,
		BankAccountID: bankAccountID,
		Amount:        amount,
	}
}

func (f *LoanFactory) CreateLongTermLoan(bankAccountID uuid.UUID, amount Money) Loan {
	return Loan{
		Type:          	       LongTerm,
		BankAccountID: 	       bankAccountID,
		Amount:        	       amount,
		RequiredLifeInsurance: true,
	}
}

In the code snippet from above, LoanFactory is now a concrete implementation of the Factory Method. It provides two methods for creating instances of the Loan Entity. In this case, we create the same object, but it can have differences depending on whether the loan is long-term or short-term. The distinctions between the two cases can be even more complex, and each additional complexity is a new reason for the existence of this pattern.

Investment interface and implementations

type Investment interface {
	Amount() Money
}

type EtfInvestment struct {
	ID             uuid.UUID
	EtfID          uuid.UUID
	InvestedAmount Money
	BankAccountID  uuid.UUID
}

func (e EtfInvestment) Amount() Money {
	return e.InvestedAmount
}

type StockInvestment struct {
	ID             uuid.UUID
	CompanyID      uuid.UUID
	InvestedAmount Money
	BankAccountID  uuid.UUID
}

func (s StockInvestment) Amount() Money {
	return s.InvestedAmount
}

Investment Factories

type InvestmentSpecification interface {
	Amount() Money
	BankAccountID() uuid.UUID
	TargetID() uuid.UUID
}

type InvestmentFactory interface {
	Create(specification InvestmentSpecification) Investment
}

type EtfInvestmentFactory struct{}

func (f *EtfInvestmentFactory) Create(specification InvestmentSpecification) Investment {
	return EtfInvestment{
		EtfID:          specification.TargetID(),
		InvestedAmount: specification.Amount(),
		BankAccountID:  specification.BankAccountID(),
	}
}

type StockInvestmentFactory struct{}

func (f *StockInvestmentFactory) Create(specification InvestmentSpecification) Investment {
	return StockInvestment{
		CompanyID:      specification.TargetID(),
		InvestedAmount: specification.Amount(),
		BankAccountID:  specification.BankAccountID(),
	}
}

In the example above, there is a code snippet with the Abstract Factory pattern. In this case, we want to create instances of the Investment interface. Since there are multiple implementations of that interface, this seems like a perfect scenario for implementing the Factory pattern. Both EtfInvestmentFactory and StockInvestmentFactory create instances of the Investment interface. In our code, we can keep them in a map of InvestmentFactory interfaces and use them whenever we want to create an Investment from any BankAccount. This is an excellent use case for the Abstract Factory pattern, as we need to create objects from a wide range of possibilities (and there are even more different types of investments).

Reconstruction
#

We can also use the Factory pattern in other layers, such as the infrastructure and presentation layers. In these layers, I use it to transform Data Transfer Objects (DTO or Data Access Objects (DAO to Entities and vice versa.

The Domain Layer

type CryptoInvestment struct {
	ID               uuid.UUID
	CryptoCurrencyID uuid.UUID
	InvestedAmount   Money
	BankAccountID    uuid.UUID
}

DAO on the Infrastructure Layer

type CryptoInvestmentGorm struct {
	ID                 int                `gorm:"primaryKey;column:id"`
	UUID               string             `gorm:"column:uuid"`
	CryptoCurrencyID   int                `gorm:"column:crypto_currency_id"`
	CryptoCurrency     CryptoCurrencyGorm `gorm:"foreignKey:CryptoCurrencyID"`
	InvestedAmount     int                `gorm:"column:amount"`
	InvestedCurrencyID int                `gorm:"column:currency_id"`
	Currency           CurrencyGorm       `gorm:"foreignKey:InvestedCurrencyID"`
	BankAccountID      int                `gorm:"column:bank_account_id"`
	BankAccount        BankAccountGorm    `gorm:"foreignKey:BankAccountID"`
}

Factory on the Infrastructure Layer

type CryptoInvestmentDBFactory struct{}

func (f *CryptoInvestmentDBFactory) ToEntity(dto CryptoInvestmentGorm) (model.CryptoInvestment, error) {
	id, err := uuid.Parse(dto.UUID)
	if err != nil {
		return model.CryptoInvestment{}, err
	}

	cryptoId, err := uuid.Parse(dto.CryptoCurrency.UUID)
	if err != nil {
		return model.CryptoInvestment{}, err
	}

	currencyId, err := uuid.Parse(dto.Currency.UUID)
	if err != nil {
		return model.CryptoInvestment{}, err
	}

	accountId, err := uuid.Parse(dto.BankAccount.UUID)
	if err != nil {
		return model.CryptoInvestment{}, err
	}

	return model.CryptoInvestment{
		ID:               id,
		CryptoCurrencyID: cryptoId,
		InvestedAmount:   model.NewMoney(dto.InvestedAmount, currencyId),
		BankAccountID:    accountId,
	}, nil
}

CryptoInvestmentDBFactory is a Factory located within the infrastructure layer, and it is used to reconstruct the CryptoInvestment Entity. In this example, there is only a method for transforming a DAO to an Entity, but the same Factory can have a method for transforming an Entity into a DAO as well. Since CryptoInvestmentDBFactory uses structures from both the infrastructure (CryptoInvestmentGorm) and the domain (CryptoInvestment), it must reside within the infrastructure layer. This is because we cannot have any dependencies on other layers inside the domain layer.

I always prefer to use UUIDs within the business logic and expose only UUIDs in the API response. However, databases do not typically support really well strings or binaries as primary keys, so the Factory is the appropriate place to handle this conversion.

Conclusion
#

The Factory pattern is a concept rooted in older design patterns from The Gang of Four. It can be implemented as an Abstract Factory or a Factory Method. We use it in cases when we want to separate the creation logic from other business logic. Additionally, we can utilize it to transform our Entities to DTOs and vice versa.

Useful Resources
#

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

Related

Practical DDD in Golang: Aggregate

·2269 words·11 mins· loading · loading
I have spent years understanding and practicing the DDD approach. Most of the principles were easy to understand and implement in the code. However, there was one that particularly caught my attention. I must say that the Aggregate pattern is the most critical one in DDD, and perhaps the entire Tactical Domain-Driven Design doesn’t make sense without it. It serves to bind business logic together. While reading, you might think that the Aggregate resembles a cluster of patterns, but that is a misconception. The Aggregate is the central point of the domain layer. Without it, there is no reason to use DDD. Business Invariants # In the real business world, some rules are flexible. For example, when you take a loan from a bank, you need to pay some interest over time. The overall amount of interest is adjustable and depends on your invested capital and the period you will spend to pay the debt. In some cases, the bank may grant you a grace period, offer you a better overall credit deal due to your loyalty in the past, provide you with a once-in-a-lifetime offer, or require you to place a mortgage on a house. All of these flexible rules from the business world are implemented in DDD using the Policy pattern. They depend on many specific cases and, as a result, require more complex code structures. In the real business world, there are also some immutable rules. Regardless of what we try, we cannot change these rules or their application in our business. These rules are known as Business Invariants. For example, nobody should be allowed to delete a customer account in a bank if any of the bank accounts associated with the customer has money or is in debt. In many banks, one customer may have multiple bank accounts with the same currency. However, in some of them, the customer is not allowed to have any foreign currency accounts or multiple accounts with the same currency. When such business rules exist, they become Business Invariants. They are present from the moment we create the object until the moment we delete it. Breaking them means breaking the whole purpose of the application. Currency Entity type Currency struct { id uuid.UUID // // some fields // } func (c Currency) Equal(other Currency) bool { return c.id == other.id } BankAccount Entity

Practical DDD in Golang: Module

·2203 words·11 mins· loading · loading
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.

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.