Skip to main content

Ddd

2023

Practical DDD in Golang: Specification

·1596 words·8 mins· loading · loading
There are not many code structures that bring me joy whenever I need to write them. The first time I implemented such code was with a lightweight ORM in Go, back when we didn’t have one. However, I used ORM for many years, and at some point, when you rely on ORM, using QueryBuilder becomes inevitable. Here, you may notice terms like “predicates”, and that’s where we can find the Specification pattern. It’s hard to find any pattern we use as Specification, yet we do not hear its name. I think the only thing harder is to write an application without using this pattern. The Specification has many applications. We can use it for querying, creation, or validation. We may provide a unique code that can do all this work or provide different implementations for different use cases. For Validation # The first use case for the Specification pattern is validation. Typically, we validate data in forms, but this is at the presentation level. Sometimes, we perform validation during creation, such as for Value Objects. In the context of the domain layer, we can use Specifications to validate the states of Entities and filter them from a collection. So, validation at the domain layer has a broader meaning than for user inputs. Base Product Specification type Product struct { ID uuid.UUID Material MaterialType IsDeliverable bool Quantity int } type ProductSpecification interface { IsValid(product Product) bool } A simple Product Specification type HasAtLeast struct { pieces int } func NewHasAtLeast(pieces int) ProductSpecification { return HasAtLeast{ pieces: pieces, } } func (h HasAtLeast) IsValid(product Product) bool { return product.Quantity >= h.pieces } In the example above, there is an interface called ProductSpecification. It defines only one method, IsValid, which expects instances of Product and returns a boolean value as a result if the Product passes validation rules. A simple implementation of this interface is HasAtLeast, which verifies the minimum quantity of the Product. Function as a Product Specification type FunctionSpecification func(product Product) bool func (fs FunctionSpecification) IsValid(product Product) bool { return fs(product) } func IsPlastic(product Product) bool { return product.Material == Plastic } func IsDeliverable(product Product) bool { return product.IsDeliverable } More interesting validators are two functions, IsPlastic and IsDeliverable. We can wrap those functions with a specific type, FunctionSpecification. This type embeds a function with the same signature as the two mentioned. Besides that, it provides methods that respect the ProductSpecification interface. This example is a nice feature of Go, where we can define a function as a type and attach a method to it so that it can implicitly implement some interface. In this case, it exposes the method IsValid, which executes the embedded function.

Practical DDD in Golang: Repository

·2108 words·10 mins· loading · loading
Today, it is hard to imagine writing an application without accessing some form of storage at runtime. This includes not only writing application code but also deployment scripts, which often need to access configuration files, which are also a type of storage in a sense. When developing applications to solve real-world business problems, connecting to databases, external APIs, caching systems, or other forms of storage is practically unavoidable. It’s no surprise, then, that Domain-Driven Design (DDD) includes patterns like the Repository pattern to address these needs. While DDD didn’t invent the Repository pattern, it added more clarity and context to its usage. The Anti-Corruption Layer # Domain-Driven Design (DDD) is a principle that can be applied to various aspects of software development and in different parts of a software system. However, its primary focus is on the domain layer, which is where our core business logic resides. While the Repository pattern is responsible for handling technical details related to external data storage and doesn’t inherently belong to the business logic, there are situations where we need to access the Repository from within the domain layer. Since the domain layer is typically isolated from other layers and doesn’t directly communicate with them, we define the Repository within the domain layer, but we define it as an interface. This interface serves as an abstraction that allows us to interact with external data storage without tightly coupling the domain layer to specific technical details or implementations. A Simple Repository example type Customer struct { ID uuid.UUID // // some fields // } type Customers []Customer type CustomerRepository interface { GetCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) SearchCustomers(ctx context.Context, specification CustomerSpecification) (Customers, int, error) SaveCustomer(ctx context.Context, customer Customer) (*Customer, error) UpdateCustomer(ctx context.Context, customer Customer) (*Customer, error) DeleteCustomer(ctx context.Context, ID uuid.UUID) (*Customer, error) } The interface that defines method signatures within our domain layer is referred to as a “Contract.” In the example provided, we have a simple Contract interface that specifies CRUD (Create, Read, Update, Delete) methods. By defining the Repository as this interface, we can use it throughout the domain layer. The Repository interface always expects and returns our Entities, such as Customer and Customers (collections with specific methods attached to them, as defined in Go). It’s important to note that the Entity Customer doesn’t contain any information about the underlying storage type, such as Go tags for defining JSON structures, Gorm columns, or anything of that sort. This kind of low-level storage configuration is typically handled in the infrastructure layer.

Practical DDD in Golang: Factory

·1023 words·5 mins· loading · loading
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.

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.

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.

Practical DDD in Golang: Value Object

·1892 words·9 mins· loading · loading
Saying that a particular pattern is the most important might seem like an exaggeration, but I wouldn’t even argue against it. The first time I encountered the concept of a Value Object was in Martin Fowler’s book. At that time, it seemed quite simple and not very interesting. The next time I read about it was in Eric Evans’ “The Big Blue Book.” At that point, the pattern started to make more and more sense, and soon enough, I couldn’t imagine writing my code without incorporating Value Objects extensively. Simple but beautiful # At first glance, a Value Object seems like a simple pattern. It gathers a few attributes into one unit, and this unit performs certain tasks. This unit represents a particular quality or quantity that exists in the real world and associates it with a more complex object. It provides distinct values or characteristics. It could be something like a color or money (which is a type of Value Object), a phone number, or any other small object that offers value, as shown in the code block below. Quantity type Money struct { Value float64 Currency Currency } func (m Money) ToHTML() string { returs fmt.Sprintf(`%.2f%s`, m.Value, m.Currency.HTML) } Quality type Color struct { Red byte Green byte Blue byte } func (c Color) ToCSS() string { return fmt.Sprintf(`rgb(%d, %d, %d)`, c.Red, c.Green, c.Blue) } Type extension type Salutation string func (s Salutation) IsPerson() bool { returs s != "company" } Logical Group type Phone struct { CountryPrefix string AreaCode string Number string } func (p Phone) FullNumber() string { returs fmt.Sprintf("%s %s %s", p.CountryPrefix, p.AreaCode, p.Number) } In Golang, you can depict Value Objects by creating new structs or by enhancing certain basic types. In either scenario, the goal is to introduce specialized functionalities for that individual value or a set of values. Frequently, Value Objects can supply particular methods for formatting strings to determine how values should operate during JSON encoding or decoding. However, the primary purpose of these methods should be to maintain the business rules linked to that particular characteristic or quality in real life. Identity and Equality # A Value Object lacks identity, and that’s its key distinction from the Entity pattern. The Entity pattern possesses an identity that distinguishes its uniqueness. If two Entities share the same identity, it implies they refer to the same objects. On the other hand, a Value Object lacks such identity. It only consists of fields that provide a more precise description of its value. To determine equality between two Value Objects, we must compare the equality of all their fields, as demonstrated in the code block below.