Implementing Domain-Driven Design and Hexagonal Architecture with Go (1)
Introduction to the domain, the bounded context, and the business usecases
This article was originally published on Medium in 2020!
Mission statement
About a year ago (hint: this article was originally published on Medium in 2020) I had some spare time and started a little playground project. My goal was to find my personal “best” way to implemement a little application using Event-Sourcing and CQRS and applying paradigms from Domain-Driven Design and Hexagonal Architecture with Go — using the learnings from my previous 3 years of working with the programming language and working on a very similar subdomain I’m describing below. It took me only a year to finally start blogging about it. ;-)
I experimented with many things and picked up some other interesting questions — apart from DDD vs. Go — on the way. So I’ll write about the following topics — and probably more — in a series of blog posts in the weeks to come:
Domain-Driven Design (mostly tactical design patterns)
Ports & Adapters / Hexagonal Architecture
Event-Sourcing and CQRS
Test-Driven Development (TDD) / Behavior-Driven Development (BDD)
Testing Strategies (Test Pyramid)
Functional (Domain) Core
Simplicity in Software Development
What defines an Aggregate in DDD?
Code Generation
Disclaimer
Target audience for the articles in this series are people who are interested in Domain-Driven Design and Go with any knowledge level. I try to highlight where I deviate from idiomatic Go or do things differently than in usual Go projects. In general I consider higher level concepts like Domain-Driven Design and Hexagonal Architecture to be “stronger” than “how most people implement things” in a programming language. Still I am always trying to balance both concerns and try to find a good compromise in cases. I will also describe simpler alternatives where applicable.
If you are religious about idiomatic Go and are not interested in different ways of doing things in Go — especially going the extra mile for defensive code and separation of concerns like Domain and Infrastructure — the articles in this series might not be for you!
Please be aware that the solutions I picked are just my decisions for the implementation of this playground project. I don’t want to suggest this as a blueprint architecture for any other projects implemented with Go! Still I hope that this article can offer some ideas and inspiration you as a reader find useful!
I’m absolutely not good in making decisions — at least with no team, product, company involved — so I’ve been through about 100 iterations with just about everything — but at least I have learned a lot. ;-)
My little application is quite simple, almost CRUD, so one might argue that the implementation is a bit of overkill for such a simple subdomain. It could well be done with less of everything — less DDD, simplified Hexagonal Architecture, no Event-Sourcing / CQRS.
But please keep my mission statement above in mind! I wanted to work on a subdomain I know very well from my previous job, so that I don’t have to guess which business logic would make sense for a more complicated Domain which I have no experience with! Also for readers of those blog posts the Domain problems should be easy to understand so that the focus can be on understanding the implementation patterns I picked and the reasons for the decisions I made.
The story is a bit different for the Event-Sourcing / CQRS parts. I believe Event-Sourcing offers a lot of benefits around modeling, changeability and simplicity (!), even for simpler problems.
The hypothetical system architecture
The whole system is distributed and event-driven
The default size of a service (a logical unit) is a bounded context
We can assume that a service is also a unit of deployment (technical unit)
Deployment units might be smaller for example if serverless is used
Communication between services is normally asynchronous (via Domain Events on some message bus) but can be synchronous (via inter-service API calls) if necessary
Hint: I won’t go deep on strategic design because the focus of this series is on tactical design with Go. For some excellent articles about strategic design I can highly recommend to visit Nick Tune’s Strategic Technology Blog
My subdomain: Customer Accounts
The little supporting subdomain I am implemementing has one relatively small bounded context. Both the subdomain and the bounded context are called Customer Accounts.
Backgound: The Business Domain could be CarRental or CarSharing. This business requires Customers to register and maintain their accounts so that they can reserve and drive vehicles, pay for the use, and receive invoices and other communication via email.
For the sake of this playground project — with the goal to implement things differently than I did in the past — lots of things have been left out to reduce the size of the application. For example gathering customer data like date of birth, drivers license and ID/passport will probably not be implemented. I won’t mention other details which I left out for disclosure reasons. I might also simplify or leave out other pieces.
Hint: There is no password and no concept of logging-in, because this is the concern of a different bounded context in the domain — probably named “Identity & Access” or “IAM”.
“IAM” is a candidate for some form of an “off the shelve” application.
Business requirements
Hint: Everything in bold in this paragraph is part of the ubiquitous language of the bounded context.
We need to gather the following data from Customers to be able to give them a vehicle:
Email Address — can be confirmed by the customer
Name — a person’s name consisting of Given Name and Family Name
Billing Profiles consisting of Credit Card and Billing Address
Additionally some pieces of information for a Customer Account should be produced by the application, e.g. Confirmation Hash, Customer ID, Billing Profile ID
Therefore, the application should offer the following use cases for prospective or existing Customers:
Register Customer
Confirm Email Address
Change Email Address
Change Name
Add Billing Profile
Remove Billing Profile
Delete Customer
Retrieve Account
Hint: The term Account is a compromise. There are (currently) no artifacts in the code with this name but it is used in communication and test scenarios, because sometimes it sounds awkward to express things without this extra word. E.g. “If a Customer deletes him/herself …” vs. “If a Customer deletes his/her account …”.
Uniqueness assertions:
An Email Address must be unique throughout all Customers
Validation assertions:
Email Address — must be a semantically valid email address with a top level domain (no local email address like root@localhost)
Given Name — must not be empty (Customers can also include their middle name)
Family Name — must not be empty
Credit Card — must adhere to the schema of credit card numbering
Billing Address — must contain a valid 2 letter ISO country code, a postal code (not empty) and some information like a street + house number or a P.O. box
It should be possible to tighten assertions later if necessary
Following business rules and policies exist:
To be able to confirm their Email Address, Customers must supply the matching Confirmation Hash
Billing Profiles must not be empty or partial, so both the Credit Card and the Billing Address must be defined
The last Billing Profile can not be removed but it should be possible to register without a Billing Profile (to get a Customer “lead”)
If Customers delete their Account they should not be able to interact with the Account any more (“not found”)
Following additional are defined:
The service must offer a REST API to be used by the Customers via website and mobile apps.
A Confirmation Hash is auto generated when Customers register and the hash is sent to them via email
If Customers change their Email Address it should be unconfirmed and they should get a new Confirmation Hash via email
The business wants the possibility to address Customers by using only their Given Name (on the website, mobile app, emails)
We use Given Name and Family Name everywhere instead of first name and last name because those name parts are used differently in different cultures (e.g. in many Asian countries the Family Name comes first)
We don’t want to overcomplicate the Billing Address for now but we want to know the 2 letter ISO country code and the postal code for B.I. reasons
If Customers delete their Account they should only be “soft” deleted
A hard delete of Customer Accounts will eventually be needed for GDPR reasons
Further requirements that have to be fulfilled for other parts of the Domain:
Customers can only reserve vehicles if their Email Address is confirmed and they have at least one complete Billing Profile
Hint: Imagine this is a real product, developed in a real company, by a real team. Some pieces of the specification might not be precise yet. ;-) That’s totally normal in iterative / agile work. We learn more about the Domain while we work on it and refine our model based on those learnings. This is how and why we do Domain-Driven Design. Our decisions are driven by business needs and not by technical concerns or egos.
Modeling
Let’s say a design level EventStorming session produced the following result, showing potential Commands, Events, Policies, some details and candidates for Aggregates, Entities and Views.
Acceptance test scenarios
The development of the application is guided by acceptance test scenarios in Given/When/Then (GWT) format (aka. Gherkin syntax). So one could say I’m doing TDD and BDD. Not always — but more and more — I’m developing ATDD style, often doing Double Loop TDD but not London School of TDD. I avoid mocks as much as possible. More about my testing strategy will follow in a later post in this series.
Hint: I’m using GoConvey as a test framework. Please note that the scenarios in GoConvey are composed with nested lambda functions. If you see some scenarios which (for example) have only one Given and multiple When/Then — the actual execution is running multiple GWTs, so the test cases are independent (but it saves me some repetition).
E.g. the 3rd scenario actually executes “given/and given/when/then” + “given/or given/when/then”. The last (a bit technical) scenario executes 5 GWTs.

The runnable source code is available on GitHub
Hint: This links to a branch that is “frozen” so it matches what I wrote in the blog article(s). Beware that the code, same as the original article, is from 2020 - nevertheless it should run fine.
The other parts of this series are here:
Part 2 - How I implement tactical DDD patterns — the Domain layer
Part 3 - How I structure my application according to Hexagonal Architecture aka. Ports&Adapters
Thank you for your time and attention! :-)
My articles / subscriptions here are for free, but you can support my work by buying me a coffee.