ga('send', 'pageview');
Categories
Metod Teknik

Domain Driven Design in Go: Part 2

In my previous post I announced a project I have been tinkering with lately; porting an existing DDD sample application to Go. I elaborated a bit about its background and the general structure of the application. In this post we are going to have a look at some of the implementation aspects that I have encountered so far.

In my previous post I announced a project I have been tinkering with lately; porting an existing DDD sample application to Go. I elaborated a bit about its background and the general structure of the application. In this post we are going to have a look at some of the implementation aspects that I have encountered so far.

To be honest, if you are coming from a Java or .NET background, implementing your domain model in Go is a really straightforward process. If you are familiar with the Go fundamentals, most of the DDD building blocks will probably look like you expect them to, only simpler. However, I would like to take a look at two of the most common: entities and value objects.

Entities and value objects

By entities and value objects, I am referring to domain objects that are referenced by their identity or their value. Two people with the same name living at the same address are still two different people. On the other hand, you probably would not care if we switched dollar bills, as long as they have the same value. Basically you should ask yourself whether two instances with the same identity but otherwise completely different attributes are the same thing. If that is the case, you are most likely dealing with an entity.

In Go, both entities and value objects are just simple structs, where the entity typically has a field containing its identity.

type Cargo struct {
    ID        TrackingID
    Itinerary Itinerary
    ...
}

type Itinerary struct {
    Legs []Leg
}

What separates the two here, is the presence of a tracking ID field in the Cargo entity (which also happens to be a value object in itself). Simple.

Comparing identities and values

In Go, for any values that are comparable, you can use the == operator to test equality. structs are comparable if all their fields are comparable, and as you might expect, most of them are. However, if you tried comparing two instances of Itineraryin the example above, your application will panic. This is due to the fact that Go does a shallow comparison of the two objects, meaning it will not recursively compare the objects in Legs. This was probably not what you intended, hence the panic. In this case, there are two alternatives.

Either you define your own method for comparing the value object and compare each element explicitly.

func (i Itinerary) Equal(other Itinerary) bool

Or, you would consult the reflect package that will do a deep comparison for you,

reflect.DeepEqual(i1, i2).

The first alternative might lead you to implement one for the entity as well, which might look something like this.

func (c *Cargo) Equal(other *Cargo) bool {
    return c.ID == other.ID
}

This was actually my first approach. Unfortunately, since the domain objects were so lean and mean, adding one of these methods to every struct really felt like a thorn in my eye. It felt like I was back writing Java again. In practice, the places where I do any comparison are so few that all those methods really did not feel like they provided enough bang for the buck. I actually ended up using reflect.DeepEqual() where it was needed.

Expressing your domain using method receivers

The general rule when you are juggling value objects is to keep them immutable. If you want to change a value object, you should replace it with a new one. While you cannot specifically define a type to be immutable in Go, there are good reasons to treat them as such. Since an immutable object cannot change its state after being created, it not only makes life easier in concurrent applications by reducing the need for mutexes, but due to the escape analysis in Go, it will allow your objects to be allocated on the stack rather than the heap, which can result in less generated garbage. A nice way of enforcing as well as communicating mutability in Go, is by using method receivers.

The way you define a method in Go is by adding a method receiver to a function. If you come from a language like Java, it is somewhat like the this keyword, except you can name it anything you would like. Not only that, but it allows you to essentially define two types of methods, depending on if the method receiver is a pointer, (c *Cargo), or a value (c Cargo) (notice the asterisk). By using a pointer receiver, you are able to mutate the instance from within the method, while if you are using a value receiver any changes are made to a copy. In the following snippet, any changes made to the itinerary in the IsEmpty() method will be discarded when the block exits.

func (c *Cargo) AssignToRoute(i Itinerary) { ... }

func (i Itinerary) IsEmpty() bool { ... }

Another way of seeing it is that entities, as opposed to value objects, have their own lifecycle and are likely to change (while keeping their identity). For this reason, in the goddd project, you will see that entity methods typically have pointer receivers while value objects have value receivers.

As a side note, unlike other languages, method receivers in Go allows you to choose whether to write a method or a function. This allows you to use methods for exposing behavior while extracting those private helper methods into functions. Needless to say, method receivers are powerful tools that offer simple ways of expressing your domain.

Next time

In this article we have looked at how entities and value objects might be implemented in Go. Naturally, the choice of programming language will have a direct impact on how your domain model is represented in the code. Go is a language that offers simple syntax that enables a refreshing business-to-code ratio. Hopefully, this post will serve as inspiration and that you might even consider using Go for your next DDD application! In the next part of this series on Domain Driven Design in Go, we will take a look at go-kit and how we can add logging, metrics and how we integrate with another bounded context.

EDIT

So I was asked about my reasons for not explicitly defining equality for entities and thought I would update the article with my response. First of all, unlike other languages, you cannot redefine equality for types in Go. The closest you get is to add an Equal(...) bool method to the type you want to compare (link). I would like to add that while this is the most idiomatic approach that I have found, I did not want to add it unless I felt I had a good reason. I agree that having an explicit definition of equality might be reason enough and I am reconsidering to add it back again. Whether or not this applies to value objects as well, I am not so sure. Using the reflect is pretty crude and a Equal method for value objects would make it symmetrical with the way entities are tested for equality. On the other hand, adding it for all value objects feels overly verbose and means that you essentially will have two ways of testing value equality since you still may use the == operator. Let me hear your thoughts.

Learn more about DDD

Leave a Reply

Your email address will not be published. Required fields are marked *