Introduction

In the last year, I have been working more and more with Golang as I used to be a 100% PHP coder before.

The community around PHP is terrific, and as a language has a lot of Domain-Driven Design (DDD) advocates.

I have been affected by the community about this methodology too, so I’ve been studying and practicing it for the last 3 years.

When moving from PHP to Go as an everyday-language, I noticed that DDD doesn’t resonate within the Golang community because most people think it influences an “OOP approach” when writing Go code since the majority of the books and examples about patterns are written in Object-Oriented Design.

I genuinely believe that the Go community is missing the benefit of this methodology, so I decided to write some articles about Golang and DDD to dispel that myth.

How Golang helps with the strategic design

As some of you may already know, Domain-Driven Design is composed of two different sides, the strategic and the tactical one.

To give a brief and superficial summary, the strategic design is the one in which you and the domain experts analyze a domain, define its bounded contexts, and look for the best way to let them communicate. As you can easily assume, the strategic design is programming language agnostic.

However, this doesn’t mean that all the languages allow you to express what you and your team analyzed in the same way, and this is also one of the reasons because I think Go is an excellent fit for the methodology.

Note: The following statements perfectly fit the design of any library and application, whether monolithic or service-oriented.

Packaging via bounded contexts

In Golang, a package is a boundary providing a set of API used to create and/or modify a set of data structures, that in the given context, have their specialized meaning.

Let’s pick an email address as an example. In its generic definition, it’s an identifier of an email box to which a message is delivered.

We can conjugate and specialize its meaning in different contexts. In an admin area, we allow email addresses with the “whatever.com” domain, but in a customer area, any domain is valid.

A widely used and, in my opinion, not very beneficial pattern is to group things by kind, combining different boundaries in one place.

This is an example of grouping by kind:

package main

import "github.com/company/email"

func main(){
     aEmail, err := email.NewAdminEmail("info@whatever.com") 
    //...
     cEmail, err := email.NewCustomerEmail("name.surname@gmail.com")
    //...
} 

This solution may not look bad at first view, as we are still using the ubiquitous language to differentiate the different email addresses of our domain. Still, we are not considering one of the most critical topics about Domain-driven design: the context.

When grouping by kind, we are communicating by design that there is one package representing a context as the owner of the different email addresses meaning.

The poor design choice gets even more apparent when we try to go get the email addresses package since it is going to be downloaded with both the email addresses functions also if there’s need only of one of them.

It is essential now to underline another way to solve this problem supported by some DDD arguments, grouping things by context.

This approach leads to design packages considering the boundaries of a domain and avoiding to rethink how to group or split the packages of an application efficiently.

package main

import (
    "github.com/company/service/admin"
    "github.com/company/service/customer"
)

func main(){
    aEmail, err := admin.NewEmail("info@whatever.com") 
    //...
    cEmail, err := customer.NewEmail("name.surname@gmail.com")
    //...
}

The packages admin and customer are giving a special meaning to the NewEmail function. The packages are protecting and isolating the invariants of the two different boundaries.

The grouping by context highlights we are re-using the domain analysis already done, and there isn’t any extra effort to shape the solution again.

Another more everyday-technical example is an application that dispatches events.

I noticed many times that teams place all the events related to the application in the same package, bounding different domain models together, even if those are not related because of a need.

📂 app
 ┣ 📦customer
 ┃ ┗ 📜customer.go
 ┣ 📦events
 ┃ ┣ 📜customer_events.go
 ┃ ┣ 📜product_events.go
 ┃ ┗ 📜user_events.go
 ┣ 📦product
 ┃ ┗ 📜product.go
 ┗ 📦user
 ┃ ┗ 📜user.go

A better way is to take advantage of the context and split them based on the models that produce the events, letting the domain analysis help you to define which boundary holds what.

We can model this in two ways depending on the existing relationships of our teams.

In case this codebase is maintained by a team that has no other teams depending on those events, we can group them in the same package where the model sits without the need to create different sub-packages.

📂 app
 ┣ 📦customer
 ┃ ┣ 📜 events.go
 ┃ ┗ 📜customer.go
 ┣ 📦product
 ┃ ┣ 📜 events.go
 ┃ ┗ 📜product.go
 ┗ 📦user
 ┃ ┣ 📜 events.go
 ┃ ┗ 📜user.go

Things start being different when other teams are depending on those events.

It means we have an upstream/downstream relationship where the upstream team dictates the rules of the representation of those events, but it still wants to take into account the downstream team needs.

Using some DDD nomenclature, we can define this as a Customer/Supplier relationship.

Since the two teams want to find a smart and maintainable way of letting the application communicate, we can create some sub-packages that define the communication boundaries.

📂 app
 ┣ 📦customer
 ┃ ┣ 📦event
 ┃ ┃ ┗ 📜registered.go
 ┃ ┃ ┗ 📜activated.go
 ┃ ┃ ┗ 📜banned.go
 ┃ ┗ 📜customer.go
 ┣ 📦product
 ┃ ┣ 📦event
 ┃ ┃ ┗ 📜added.go
 ┃ ┃ ┗ 📜removed.go
 ┃ ┃ ┗ 📜published.go
 ┃ ┗ 📜product.go
 ┗ 📦user
 ┃ ┣ 📦event
 ┃ ┃ ┗ 📜logged.go
 ┃ ┃ ┗ 📜signed.go
 ┃ ┗ 📜user.go

Some people may now be worried about the number of packages created since we raised the number from 4 to 6, and it may look like an object oriented design.

Do not worry; the need for creating sub-packages is a clear need from the domain analysis, and there won’t be any package pollution since we are not using the packages as namespaces.

Packages, as explained before, are boundaries providing a set of API used to create and/or modify a set of data structures, protecting and isolating the invariants.

It means that we isolated the events around the model but without depending on it, letting other teams reuse the event packages without the need to carry the models into their codebase.

Defining better communication strategies between different packages

To quickly detect the relationship and the communication of packages living in the same codebase, it is mandatory to understand the dependencies between each package.

The folder structure needs to explain the dependencies between the boundaries of your domain and set policies to avoid high coupling or cascading dependencies of unrelated code.

It is possible not to have high coupling if there are no imports of packages on the same level, as this may be a flag of a wrong domain modeling or just a non-optimal design choice on the code side.

When importing packages on the same level, it is possible, most of the time, to move one of the two packages inside the other one defining a more clear bounded context.

Imagine this scenario: we have a customer package holding the representation of the domain model and a client that we use to retrieve the data of the customer from a third party service.

A package structure may look something like this:

📂 app
 ┣ 📦customer
 ┃ ┗ 📜customer.go // import module/client
 ┣ 📦client
 ┃ ┗ 📜client.go

From a packaging point of view this means that the two packages are not related as they apparently do not share anything, but when looking into the code implementation we see that the customer package depends on teh client package.

It looks to me a better approach to put the client package inside the customer package since it is only used from it. It is also important to treat the client package as an independent package without the need of its parent package, separating the boundaries of the two since the client returns a raw representation of the customer.

📂 app
 ┣ 📦customer
 ┃ ┣ 📦client
 ┃ ┃ ┗ 📜client.go
 ┃ ┗ 📜customer.go // import module/customer/client

In this way we clearly see the dependency of the two boundaries.

Hint: if it is possible to avoid the creation of the client package it’s event better!


Sometimes it is mandatory to let two or more packages communicate, even if they are on the same level, making a little bit harder understanding a relationship between packages.

Imagine a domain where a delivery team needs to ship a product, and a product team owns the product model.

The two teams want to operate decoupled, and the ubiquitous language used in the two teams only partially overlaps, but there isn’t any good reason to define an upstream/downstream communication.

A package structure holding this domain may look like this:

📂 app
 ┣ 📦delivery
 ┃ ┗ 📜delivery.go // may import module/product
 ┣ 📦product
 ┃ ┗ 📜product.go // or may import module/delivery

When this happens, it is possible to use the patterns defined in the strategic design of DDD.

The goal is to avoid a direct dependency between two packages.

One of the most used strategic patterns is the Anti-Corruption Layer. This pattern can be used when two bounded contexts need to share details about a domain model, and the teams want to protect themselves from the chance of leaking model data or ubiquitous language into the owned bounded context.

This can be achieved by creating a third package (example pubsub) that translates the data between the packages using only the required fields (and not the whole struct of each package), reducing the number of cascading dependency.

 ┣ 📦delivery
 ┃ ┗ 📜delivery.go // imports module/pubsub
 ┣ 📦product
 ┃ ┗ 📜product.go // imports module/pubsub
 ┣ 📦pubsub
 ┃ ┗ 📜pubsub.go 

Conclusion

Packaging have a key role when writing Go code, and we have to take the full power of the package mechanism the language delivers and relies on.

My rule of thumb is to design a package as a bounded context and treat the communication across packages as such using strategic patterns to identify the relationships and the contracts.

This approach also align with the Package Oriented Design by William Kennedy which is worth a read.

Do you disagree with me and my design philosophy? Feel free to share what are the main decision you make when developing a codebase in Go in the comment below!

Note: this post may be part of a series about DDD and Golang, if you are interested to hear more about how to implement tactical pattern in Go let me know!