Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I’ll earn a commission. Please notice your final price is not affected at all by using those links.
I briefly covered Domain Driven Design three years ago but I quickly realized the project I wanted to use as example was perhaps not the best idea because it was both too niche and it included some naming that is considered offensive. I even mentioned the “To Do program” domain during that original post.
What is Domain Driven Design?
According to Martin Fowler:
Domain-Driven Design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain
It’s not a new concept, in fact Domain Driven Design was a term coined by Eric Evans almost 20 years ago (the blue book was published in 2003). However in recent years it has become more relevant because of the popularity of Microservices and how those are meant to be designed to do one single thing the best way possible.
However Domain Driven Design has the following caveats:
- It’s not easy to implement and it requires more work, specifically isolation and encapsulation on the domain layer to maintain the principal model as pure as possible.
- It is recommended for large and complex systems because it requires having domain experts working collaboratively with the team in charge of writing the software.
Recommended books covering Domain Driven Design, listed in the order I suggest reading them:
- Domain-Driven Design Distilled
- Domain-Driven Design: Tackling Complexity in the Heart of Software
- Implementing Domain-Driven Design
Understanding the “To Do” Domain
The code used for this post is available on Github feel free to play with it.
Our “To Do Microservice” will be implementing a To Do Domain, which consists of the following rules:
Task(s) are activities that need to be completed within a Period of Time, they have a Priority and can be Categorized.
- Tasks require a Description.
- Other Tasks can be defined as pre-requisite(s).
Period of Timeis a point in time where a Task starts or completes.
- It consists of a Start Date and a Due Date.
- This value is not required on Tasks.
- Start Date is “less than” Due Date.
Priorityindicates how important a Task is.
- There are four priorities:
- Default priority is:
- There are four priorities:
Categoryis a human readable value meant to be used to organize Tasks.
- Values are unique in all.
Go does not enforce any project structure or project layout, there are no concrete guidelines for selecting how to organize our projects, usually the most common answer is use a flat structure, and that really works for small projects, however this may not work at scale when dealing with really complex systems.
Similar to Go, Domain Driven Design does not enforce any project structure or project layout, instead it defines concepts we have to follow to properly communicate between the team in charge of the implementation and the domain experts.
Clearly we need something else…
This is where another concept comes into place: Hexagonal Architecture, with the sole purpose of creating a loosely coupled application that can be interconnected via ports and adapters. The idea of defining those ports and adapters is to allow components to communicate with each other via concrete protocols, which in the world of Go we could define them as interface types.
In Go, if we put everything together we can define a project layout that includes an
internal/ package with the following structure:
This is the structure I like to follow for complex projects, where each package is described as the following:
internal/defines all domain types and domain logic, all business logic should be defined here as much as possible.
internal/datastoreis a placeholder value, it’s meant to represent Repositories and it literally should be named as the datastore we are planning to use like
internal/apiindicates the public way to access our service, the method we use to expose our API for the outside world, for example
internal/servicedefines all use cases, and in some cases domain logic, mean to connect datastores and apis together.
Recall this is the beginning of this series, I will continue posting more content that describes in detail how to deal with all changes required for building a Microservice, this includes among other things: Testing, Authorization, Configuration and Observability.
Until then, keep it up. I will talk to you next time.