How to Structure DDD in Golang
A follow-up on how to organize the structure of code and packages in a DDD approach in Go
by Percy Bolmér, September 1, 2021
It is time to take a look at a better architectural solution when using DDD in Go projects. In this article, we will take a repository that has all the DDD components in place and show how a less complicated project setup can be managed, while still maintaining DDD.
The repository that we will change is from a previous article I wrote on how to implement DDD in Go. In that article, I explained all the components present in DDD as explained by Eric Evans. The repository can be found on GitHub.
How To Implement Domain-Driven Design (DDD) in Golang
The easy way of learning how to use DDD in a Go applicationRead more
All images drawn in this article are drawn by Percy Bolmér, the Gopher is drawn by Takuya Ueda, inspired by the works of Renée French. The gopher has been modified in the images
Moving Aggregates to Their Domain Package
The first thing to do is to remove the aggregate package altogether. We learned what an Aggregate is and what rules to apply, we don’t necessarily have to name the package to aggregate. I tend to place the Aggregates in their respective domain package, so the Customer aggregate should go inside the customer domain. It makes a lot more sense in my opinion.
Of course, after moving the files to the customer domain, you also need to change any reference that is pointing to aggregate.Customer and replace it with customer.Customer or Customer if you’re in the customer package.
The same thing has to be performed for the Product aggregate, it should go inside the product domain. I won’t cover all the code changes, it should be easy enough to find and refactor all mentions of the aggregate package.
According to me, it looks a lot nicer having the aggregate in their domain package in this way, it also kind of makes more sense.
Deleting the aggregate folder and we have tidied up some of the code smell.
Moving ValueObjects and Entities
We still have a folder for both entity and valueobject, which I guess isn’t wrong. One thing that is nice about storing the shared structs in their separate package that way is to avoid Cyclical Imports.
We can achieve this in another way though, which is less bloated. Right now we have no root package at all. Move all files from entity and valueobject to the root and rename the package to tavern
This leaves us with an even more improved structure.
You will also need to rename your module in go.mod into the appropriate name.
Not only that, we need to change all the imports in all the files to reflect the change, and all references of entities package have to be changed to tavern like the following gist.
All the imports starting with github.com/percybolmer/go-ddd are changed to github.com/percybolmer/tavern.
The easiest way to apply this change is to change all the files, remove the go.mod and go.sum file, and reinitialize the go module with the new name go mod init github.com/percybolmer/tavern.
That was quite the refactoring exerciser, but it was worth it. Feel free to rerun any tests so you know that it is working as expected.
Splitting the Services package
Right now, the services package contains all services in the same package. I’d like to split these into two packages, Order and Tavern.
The reason is that as projects grow it is nice to have the services divided into smaller packages. It can in my opinion become bloated storing all of them in the same package. I also like keeping the domain as part of the infrastructure, in this case, we create a new folder named order in the services folder. The reason is that in the future we might see more order-related services appearing, for instance, the current Order service focuses on Customer that orders a beverage, but what about when the Tavern needs to resupply? Using this structure it is easy for the developers to know where to look for related code.
Another important thing is the naming of the Configuration functions, if we keep creating functions like WithMemoryCustomerRepository it is hard to maintain what Configuration goes where. It is a lot easier if we see order.WithMemoryCustomerRepository to know what is happening.
Making this change requires the Tavern package to reference order.OrderService instead of the only OrderService
Change all references to match the new structure. We will also add a new function to the OrderService to add new customers because right now we can only do that by using the customer repository inside the struct. This won’t work since the service will be exposed to other packages. Also, a Service should never assume that the user of the service knows how to operate like this, so it is only natural that this logic is maintained in the service’s own domain.
After applying these changes, the test to order a product is even easier to follow.
Now we finally have the final solution. A very simple to navigate, clean structured project.
How to Run The Tavern
One last thing I like to recommend is the use of the cmd folder as found in many Go repositories. In that folder, all command-line tools should be found, and it can be more than one. For the sake of concluding this article, I will create a main program in the cmd folder.
mkdir cmd touch main.go
Let’s order a beer to celebrate.
This concludes this very long two-part series about DDD.
You can find the full code on GitHub.
There is still a lot more about DDD to learn, this only touches the basics and how I find structuring projects. We haven’t touched topics as Domain events, CQRS and EventSourcing yet. Stay tuned for more.
If you’re eager to learn more about DDD I do suggest Eric Evans book Domain-Driven Design: Tackling Complexity in the Heart of Software.
As always, feel free to reach out to me on any of my social media which you find below.
If you enjoyed my writing, please support future articles by buying me an Coffee