Exploring DDD in Go

2,780 views
Skip to first unread message

Marcus Olsson

unread,
Nov 6, 2014, 8:20:47 AM11/6/14
to golan...@googlegroups.com
A while back, I got interested in how a Go application would look like when applying Domain-Driven Design. I decided on making a Go port of the dddsample application - a sample application that aims to demonstrate DDD in a logistics domain - that was originally written in Java and has already been ported to .NET and Ruby. The main purpose here is to learn Go and to write something that is not your typical infrastructure application, which I already know Go does so well. 

I feel I have gotten to a point where the use cases from the original application are implemented (but far from done). For the next step, I want to take a step back and try figuring out how to make it more idiomatic regarding design, code organization and so on. I'm doing this as a learning experience and so I would really like to hear the opinions of all you seasoned Go developers. Hopefully, some day in the future, it may serve as inspiration on how one may use Go to model a business domain.

The project consists of a frontend, written using AngularJS (another thing I'm currently learning about), the main backend that is exposing a REST API on top of the logistics domain. Finally I have another microservice running to demonstrate a separate context for fetching cargo routes.

The Go backend on Github:

The live demo application:

Some more thoughts I've been having during the project:

Of course, all feedback will be greatly appreciated!

tomwilde

unread,
Nov 6, 2014, 10:50:02 AM11/6/14
to golan...@googlegroups.com
Hi and welcome to Go.

I don't want to be mean so I will objectively say that this:

    func NewHandlingEventServiceFacade(cargoRepository cargo.Repository, locationRepository location.Repository, voyageRepository voyage.Repository, handlingEventRepository cargo.HandlingEventRepository) HandlingEventServiceFacade

Is the antithesis of Go.

Daniel Theophanes

unread,
Nov 6, 2014, 10:56:06 AM11/6/14
to golan...@googlegroups.com
Perhaps, or perhaps not. It is a descriptive name and more then anything, Go is about getting a job done.

Marcus, I like your sample, it looks interesting. I'll be taking a look at it later. Thanks.

egon

unread,
Nov 6, 2014, 11:05:45 AM11/6/14
to golan...@googlegroups.com
Based on a quick review, here's how I would structure the code. (Note that I put this together under 20min, so opinions could change with more exposure).

cmd/cargo-server/main.go
cmd/cargo-server/rest.go
cmd/cargo-server/admin/...

// features
booking/... (contains struct or an interface named Service)
tracking/...
logging/...
inspection/...

// domain related things
cargo/cargo.go (struct Cargo)
cargo/itinerary.go (struct Itinerary)
cargo/voyage.go (struct Voyage)
cargo/route.go (struct Route)
cargo/location/...

// infra
repository/...
routing/...

First, I wouldn't create special folders (interfaces/domain/infra), it would hide the details of the architecture. Although, I know it's not a conventional layout for DDD.

I would try to get rid of *EventService. It makes things harder to follow. Simply create an Event and pass it to relevant handlers.

Any time you have 3 or more nouns in a name, it is probably poorly structured part. I.e. create a new package, simplify etc.

+ egon

Marcus Olsson

unread,
Nov 6, 2014, 11:06:51 AM11/6/14
to golan...@googlegroups.com
Thank you. I must admit that it pained me greatly writing it (and other places like it). I wrote about this in the gist I linked to in the original post but the short (and sad) version is that so far I've done a (rather crude) translation of the Java code. This is also what has been done in the other ports, but in Go it just feels bloated. I had some inspiration from this article but I'm not sure that I would've gone with the Clean/Hexagonal Architecture, had I written the application from scratch. What do you think would be a better way of doing it?

Marcus Olsson

unread,
Nov 6, 2014, 11:08:30 AM11/6/14
to golan...@googlegroups.com
Thanks! Please don't hesitate to ask if you have any questions. I'm looking forward hearing your thoughts.

Marcus Olsson

unread,
Nov 6, 2014, 12:15:46 PM11/6/14
to golan...@googlegroups.com
First of all, thank you for taking your time to make such a concrete proposal for improvement. 

The reason why main.go sits in root is because I haven't found a way of deploying (wercker/Heroku) without main in the root directory. I will have another shot at it.

I would argue that DDD is not limited to a specific layout or architecture, although the current Hexagonal Archtecture for DDD is indeed common in many OOP languages. Finding a idiomatic architecture/design that still conveys the ideas of DDD is actually one of the biggest reasons why I'm doing this. I would say your code structure suggestion is very interesting for this very reason. I'm curious though, what you mean by hiding the details of the architecture? 

*EventService is actually what I'm currently working on. I'm experimenting with different ways of letting domain events be more of first-class citizens then they currently are. If you have any ideas on how communicating between packages using events, I would be very interested in hearing them.

Any time you have 3 or more nouns in a name, it is probably poorly structured part. I.e. create a new package, simplify etc.

This. I would say the code is very much infected by Java at this point and I'm actively trying to simplify it. In a way, I think, by porting a Java application in this way, it becomes painfully obvious why we need a language like Go.

egon

unread,
Nov 6, 2014, 1:48:57 PM11/6/14
to golan...@googlegroups.com


On Thursday, 6 November 2014 19:15:46 UTC+2, Marcus Olsson wrote:
First of all, thank you for taking your time to make such a concrete proposal for improvement. 

The reason why main.go sits in root is because I haven't found a way of deploying (wercker/Heroku) without main in the root directory. I will have another shot at it.

I would argue that DDD is not limited to a specific layout or architecture, although the current Hexagonal Archtecture for DDD is indeed common in many OOP languages. Finding a idiomatic architecture/design that still conveys the ideas of DDD is actually one of the biggest reasons why I'm doing this. I would say your code structure suggestion is very interesting for this very reason. I'm curious though, what you mean by hiding the details of the architecture? 

*EventService is actually what I'm currently working on. I'm experimenting with different ways of letting domain events be more of first-class citizens then they currently are. If you have any ideas on how communicating between packages using events, I would be very interested in hearing them.

I've tried several approaches. One is using an event bus to tie things together: 

services were declared as:

package disk

type Service interface {
HandleMessageReceived(v event.MessageReceived)
HandleMessageSent(v event.MessageSent)
HandleMessageSendFailed(v event.MessageSendFailed)
}

And in main, I brought things together:

Disk := disk.New(Bus, Conf.Mailbox)
Bus.Subscribe(Disk.HandleMessageReceived)
Bus.Subscribe(Disk.HandleMessageSent)
Bus.Subscribe(Disk.HandleMessageSendFailed)

Of course it introduced a lot of wiring in the process; and it was very difficult to follow the code; although it decoupled the code a lot.

Alternative approach is to simply wire most things manually. I.e. inject services directly into other services. e.g.

package mailer

type Service interface {
    Send(from, to direct.Address, content []byte) error
}

type SMTP struct {
    Log logging.Service
}

func New(logger logger.Service) Service {
    return &SMTP{logger}
}

...

Although whether to declare the interfaces in a separate package or not, depends on the exact case. Or whether to declare an interface at all.

I've also started using a simplified bus... http://play.golang.org/p/EoOQ2sHdho
And do the type-switch/casting inside the listener.

Hopefully, it gives you some ideas. Eventually I'll get to publishing something proper regarding this; but don't expect anything too soon :)

+ egon

Marcus Olsson

unread,
Nov 6, 2014, 2:07:18 PM11/6/14
to golan...@googlegroups.com
You've gotten a lot further in your implementation than I have:

type CargoHandler interface {
    HandleCargoWasMisdirected(e CargoWasMisdirected)
    HandleCargoArrived(e CargoArrived)
}

type InspectionService struct {
    CargoHandler
}

s := InspectionService{}
bus.Subscribe(s)


I will take a closer look at your examples. Looking forward to reading about your findings :)

tomwilde

unread,
Nov 6, 2014, 6:45:00 PM11/6/14
to golan...@googlegroups.com
Hi. I'm sorry for the late answer, I didn't check in until now. Your perception is correct; Go is a light, quick and simple language. Some gophers like to call it a "get sh!t done"-language because building structured code is almost effortless.

Most if not all concepts found in DDD and similar methodologies, guides, patterns and philosophies (SOLID; separation of concerns, the open-closed principle and so forth) are all based in the thoughts of people who spent years and years doing Java and other inheritance-oriented object-models like C++. It's hard to structure such applications well while at the same time keeping enough wiggle-room around to change things later. In that sense this wisdom is priceless and has saved many programmers countless hours of work.

But Go is not like Java or C++, Go is much smaller (C++ has 85 keywords, Java has 50, Go has 25). Also, there is no inheritance and embedding is something that is only rarely used. Add to that the novel approach to implicit interface implementation and you have a language where every package hosts its own dependencies and contracts and has clearly defined boundaries.

The only conventions/patterns/rules we consider as "good practice" in the Go community is using the basic interfaces that the standard library provides (like io.Reader, io.Writer) whenever possible, instead of inventing new ones that do the same. Some additional common idioms are described in "effective go" (https://golang.org/doc/effective_go.html).

After you've written some Go code you'll realize (or get the impression that) "patternism" as I like to call it has gotten out of hand; people doing design-patterns are more concerned with the form of their programs than actually shipping functional code. Hence stuff like the often mocked "ProviderBuilderFactoryFactory". That's why I called that function the antithesis of Go.

Marcus Olsson

unread,
Nov 7, 2014, 2:25:57 AM11/7/14
to tomwilde, golan...@googlegroups.com

What you are describing is a big reason why I decided to learn Go in the first place. I can't say I fully understand the simplicity of Go yet, but I now know enough to say that what I have done is simple not the Go way.

My goal at the moment, and the very reason I posted the project here, is to figure out if it's possible at all to refactor away the 'patternism' as you call it, or if I should scrap everything and start afresh with the knowledge I've gained along the way.

I would just like to point out that DDD really is not restricted to a certain paradigm. People have been successful in creating powerful domain models in functional languages where the OOP patterns don't apply. This of course begs the question, is Go really the programming language you'd want to use when modelling business logic. I believe it very much is, but it's still important to be critical about this as well.

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/0hJuub86zpo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

egon

unread,
Nov 7, 2014, 4:18:21 AM11/7/14
to golan...@googlegroups.com
On Friday, 7 November 2014 01:45:00 UTC+2, tomwilde wrote:
Hi. I'm sorry for the late answer, I didn't check in until now. Your perception is correct; Go is a light, quick and simple language. Some gophers like to call it a "get sh!t done"-language because building structured code is almost effortless.

Most if not all concepts found in DDD and similar methodologies, guides, patterns and philosophies (SOLID; separation of concerns, the open-closed principle and so forth) are all based in the thoughts of people who spent years and years doing Java and other inheritance-oriented object-models like C++. It's hard to structure such applications well while at the same time keeping enough wiggle-room around to change things later. In that sense this wisdom is priceless and has saved many programmers countless hours of work. 

But Go is not like Java or C++, Go is much smaller (C++ has 85 keywords, Java has 50, Go has 25). Also, there is no inheritance and embedding is something that is only rarely used. Add to that the novel approach to implicit interface implementation and you have a language where every package hosts its own dependencies and contracts and has clearly defined boundaries.

The only conventions/patterns/rules we consider as "good practice" in the Go community is using the basic interfaces that the standard library provides (like io.Reader, io.Writer) whenever possible, instead of inventing new ones that do the same. Some additional common idioms are described in "effective go" (https://golang.org/doc/effective_go.html).

After you've written some Go code you'll realize (or get the impression that) "patternism" as I like to call it has gotten out of hand; people doing design-patterns are more concerned with the form of their programs than actually shipping functional code. Hence stuff like the often mocked "ProviderBuilderFactoryFactory". That's why I called that function the antithesis of Go.

First and foremost problem with patterns is that people equate them with "good practice". A pattern is there to solve a concrete problem in a context. When you ignore the context and the problem it was meant to solve you get this "patternism". And many of the patterns assume the presence of an class oriented language. Also good Go does contain patterns (e.g. adding an interface/struct as a dependency is a pattern - dependency injection. )

Regarding SOLID, those things still apply (mostly):
SRP - don't overburden the type
OCP - use interfaces as dependencies (e.g. sort)
LSP - doesn't apply
ISP - fine-grained interfaces (e.g. Reader, Writer)
DIP - don't hardcode dependencies

Of course they are principles - not hard rules.

Yes many people come from different languages and try to verbatim translate the patterns, and this can/will cause problems.

+ egon

tomwilde

unread,
Nov 7, 2014, 6:24:16 AM11/7/14
to golan...@googlegroups.com
On Friday, November 7, 2014 10:18:21 AM UTC+1, egon wrote:
First and foremost problem with patterns is that people equate them with "good practice". A pattern is there to solve a concrete problem in a context. When you ignore the context and the problem it was meant to solve you get this "patternism". And many of the patterns assume the presence of an class oriented language. Also good Go does contain patterns (e.g. adding an interface/struct as a dependency is a pattern - dependency injection. )

Hi. In general I agree with that statement. At the same time I'd say that it's hard to draw a line between what is a "pattern" and what is "just a good practice" because patterns are merely protocols for solving particular problems without causing collateral damage, in other words, conforming to good practice. Much how we learn to solve equations in high-school by protocol e.g. "never take the root of a negative number" there's actually a reason for not doing so and in college-algebra you sometimes need imaginary numbers, but for high-school, we don't care.

Stealing your DI example; there's a protocol of how DI is to be implemented and used. The average Enterprise Developer™ can tell you all about the protocol and tell you how it's good cause now their "components are decoupled". However, when confronted with a tricky situation where DI doesn't quite fit the bill they'll resort to 3 layers of indirection (hide the body) and write an apology comment for "infringing The Pattern" instead of being able to weigh the pros and cons of taking a different approach instead.

I consider it part of the Go philosophy that instead of teaching people to learn patterns we give them good tools and code to guide by: Go programmers can, in fact, make decisions because they know their language well and there are no hidden surprises. It's transparent, concrete. The only exception would be concurrency which is easy to get wrong, but it's optional, and if you're interested even that can be learned.

Marcus Olsson

unread,
Nov 7, 2014, 9:10:40 AM11/7/14
to golan...@googlegroups.com
I have created issues in Github based on the feedback from this thread (and will continue to do so). I have already flattened the code structure as suggested, it really felt like the right thing to do and enables further refactoring. Also, after watching Jeremy Saenz's talk on dotGo I felt inspired to take a closer look on how the use of structs and interfaces. 

Jeff

unread,
Dec 7, 2015, 3:20:33 PM12/7/15
to golang-nuts
I know this is an old thread, but I found it while searching for microservice project structure.  I am just about to start modeling one for a new product we have.  To keep it simple for now, I planned on one go project with a folder for each service/ar. I would then have other top level folders for any cross cutting, or other stuff that is not directly related to an aggregate root.  I was thinking about using this ddd project as something to look at as a guide.  I have also been looking into go-kit to use as well.  We will be running each service as an aws lambda function.

Any thoughts, comments, suggestions on a decent way to model a microservices api project? It will be used for all  anon/auth access into our core system.
This is my first *real* golang project, my background is in ms.net c# visual studio
Thanks everyone.

Egon

unread,
Dec 7, 2015, 4:19:40 PM12/7/15
to golang-nuts
On Monday, 7 December 2015 22:20:33 UTC+2, Jeff wrote:
I know this is an old thread, but I found it while searching for microservice project structure.  I am just about to start modeling one for a new product we have.  To keep it simple for now, I planned on one go project with a folder for each service/ar. I would then have other top level folders for any cross cutting, or other stuff that is not directly related to an aggregate root.  I was thinking about using this ddd project as something to look at as a guide.  I have also been looking into go-kit to use as well.  We will be running each service as an aws lambda function.

Any thoughts, comments, suggestions on a decent way to model a microservices api project? It will be used for all  anon/auth access into our core system.

Short answer: depending on your experience -> "don't do it", "do not do it, yet, do it later".

Structure follows Value,
with regards to structuring code: create folders/files based on what is valuable. It highly depends on what you are building. For example "shipping" could be one of the top-level folders, but it might contain "shipping/cmd/shippingweb", "shipping/cmd/shippingapi" and more... implemented as different services.

Do not make coed architecture decisions based on technology you use; but instead focus on why you are building something. (see relevant conversation here https://forum.golangbridge.org/t/comparing-the-structure-of-web-applications/1198)

Microservices make tradeoffs

tl;dr; microservices makes substituting pieces easier, better fault-tolerance and scaling, at the cost of having to develop more APIs and binding points and more difficult deployment. (terms and conditions apply -- as usual, you can do stupid stuff, so you won't get any benefits)

Implement a monolith first
getting things running and booted as a monolith will be easier than with microservices.

Implement your services with interfaces as dependencies to other services. e.g.

package processmonitor

// Note: I do not import some "mailer" package, but declare the dependency inside here
//         ... to reduce coupling; and the "mailer" package, is pretty much irrelevant here
//         ... it also makes testing easier...
type Mailer interface {
    Send(from, to string, content []byte) error
}

type Service struct {
    ErrorMailer Mailer
}

Then inject the Mailer service from outside this package... (unless you have a sufficient reason not to.)

When you actually have a pressing need to convert some piece to a microservice, substitute the implementation (with RPC, REST or whatever) to another server.

+ Egon
Reply all
Reply to author
Forward
0 new messages