Given the simple yet expressive syntax of Go, it should allow me to really focus on expressing the business logic, the ubiquitous language of a domain. Inspired by Paul Rayner’s work on porting the DDD Sample App to Ruby, I felt compelled to start working on my own port of the application, using the Go programming language.
Since Go is still object-oriented in a minimal sort of way, it was fairly easy to just rewrite all the Java classes to Go. In a relatively short amount of time, I had the basic use cases such as booking a cargo, assigning it to a route and making sure it had a valid state. Somewhere along the way though, something started bothering me. I started feeling that I was forcing a design upon the code that had made sense in Java, but felt contrived, or even bloated, when written in Go. I focused on how I would implement the building blocks of DDD such as Value Objects and Entities, but the
SameIdentity() methods were really starting to pollute my packages. Finally, I decided to remove them all, and it was about then I started realizing that the code had become so lean and mean, that each line that did not express the language of the domain, felt superfluous. It felt like I had removed a lot of unnecessary syntax while keeping the domain logic intact and possibly making it even clearer without all the clutter. But it still did not feel like I was anywhere near a idiomatic port.
When organizing the code according to the Clean Architecture, or Hexagonal Architecture, as the original sample application had, I was getting packages containing only a few lines of code. I liked the idea of having pure domain packages so that if I would go get one of them, it would come, preferably, completely without any dependencies except other domain packages. The result then was, because tasks like setting up a REST API or persisting domain objects to MongoDB requires so little code, the remaining packages would contain one file with just a few lines of code. Furthermore, the reusability of those packages would be close to none. A Q&A session on the recent dotGoEu conference caught my attention when the following question came up:
Q: Antipatterns arise in every language, especially new ones as people find what features can be used and reused. What antipatterns do you see, and what are the better patterns?
Dave Cheney: Too many packages. Packages should be at the granularity of an idea. HTTP is an idea; an HTTP client is not an idea.
Cheney’s answer resonated with the thoughts I had been having. Cargo is an idea but a MongoDB implementation of a cargo repository is not. One could argue that “infrastructure” is an idea, but I would say it is too vague, too general. But where else would I put it?
By now, the progress on my port had grinded to a halt. So I decided I would ask Rinat Abdullin, who has been doing DDD with Go on his current project, a swedish dating site, about what thoughts they were having. His answer was more comprehensive than I could have hoped for, and I highly recommend reading it. Basically, he and his team went for the decision to include the implementation details into the package and exposing the behaviour through a REST API, making each package at the granularity of an idea, with a clear focus on communicating using domain events.
Indeed, their design is much different from the design of the original DDD sample application (as well as my port). When doing a port like this, I believe that it should not just be about changing syntax, but also adhering to the design principles of the programming language you are porting it to. As such, I am currently trying to decide on where to go from here. Either I accept that it was indeed a great learning experience and acknowledge that the original design just is not the Go way. Or, I start refactoring towards a completely new, more idiomatic, design. I would love to hear your thoughts!