back
168 comments
Go did rise to prominence by being the programming language behind such high profile software as Kubernetes, Docker, and Prometheus. None of those are business applications and Rob Pike (ex Bell Labs guru and co-inventor of Go) gave a presentation about Go in 2015 where he explicitly identified that Go was invented for infrastructure and not business applications.

I have encountered many engineers who are excited about Go and want to adopt it for enterprise systems. In 2019. I evaluated Go for that purpose. You can read my evaluation at http://glennengstrand.info/software/architecture/microservic...

What I discovered is that Go is not any faster than Java on a light weight framework. Here is the part that may seem controversial for the folks here. The simplicity of Go did not result in simpler systems.

I found that the lack of inheritance hampered me in expressing solutions to complex business rules and data models. Many of you may disagree with that and counter argue that inheritance is a bad thing. That is most probably one reason why there is so much interest in Go. It intentionally lacks support for inheritance. I agree that inheritance can and has been misused a lot. It is an advanced programming language feature that should be used sparingly. Removing inheritance entirely reduces the expressiveness of the programming language, especially when it comes to enterprise computing.

I’ve done a huge amount of “enterprise architecture” going back 20 years or so. I’ve designed and built horrible things that scare most people to death. Imagine monoliths with over 1000 DD and ORM mapped business domain classes, 2000 http endpoints and hundreds of database tables glued into tens of megs of Hibernate mappings.

Inheritance is the number one cause of these turning into a massive shit show. It’s not a pattern that can ever be refactored into something else down the line once the decision is made. You end up with things that can’t be fixed and escalating costs like you have never seen just to keep the plates spinning.

I always favour composition for that and very light weight service oriented architecture.

Roughly the only thing that scales is things that have fairly strict command-query type segregation. Carefully designed REST interfaces are a good example of that.

Regarding complex business rules they are mostly best evaluated through simple stacked middleware, messaging, matching, routing and workflow style applications. All of those are feasible with the simplest of languages. Hell half the world is still hanging off rancid bits of COBOL.

If you are short circuiting your evaluation on this basis I think you are making a lot of noise without a lot of experience.

I've used Go on both infrastructure and web projects. "The simplicity of Go" entirely depends on architecture and code quality. Go will force you into some design patterns that are not too favorable if you're not familiar with them. When I was helping people with Go I would see it a lot when people first start using interfaces. I would never claim, as you have, that Go cannot be used on the web or that it won't be just as effective as Java + Spring. Generally I've found most tasks between Java and Go to be similar in complexity (and requiring different approaches), as it seems you found. Where there is a massive difference is tooling and dependency management. That absolutely contributes to developer ergonomics and is a fairly weighty point that is not mentioned in your analysis.

Personally speaking, choosing between languages is a non-starter conversation for me. At this point, since my childhood, I have rotated through 10+ languages. Languages are certainly tools and picking the right tool for the job is important. That said, I don't want to work at another company that mandates one language (as is commonly the case with Java) across the entire enterprise. We need solutions that bridge tooling language gaps for enterprises more than we need archaic mandates about what I'm allowed to write in for a given purpose.

In my experience, I've found the lack of polymorphism to be the larger challenge in building complex business systems in Go.

Managing a bunch of polymorphic entities in Go often requires implementing a bunch of interfaces in various types. You then have to write a bunch of mapping logic for every type, driven by type assertions. Apply this in a large codebase that encapsulates transport, data access, and so on and you end up with a ton of complexity.

> The simplicity of Go did not result in simpler systems.

I echo this 100%. I worked at an employer who heavily used golang. The resulting projects were honestly a mess, yet they pushed through.

What's ironic, is that they ended up reinventing the wheel on so many different things, including DI and an entire application framework. Millions of dollars spent writing and maintaining these libraries, where Java already had them ages ago.

As you point out, the modeling capability of golang is extremely subpar. It results in verbose code that is very sparse, lots of code to implement something that would have taken a few lines in a language like Java and C#, let alone something more dense like Scala.

The way interfaces are handled in golang makes it very annoying to try to find out which types implement said interface. It puts a lot of pressure on the IDE to search the entire code base, and you end up with types you don't even care about. It clearly shows that the golang authors did not have IDEs in mind when writing the language, which is just absurd as it's supposedly a language designed for "programming in the large". Anyone who used "goland" knows what I'm talking about. Try refactoring a type, and have the IDE scan the entire code base to look for comment strings, which it has to because golang has no notion of doc strings like Java or C#.

Add features like records, pattern matching, enums (the amount of code that had to be written to implement something to emulate enums was just absurd and frustrating to deal with, not to mention error prone).

The concurrency aspect of golang is decent (though it will be even better in Java). Other than that, the language doesn't really have anything going for it other than having a large brand name backing it.

(a pedantic correction: Pike originally describes it as a systems language, which can include infrastructure)

Thing is none of the above is going to apply when generics land. You mentioned kubernetes which use complex, maintainable abstractions and structures with only the facilities Go provides. There isn't that much difference between writing a maintainable system, whether the primary user is the business, or other software.

Would you be able to share an example of an abstraction you wanted to create in enterprise computing where lack of inheritance stymied you?
The thing with Java is that it's harder to make code fast, in Go it's easier.
So I think DDD is basically one of the best pattern that has ever been discovered for developing good software (thinking of it along with gangof4, SOLID, etc). There's a lot of cruft around it and consultants looking to over-complicate it for the sake of getting hired, but viewed simply it's mostly about abstraction at conceptual boundaries -- very hard to be against that on most large projects.

That said, can someone weigh in on how it is building large "enterprise-grade" applications in Go 1.x given how (purposefully) constrained the type system in Go is? While protocols in Go are great (one of the very many breaths of fresh air that Go provided), operations on data structures without generics does not seem like a good time. Does everyone get around the lack of generics by making most generic-looking operations slightly-specialized structs and some interface composition?

I'll be honest, I view DDD as an org smell at this point. Every place I've encountered it, it was attempting to fix with process something that needed to be fixed with culture.

That is, in places where "product understands the problem it wants to solve, and wants to engage with dev about it", and the engineering manager cared about the problem, and worked to get the team to understand and care about it, pitch potential solutions, and then deliver them in a prioritized manner, DDD wasn't even suggested. It just wasn't necessary; all the useful artifacts from it happened naturally because product and dev were so closely aligned.

In the places where product didn't really know what problem they wanted to solve, and didn't want to engage with dev, and the org pressures were around documentation and gating, DDD was heralded as the cure-all that would get everything flowing smoothly, while solving many of the documentation and gating needs. It didn't do any of that.

As awesome as Go is, Java & SpringBoot are so entrenched in the enterprise world that Go has to come up with something much more awesome.

It's very hard to justify why teams within an enterprise should not use Java/SpringBoot. A typical enterprise already has thousands of developers who support and develop existing critical business applications. Due to historical reasons most of these developers are familiar with Java and they find it hard to justify using another language.

That coupled with the fact that Java and SpringBoot made some really good choices and can compete with most newer language features, its hard for other languages to penetrate that.

> can someone weigh in on how it is building large "enterprise-grade" applications in Go 1.x given how (purposefully) constrained the type system in Go is?

I'm not a Go developer, but as a C# developer I can say that a more powerful type system, generics etc can be both a boon and a curse - especially so for large enterprise systems.

I've seen these go both ways, and it ultimately comes down to two things:

1. being able to create a design with the right abstractions more or less from the start 2. not over-using generics and inheritance

If you don't succeed with these, or/and you don't forsee some things you end up needing later, the code becomes an incomprehensible mess, as developers try to crowbar functionality into the constraints of the design, or make things even more complicated by refactoring and introducing even more use of generics and inheritance.

Use packages as layers: https://www.gobeyond.dev/packages-as-layers/ and you can probably write arbitrarily large Go systems, or at least, well beyond a point where most projects get.

A lot of the problem emerges if you try to use some organizational pattern that, without you perhaps realizing it, is foundationally based on being able to have circular dependencies, or otherwise working poorly with Go's visibility rules.

I have found myself becoming increasingly disenchanted with all the heavyweight design methodologies over the years anyhow. They amount to a claim: "All problems you may ever face have this structure and as a result this is the optimal structure to meet those problems with code." The problem is that first bit is wrong. Problems have all kinds of different structures. Start with the layered approach and your code will naturally follow the faultlines of the problem, whatever it may be. Try to jam the code into a particular structure up front and you just have to hope that it is the right one; you won't know until you've put a lot of work in, and, almost certainly for any non-trivial project, there will be at least one major component for which it is actually wrong. (And often it's just wrong across the board.)

Worse, people who get too focused on a particular up-front highly-structured design lose the ability to even realize that their structure is the wrong structure; it starts becoming the very lens through which they see the world.

Well, the truth is a lot of applications don't actually really need generic data structures beyond those already provided (maps and dynamic arrays). If you do need that, yeah, you're in for nearly duplicate code. Over time I think you'll just start to approach problems differently and not really think about the limitation much, or that has been my experience.
I built a large enterprise website in go, and I can say that I barely ever even had to consider the lack of generics. Occasional slightly annoying interface shenanigans and mild interface hackery, but nothing significant at all.
I've been around the block a few times on modeling complex domains, and have found that generics do not help in a fundamental way.

If you back all the way out to the theoretical/academic realm and view the problem domain through the lens of normalization, you would probably find that any opportunity for use of generics to be a sign that you have not achieved 3NF.

Our core domain models are extremely simple types. They have no other complex types as properties and could be mapped 1:1 to SQL tables without any complication.

We could have certainly leveraged generics if it made sense (we use C#). But, for these core domain model types it causes more harm than good.

Have a large line of business application written in Go. The type system is just fine. Here is the key: stop writing CRUD structs and junk. Use a framework that understands your schema / data storage system. Declare what you need done, then let application definitions do the work.

"Microservices" is a business setting is somewhat laughable. After a number of years, yes, I have a handful of services that run outside of the main program. But the Line of Business core application might as well be a single executable.

From my experience generics are the most useful at the library level. For containers you obvious want it for many libraries it is useful.

Most of the time once you are getting to business-oriented software I think you tend to have more concrete implementations and you don't miss generics much (other than the implementation side where there is the pain of using libraries that didn't have access to them)

> Does everyone get around the lack of generics by making most generic-looking operations slightly-specialized structs and some interface composition?

I think this is largely correct. It's pretty rare that I find myself wishing for generics; rather, I find myself frequently wishing for sum types (Go has workarounds but they tend to be laborious to express, keep updated, etc and they still make weaker guarantees wrt exhaustivity).

That said, most of the complaints I hear about generics are about terseness rather than correctness (i.e., people want to elide lots of little bits of boilerplate), which is perhaps why I don't find myself missing generics very much--I'm happy to put up with verbosity as long as correctness and readability are preserved.

As if type systeme is necessary to build "enterprise-grade" software, "enterprise-grade" sounds to me like bloated Java with terrible framework like hibernate.
> While protocols in Go are great (one of the very many breaths of fresh air that Go provided), operations on data structures without generics does not seem like a good time. Does everyone get around the lack of generics by making most generic-looking operations slightly-specialized structs and some interface composition?

When it comes to domain code - probably when you are starting to think about generics that's a bad sign. Probably you are trying to make it overcomplicated. Domain code should stay simple and interfaces should be enough to handle it.

But when it comes to libraries the situation is totally different. One of the example is https://watermill.io library (that we are authors of BTW). Generics could give some nice simplifications.

It's also a case for event-sourcing library that we are using in the company where we are working on now. But You can still do a lot with interface{} and reflection. But it's not perfect and generate a lot of boilerplate.

The third example is a decorator pattern. You can't create generic decorator without code generation. It is of course some sort of the solution, but it's not trivial to implement.

Lightweight DDD is a great match for Go. You don't want to go all-in -- you don't want a package usecases, for example. You just want to take the high-level concepts and apply them in a way that's idiomatic to the language. The Hexagonal Architecture is basically equivalent.
We build enterprise-grade applications in Go 1.x. There's no trick. You just type a lot.
map[string]interface{}
I would not recommend many of these patterns. A business application needs are specialized; create a framework for your needs, then define the application into being. Make your functions as large as makes sense, which is typically fairly large. Ignore most of these hot buzzwords.

Define the needs of the application, develop a framework to meet those needs, then define the application into being. Never write a business application screen by screen; define it into being screen by screen.

If this post were called "We wrote a book about our approach to building DDD/CQRS business software using Go", I would be perfectly fine with it.

Feels like a clickbait, especially after your learn that the consultancy that wrote the book built their whole identity around being "DDD in Go" experts. Just don't push it on everyone. This kind of marketing is doing real harm to Go newbies, like that "standard Go project template" repo.

Here is example code and blog series for a step-by-step DDD-based refactoring of an existing app. Don't know if they are the same as what's used in the book, but found them quite interesting.

https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-exampl...

Hey, one of the authors here. The example is exactly what we base the book on. The chapters go through refactoring of this app.
I've been involved in the European DDD community from the start, sparked the initial IDDD tour with Vaughn Vernon, and am still highly involved, so you could say I know a thing or two about DDD, and I consider the patterns most people think about when being introduced to DDD a bit similar to the inheritance part of Object orientation: it's pars pro toto and you are probably missing out on the valuable part of DDD:

First: DDD will not magically fix all your problems; however, it does ask the right questions: what is your common language, how does your code align with your organisation, what are the boundaries and the relationship between boundaries, what is your core domain and where might it make more sense to buy something existing etc... So while it is not the silver bullet, it might help you to pinpoint some typical issues with your current code base.

Second, people new to DDD tend to focus on tactical patterns and things like CQRS etc, which might be interesting but it's usually way less important than the strategic part of DDD.

Third, it also attempts to define nomenclature for developers, f.e. about responsibilities and granularities: aggregate roots, bounded contexts, commands, events etc...

Like every other methodology there is some cargo culting going on, and that is also the reason I decided to spend a few years outside the echo chamber. However, now that I'm back it's great to see how the methodology is maturing and the community trends to attract the right kind of people.

TL;DR: DDD can be useful, but don't blindly start applying tactical DDD patterns everywhere without understanding the bigger picture; this will only work against you.

Edit: some line breaks

Look, I know email marketing is everything. I would love to take a look at the book but not giving out my email, sorry.
The book content is basically on their website: https://threedots.tech/series/modern-business-software-in-go...
Love this!

Hey, one little tip. I see you're using LaTeX for the book. If you switch to xelatex to compile it, you can use your own fonts, like this:

  % Set the typeface to use
  \usepackage{fontspec}
  \setmainfont{Libre Baskerville}
And the compile command:

  xelatex mybook.tex
Thanks for the tip! I think we already use xelatex, as the book is created with pandoc (https://pandoc.org/).
Aren’t the first 3 or 4 chapters basically what you can find in Google’s own cloud toturials?

Why does the domain-driven-design chapter come after you’ve tied the reader into GCP? I can’t think of a single enterprise business domain where that would make sense from a European GDPR driven perspective.

Why do you think you can cover that many topics in a single book? I mean, part of the reason Clean Code is well liked is because it covers one topic in depth, rather than covering a bunch of different topics without ever giving the reader anything of value on any of them.

Have you considered going the TDD GOAT book route of earning sales?

I’m really sorry that I’m so negative, but your “contents” read like every terrible tech-book I’ve ever opened. Good luck though.

Hey, these are good questions.

> Aren’t the first 3 or 4 chapters basically what you can find in Google’s own cloud toturials? > Why does the domain-driven-design chapter come after you’ve tied the reader into GCP? I can’t think of a single enterprise business domain where that would make sense from a European GDPR driven perspective.

Our idea was to create a seemingly modern app based on microservices, with full GCP setup and fancy tools used. We then go on to point the issues in the code. We wanted to show how an app that seems well built on surface can have hidden problems that are hard to spot. We don't deep dive into GCP, mostly just describe the setup.

> Why do you think you can cover that many topics in a single book? I mean, part of the reason Clean Code is well liked is because it covers one topic in depth, rather than covering a bunch of different topics without ever giving the reader anything of value on any of them.

I believe you'll find a lot of value on these topics, even if we don't go super-deep into each of them. It should give readers enough ideas on how to approach building complex apps. We base this on our experience, so it's not just bland descriptions of the patterns. Also it's all focused on Go and on a real example project, so it's not abstract.

I signed up for the newsletter (~10h ago), no ebook yet but I'm apparently enrolled in a mini-course of the same name?
Hey, you should receive links to the ebook in the first email after confirming subscription.
Subscription confirmation email link goes to a 404 page on mailchimp. You guys might want to check it out.
Can someone just post a PDF of the book so I don't have to give them an email?
If you don't want to share your email, you can still read (almost) the same content on our blog. If you sign up, you'll have access to the most recent PDF, as we update the book with each new article.
I signed up for the newsletter but got no link to the book so far.
Hey, unfortunately our e-mail automation is a bit slow. It should arrive to your mailbox within couple minutes :)

I'll add information to subscribe confirmation page about the delay.

Big Threedots guy.
Pleasure is all mine!
I’d rather pay for the book than give you my email.
DDD-Lite
I took "Show HN" out of this title because requiring an email signup to get access to the content is against the spirit of the rules: https://news.ycombinator.com/showhn.html, which say that there needs to be a way for people to try it out. In the case of a book, the established way to let people "try it out" is to share a sample chapter. If you share a sample chapter on your web page, rather than just a sample page, we can put "Show HN" back. (Edit: done now.)