Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: We wrote a book about building business applications in Go (threedots.tech)
269 points by roblaszczak 6 months ago | hide | past | favorite | 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.


>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#.

Even if this was the case years ago - it is not now with an LSP.


It still is, it's inherent to the way interfaces are designed. Did you use golang on a monorepo for example?


>Did you use golang on a monorepo for example?

No. I have met monorepo on my path only once (not a golang one, it was mix of java and jruby) and I hope I will never see one again.


golang fails miserably in a monorepo setting, which is ironic because it was designed at Google, and Google are known for their monorepo.


>which is ironic because it was designed at Google, and Google are known for their monorepo.

Google is huge. I'm fairly sure they use different approaches to code organisation in different teams\departments.

Anyhow - I'd rather thank them for that. Now chances I'll have to deal with monorepo are lower. I'm going to think of this as a feature, not a bug, lol.


May I ask, what do you mean and why do you say so? What is wrong with golang in monorepos?


Because interfaces are nominally typed, it's very simple to accidentally implement interfaces that are defined in other parts of the code base, even though they are not related to your project. This makes searching for implementors of a given interface in a project show implementors you don't care about, which makes navigation and discoverability very tedious.

Furthermore, golang does not have a proper format for docstrings like Java or C#. When refactoring, the IDE tries to be smart and parse all comment strings as they are doc strings, and when you want to rename a struct, you're going to get a huge dialog with so many entries which can be false positives, and you have to go through them manually to check whether or not they apply. Same as with the interface issue, similar struct names may appear all across the code base which makes this refactoring very tedious.

There are other issues of course, like the fact that golang has no constructors, and structs are defined adhoc, meaning adding a new field to it needs more hunting across the code base to make sure you don't miss defining and break something.


> I worked at an employer who heavily used golang ... they ended up reinventing the wheel on so many different things, including DI and an entire application framework.

Sounds like they were trying to force the language to be something it's not. DI frameworks (I think what you mean) are basically incoherent in Go, dependency injection is naturally modeled with interfaces. In fact any inversion-of-control style "framework" is a mismatch to the language, it (intentionally) lacks the features that frameworks rely on to deliver benefits that outweigh their costs.

> The way interfaces are handled in golang makes it very annoying to try to find out which types implement said interface.

The desire to do this reflects a misunderstanding of interfaces.


Take a look at the major DI frameworks for Go, Wire [0] and FX [1]. They just plumb the gaps between methods that return types and methods that want them. Although technically they work for concrete types, 99% of the time it's going to be an interface, so that you can substitute a mock for testing.

Prior to DI frameworks we used global variables, often initialized in a package's init() method.

>The desire to do this reflects a misunderstanding of interfaces.

Generally you have a few layers: gRPC/HTTP/Kafka handler, business logic, and database or external service access. Layers are unit tested individually against mocks of the layers below. Because you're going to inject a mock, you can't depend on a concrete type, so you depend on an interface. Often when you're developing you want to know what the concrete implementation of a lower layer does, so it's useful to have "go-to-definition" see through the interface declaration to its implementations.

I think the implicit satisfaction of interfaces is very cool and I wish I had it in every language. I wouldn't give it up just to simplify the IDE's job. But the IDE having this functionality does matter.

[0] https://github.com/google/wire

[1] https://github.com/uber-go/fx


> Take a look at the major DI frameworks for Go, Wire [0] and FX [1]

I'm well aware of them. (There are several more, too, which see at least as much use as these two, unfortunately.) They also reflect a misunderstanding of the language. Wire less so than FX. (I'm also aware Wire is written by Googlers, no need to point that out.)

> Prior to DI frameworks we used global variables, often initialized in a package's init() method.

I don't understand how these things are related. DI frameworks aren't necessary for Go-idiomatic dependency injection, and global variables were never an appropriate way to manage dependencies.

> Generally you have a few layers: gRPC/HTTP/Kafka handler, business logic, and database or external service access.

Yes!

> Layers are unit tested individually against mocks of the layers below.

Yes!

> Because you're going to inject a mock, you can't depend on a concrete type, so you depend on an interface.

Yes!

> Often when you're developing you want to know what the concrete implementation of a lower layer does, so it's useful to have "go-to-definition" see through the interface declaration to its implementations.

...no?

There are basically two situations where you use an interface in this way. One, most often, in an application where the "production" code uses one implementation, which you know a priori and therefore have no need to go spelunking. Or, two, in a library where you by definition cannot know what implementation your users will provide to you. In the first case, your mock would model the implementation, which I guess is trivial. In the second case, your mock would reflect your expectations of the interface's implementations, explicitly without knowledge of any implementation.


> which you know a priori and therefore have no need to go spelunking.

Except when you're working on a codebase that's new to you, and you're trying to navigate it to make a change somewhere, so you don't have a priori knowledge about what implements which interface.


> Sounds like they were trying to force the language to be something it's not

They're getting around the lack of features in the language. Most languages that I know of have mature DI solutions, and those are needed for writing any non-trivial app.

Also, one of said frameworks I was referring to generates mocks for your app and wraps errors so you automatically get stack traces in errors that are being passed around. Something already solved in Java and C# and practically any language with exceptions.


> Most languages that I know of have mature DI solutions, and those are needed for writing any non-trivial app.

Patently false.

> Also, one of said frameworks I was referring to generates mocks for your app and wraps errors so you automatically get stack traces in errors that are being passed around. Something already solved in Java and C# and practically any language with exceptions.

You're speaking as if generating mocks and adding stack traces to errors are... features? They're not! Generating mocks almost totally defeats the purpose of using them, and stack traces have no business being attached to errors in the general case.

Sometimes I truly don't understand the context that my peers are working in. Totally incoherent...


> You're speaking as if generating mocks and adding stack traces to errors are... features

They're working around a weakness in the language. Stack traces in exceptions are definitely a feature. golang doesn't offer it out of the box, which is why the abomination of a framework I was telling you about had to be written. Similar things had to be done to work around the lack of generics, or the lack of composability of goroutines, or even in normal functions because of lack of composability of error handling.

> Sometimes I truly don't understand the context that my peers are working in. Totally incoherent...

Exactly what I think when I see people writing large projects in golang, and all the messes they have to go through to work around its limitations.


> They're working around a weakness in the language. Stack traces in exceptions are definitely a feature.

OK, I understand this is your position. I don't share it, nor do the authors of the language. So I guess it's not a truism.


The language authors did not work on large systems. If their position did hold any merit, we wouldn't have seen many people try to solve the shortcomings of their language.


> The language authors did not work on large systems.

Um. Alright.


Sometimes these "everyone else is wrong about everything they think is obvious and only I know the truth" takes can be interesting, but usually when accompanied by some kind of reasoning or presentation of an alternative.


(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.


https://www.youtube.com/watch?v=rFejpH_tAHM

“Go is software for the parts that we work on which is mostly infrastructure.”

Generics is a good thing and I look forward to seeing the details in how Go will support it.


You can say the same about Java and C#. These claims about golang are all baseless as far as I'm concerned, especially since they're contradicted by reality.


Would you be able to share an example of an abstraction you wanted to create in enterprise computing where lack of inheritance stymied you?


I was going along with the comment till they mentioned inheritance too. I hope they can share such an example.


Like I already said, inheritance is an advanced programming language feature that is easily misused. To give a specific example out of context would just leave you open to inventing context that would yield inheritance as an improper choice.

There is plenty of advice online already about how to use inheritance properly. In general, inheritance should be used to signal taxonomy. It should not be used to DRY unless there are good extenuating reasons behind that. There are lots of frameworks that require the use of inheritance which is somewhat unfortunate. You are most probably okay until you find yourself starting to form deep inheritance hierarchies. If you do have deep taxonomies, then consider modeling that in data (e.g. widgetType or widgetParent) instead of using inheritance. The decorator pattern is also used a lot when inheritance gets out of hand.


> inheritance is an advanced programming language feature

It really isn't.

> that is easily misused.

If a tool is misused frequently enough, it really isn't the fault of the users.


The thing with Java is that it's harder to make code fast, in Go it's easier.


My only experience with Go was to help advise a team when porting a Java cache. They struggled with performance because Go kept fighting them. This was partially due to it having a poor concurrency library, resulting in excessive use of read/write locks whereas Java's advanced capabilities made more things lock-free. The database founder noted that Golang is slow single-threaded but makes up for it via concurrency and low latency.

To my unexperienced eyes, it seemed like a real PITA mostly because the Go authors insisted on artificial limitations externally, but bypassed them internally. I certainly have not found it hard to make Java fast and the core developers have always been open minded when I've talked to them.


Sun made huge investments into HotSpot to optimize reusable object-oriented code. As I understand it, GC and interface method dispatch on heap objects are relatively slower in Go so they’re mildly discouraged.


Could you provide an example or citation?


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.


Yeah I'll pile on here. DDD is a process fix, not a tech fix, and broadly unless you're using Java, you can't implement it very well. Or, you'll find that you're actually using it without knowing it. Case in point, we used Django at my last gig and Django basically is DDD (apps are domains, models are entities, etc). We then started implementing DDD and essentially built weirdly named Django on Django, with way worse performance--and now any engineer we hired had to know two things (Django _and_ DDD).

I wouldn't necessarily blame DDD here though? Mostly the problems were we didn't have reasonable performance metrics. Our CTO thought we had a problem, then he decided we should implement DDD, then we implemented it, and he thought we no longer had a problem. But there were no numbers to point to, we didn't know if we were delivering features faster/slower, if our PRs were smaller/larger, if our defect rate was higher/lower. We _did_ know our app was slower, because it would timeout (ha), but that's kind of the worst way to find that out.

But, generally my argument here is:

Think of any baller software project you use. Is it VS Code? Chrome? GitHub? NGINX? Do any of these use DDD?


DDD does not have a canonical implementation in the form you suggest - the modern consensus is that it is a problem space tool, not a solution space one. The meat of DDD is in the strategic design section, not the structural patterns which have clearly not evolved for the modern world.

And no, none of those pieces of software listed use DDD, because none of them are business applications, and it would be very inappropriate for them to do so.


In fairness though, I think "business application" doesn't have a clear definition. What separates them from client/server games? Suddenly yeah, now you're thinking about CQRS, reading account information from a database, serializing state--certainly sounds like a business application to me. Doesn't Chrome connect to Google servers that talk to databases? Isn't VS Code an Electron app that talks to backend services?

I would argue that thinking about things in terms of application type isn't that useful. Rather, I've gotten more mileage from thinking about the lower-level problems. Like do we have to deal with concurrent modification, do our users span timezones, blah blah blah. These things give me more to work with when designing an application than like, whatever its "type" is.


> Chrome? GitHub? NGINX? Do any of these use DDD?

These are not business applications, they are general purpose software. A business application is something made to model a business process or an entire business, such as an ERP. DDD is perfect for this.


Any software can be described as a business application, for Chrome it could be that I want a software that's able to browse and render web pages.


> Think of any baller software project you use. Is it VS Code? Chrome? GitHub? NGINX? Do any of these use DDD?

That's a fantastic question. Something I've often wondered too.

I'm a fan of DDD but it's like saying "I'm a fan of hammers"... hammers have their place and there is no other tool like them... for the thing they are good at.

I used to be all "omg <FoM> is the only way to do things" but nowadays, I tend not to get too hung up on prescribed, named processes and aim for decoupling where it makes sense (for the most part!)


Speaking of hammers and software design reminds me of a classic:

https://web.archive.org/web/20180924092925/http://discuss.jo...

"Nobody really buys hammers anymore. They're kind of old fashioned."


So this is part of the problem with DDD, there are so many ideas about what DDD is that it's hard to boil it down. Trying to measure without metrics and then blaming DDD sounds very silly. If performance is the issue, then measure and improve it -- adding more layers of abstraction (it really depends on what "implementing DDD" means) is generally not the way to do that.

ORMs are DDD (ORMs are also bad), but you can also consider SQL as DDD (declarative queries are a way of dealing with only domain logic), and so is the generic serialization problem (so you can use gRPC or JSON, let's say). Note that DDD is NOT creating DSLs -- I'm pretty against DSLs for most things because usually the problem space expands for super general DSLs.

People tend to go overboard with DDD though. So much so that there are some people who have snapped back the other way and think that basically nothing should be generic.


> ORMs are DDD (ORMs are also bad), but you can also consider SQL as DDD

I'm not convinced we're all talking about the same DDD, because ORMs or SQL are not DDD in any context other than being implementation details on a project (unless your domain is specifically providing an ORM or SQL product).


I don't think saying ORMs are an implementation detail is useful. DDD is a design and implementation strategy. It is telling you how to structure and build your application. For example, DDD says you should have entities and repositories. Well, that's an ORM: you have models (entities) and some kind of querying facility (maybe a fluent API, maybe a parameterized API, but whatever, it's a repo).

Realistically, when using an OO language w/ an SQL database, there's realistically no other way to do this. You'll either use an existing ORM, or you'll end up building a weird, custom ORM.

In fact, some of my frustration with DDD is that because it uses so much new language to refer to existing concepts, techniques, and technologies, it obfuscates the fact that you're probably already using DDD in some significant measure. Like are you using Spring, Rails, or Django? You're probably more than 3/4 the way to DDD.


Well it depends on how you look at it. So ORMs definitely are, just look at the definition and that's exactly what ORMs (with class mappers) usually let you do. Thinking in terms of User instead of DatabaseRow<T> is certainly DDD.

What I was wrong about was that SQL isn't really DDD. I didn't realize the colloquial definition[0] is so strict as to call out classes.

[0]: https://en.wikipedia.org/wiki/Domain-driven_design


ORMs have nothing to do with DDD, they merely provide objects which map to your database tables. The crux of DDD is to model your application in a domain centric way, what that means is using domain concepts in your business objects instead of just getters and setters.

One of the most important things is separating your domain from infrastructure and the ui around it. This can be achieved in a variety of ways, with and without an orm. DDD encourages application designers to start with the domain first, and build the infrastructure and ui around it later on. It changes the language we speak of during designing application, instead of asking whether to use SQL or NoSQL say, you design your domain first + the operations around it and afterwards think about what's the ideal infrastructure to use.



I don't think you have a grasp on what DDD is or confusing it with more concrete architectural or design patterns. An ORM has nothing to do with Domain-Driven Design, nor bounded contexts or aggregate roots or events or the many other parts that are DDD.


Again, I really think we're having some fundamental disagreements about what "DDD" is. Maybe that term is just doomed and what I'll say from now on is "hexagonal infrastructure".

For now, this is where I'm drawing my definition (that I use in this thread) of what DDD is, and what makes me think ORMs are indeed DDD:

https://en.wikipedia.org/wiki/Domain-driven_design#Building_...


> For now, this is where I'm drawing my definition (that I use in this thread) of what DDD is, and what makes me think ORMs are indeed DDD

That doesn’t mention ORMs at all, and I’m not sure what in that source gave you the idea. ORM had no fundamental connection to DDD; an ORM could be used in the implementation of Repositories, but there is no strong link between the ideas.


I said this up a little further but, I think there's realistically no way to avoid an ORM when implementing DDD in an OO language, using an SQL database.

Like, like it or not, DDD's wheelhouse was going into Java shops and telling them how to structure their apps. Those shops use objects and they almost certainly (though not absolutely certainly) use a relational database. You're not gonna get away from mapping your relational data to your objects, the only choice you have is to use an off the shelf ORM, or to build your own.

So I think that's mostly inarguable. I do think building an app on a document database is interesting though, because then there's no need for the "mapping", so to speak (depending on how static/dynamic your OO language is), but I don't necessarily see how that fact means that ORMs aren't DDD.

But broadly, the case I'm referring to is:

- We wanted to implement DDD

- We were using Django, i.e. QuerySets and Models

- We wrapped QuerySets to make "repositories"

- We wrapped Models to make "entities"

- This was silly (read: more confusing, slower, and a waste of engineering resources), because QuerySets and Models are already DDD, because ORMs are DDD


If your only reference is Wikipedia, it's going to be hard to have a productive conversation.

The fact that these two things share words, doesn't make them related other than the fact the same English words are used. If I were to see an ORM-tied object in the business logic of a DDD-based application, something has gone horribly, horribly wrong.


It's not the only reference, but I bring it out because you and I are talking past each other, and shared definitions or discovering differences between our definitions and what others think is the only way past an impasse like that.

Thanks for the attempt at discussion, but looks like there's no more productive discussion to be had.


We're not talking past each other, rather you seem to lack a fundamental understanding of DDD but feel confident to speak matter-of-factly about it. There's as much a relationship between DDD and ORMs as there is between Agile and ORMs or queues and ORMs.


Well so far all you've done is suggest that Wikipedia is wrong (or using it as as a source is wrong?) and you're right, so I hope you'll excuse me for not putting any stock in your opinion.

If you do happen to find time to put your thoughts down to paper with some sources anywhere in the public sphere I'd love to read about it! :)


Oh that's pretty insightful about SQL, innnnteresting.

Yeah stuff like this gets entwined with engineers' experiences, so you get very subjective takes. Plus, there seems to be some kind of human bug with these things that gets people fanatic about them (see also Agile, DRY, microservices) that then creates a lot of backlash. Like, it'll be hard for anyone to get me to do DDD ever again, just because my intro to it was pretty negative, and no matter how pragmatic or whatever I was in trying to analyze our performance after, our CTO was like "nah we're doing it, please be quiet". I don't know about you but, that doesn't sound like engineering to me.

But that aside, I find that I generally agree w/ DDD. I like coming up with the language of the domain, I like the idea of event storming, I like immutable values, etc. I generally think it maps well to modern web apps, and FWIW people seem to agree, see GraphQL, Redux, and so on.

I always come back to construction--there's building and there's building science. When you're building, do things you know work, don't experiment. But in-between or on side projects, try out new things building science has invented, tested, and the industry has productized. If someone came to me and said, "use this ___", I'd say "show me stats, metrics, case studies, examples, comparisons", and then if all that went well, we might start evaluation--on the way to evolving our process. Doing anything else feels like satisfying a different need, maybe that need is important, but it's not the same need as "build things professionally".


> Oh that's pretty insightful about SQL, innnnteresting.

Probably a little wrong but interesting food for thought nonetheless.

> Yeah stuff like this gets entwined with engineers' experiences, so you get very subjective takes. Plus, there seems to be some kind of human bug with these things that gets people fanatic about them (see also Agile, DRY, microservices) that then creates a lot of backlash. Like, it'll be hard for anyone to get me to do DDD ever again, just because my intro to it was pretty negative, and no matter how pragmatic or whatever I was in trying to analyze our performance after, our CTO was like "nah we're doing it, please be quiet". I don't know about you but, that doesn't sound like engineering to me.

Well that sounds like a bad atmosphere, but also the people side of things is REALLY hard at work (or in life in general). When someone with authority has an idea and they want to do it, it's probably going to happen and standing in the way of it doesn't do any good most of the time. Sometimes it's even worse and then becomes a personal vendetta thing where they just start perceiving you as a threat to their dominance.

> But that aside, I find that I generally agree w/ DDD. I like coming up with the language of the domain, I like the idea of event storming, I like immutable values, etc. I generally think it maps well to modern web apps, and FWIW people seem to agree, see GraphQL, Redux, and so on.

Yup, I agree -- it's overkill for most apps because the DDD's been done for you most of the time at this point (there was a time where it wasn't), but the DDD promised land is really enticing to me.

> I always come back to construction--there's building and there's building science. When you're building, do things you know work, don't experiment. But in-between or on side projects, try out new things building science has invented, tested, and the industry has productized. If someone came to me and said, "use this ___", I'd say "show me stats, metrics, case studies, examples, comparisons", and then if all that went well, we might start evaluation--on the way to evolving our process. Doing anything else feels like satisfying a different need, maybe that need is important, but it's not the same need as "build things professionally".

Fully agreed... In particular

> how me stats, metrics, case studies, examples, comparisons

This is basically how I judge F/OSS projects and/or new tooling I might use. If you find a project and it has a comparison to it's competitors/alternatives in the README (and clear screenshots/usecase/examples), then I'm almost always sold (then I go check the issues and look at open/closed ratio, whether they use an autoclose bot, etc).

Since I can't see a world where the VC dollars stop flowing, I don't know if we'll ever be at a point where building things professionally (like... in the engineering sense) will ever become mainstream. Even if you waste 90% of a developer's salary, if that developer contributes something that increases revenue by 1%, you usually gain WAY MORE than the developer was worth as the business owner (in perpetuity as well, all other things equal). It's just not cost-effective to engineer well upfront sometimes (see RethinkDB and MongoDB), though I still strive for that.


> all the useful artifacts from it happened naturally because product and dev were so closely aligned.

Aren't you just saying that DDD wasn't proposed as a solution to the problem because the problem didn't exist? Starting to jog is a smell that you're out of shape, if you were in shape you wouldn't need to start jogging.

One of the ideas that DDD pushes is that product and dev should be closely aligned. The reason that's emphasized is that it isn't the case in a lot of organizations. Of course you could try to avoid all orgs with these kinds of problems (I haven't been able to, unfortunately), but the question is, if encountering an org that _does_ have problems, what solution would you propose?


I am indeed saying DDD wasn't proposed as a solution to the problem because the problem didn't exist.

I even call that out in the post you're responding to - "Every place I've encountered it, it was attempting to fix with process something that needed to be fixed with culture." And then expound how places with the right culture didn't see value in DDD, and places with poor culture tried to band-aid over it with DDD. As I comment elsewhere, that's not an indictment of DDD as a practice, just that when orgs seek to adopt it it's indicative of a problem, one that DDD won't fix.

>>> Starting to jog is a smell that you're out of shape, if you were in shape you wouldn't need to start jogging.

Let me rephrase that, since it's not 'starting to jog', but 'jogging'. Trying DDD (because of purported benefits) is different than continuing to use DDD (because of actualized benefits).

So "jogging is a smell that you're out of shape; if you're in shape you won't be jogging". And that's patently false.

What I think you're trying to say is that jogging will help keep in shape people in shape, and will help out of shape people get into shape as well, yes? Which would equate to "DDD can help effective software orgs stay effective, and ineffective software orgs become effective". Which is a reasonable claim, but one I've simply not seen; I've not encountered an effective software org that used it, and all the ones I've encountered that used it were ineffective, and, at least during my tenure there, remained so.

>>> One of the ideas that DDD pushes is that product and dev should be closely aligned.

It may try to push that idea, but it does nothing to help it occur. It's implicit, but no more than anything else software related (in that to build something the builders have to know what to build). Certainly, if you asked anyone, using any process, whether or not it's a good idea to have product and dev -unaligned-, they would say "of course not! As a practitioner of X I certainly believe they should be aligned".


So I'm not really sure what DDD looks like in those kind of orgs at this point -- DDD is purely a code organization thing -- it's an implementation detail, so I'm not sure how it would show up in a product/dev discussion scenario...

The fact that a prodcut team doesn't know what it wants to solve has nothing to do with DDD, other than maybe asking the devs to make the solution as generic as possible (?) -- but that's a bad situation whether you're using DDD or not.

I probably just can't understand this because I haven't had this experience


That's fair, but I will point out that that's a bit of a "no true scotsman" argument, if it's meant as an argument.

Regardless, I'm not really saying anything about DDD; just my experience of organizations saying they're using it. They may indeed be misusing it.

They probably are, in fact; my whole point is that the orgs I've encountered that have figured out how to write software successfully don't say they use DDD (even if implicitly they do based on someone's definition of what DDD is), and the orgs I've encountered that say they use DDD to write software then execute very, very poorly, if at all, to the point I don't want to work for orgs that say they use DDD.

I believe the cause of that experience to be that the orgs that have cultures that prioritize the right things to where product and dev align very rarely look to adopt new processes that purport to solve problems they already have solved, and the orgs that have cultures that create obstacles look to processes as silver bullets to address them.

It's very likely akin to your comment about design patterns; a well built implementation may exhibit some design patterns, even without the author knowing that's what they are. A badly built implementation may look like someone had their gof4 book at hand and was looking for any possible excuse to use a pattern (and they're all named according, FooFactory, BarSingleton, so you can go "yep, they're using design patterns alright"). The latter isn't a judgement on design patterns, but the former may not find very much use for designating what is working for them as design patterns.

I.e., it's not necessarily about what is actually in use, but what is being said is in use. My experience is that orgs that say they use DDD = avoid.


Also, for how DDD would incorporate both dev and product, product (ostensibly) knows the domain, and needs to communicate it to dev to be able to design things.

A logical follow-up, then, is Event Storming ( https://en.wikipedia.org/wiki/Event_storming ) which came out of DDD, and explicitly lists out dev and domain experts as being involved. Which very much points at my earlier comment of trying to solve by process what should be natural from culture (i.e., are dev and product talking to each other).


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.


the support tooling for java is fantastic. Profilers, APM tools that can trace webapps, visual debuggers and so on. Most of the tooling just works.

We have part of our application written in another language/framework and while developing in it is fun and easy, it is a nightmare to debug and maintain in production.

People complain about the verbosity of java, but that makes it great for projects where lots of people are working on it over the years. It is fairly easy to see what is going on. (spring complicates things a bit). Contrast this with say, clojure, where sometimes I have to spend an hour trying to figure out what a blob of code I wrote last year does.


If we're talking about Go, the tooling is good, your IDE will be pretty snappy because of the dedication to single-pass parsing, and you have to really work against the language to write complex and confusing code. The trade-off is that your first write can be more tedious (I really miss filter/map type operations for instance).


The good news is that Go have very little entry point. Thanks to that you can hire people, who are working in other technologies and are interested in learning something new. From my experience they can be productive within a week. It's totally not possible with Java I guess ;-)

It's probably a bigger problem to justify it on the company level. It's always some risk to go with a language that doesn't have such good position on the market ("Who will maintain it?", "Where you will find people who will fix it?", "Wouldn't this language disappear in 2 years?").

Fortunately, a lot changed in recent years. Thanks to Kubernetes, Prometheus, Docker and all other infrastructure Go is already used in the most of the companies. That's giving people much more confidence about Go.


"Go is already used in the most of the companies" - there is nothing close to truth in this statement.


You're missing one key question in your list: "How much (time/money) will it cost and what will be the gain?" Two key aspects to that question: whether/when/how to re-write existing systems in Go and how re-written or new implementations integrate with the existing ones. That's probably the most important question, actually.


> You're missing one key question in your list: "How much (time/money) will it cost and what will be the gain?"

I guess it depends on the situation of the company. But I guess that before doing such movement, company should do a pilot to verify if introducing Go can solve currently existing issues (with development velocity, bugs, performance etc.). After that answer should be simpler :)

> Two key aspects to that question: whether/when/how to re-write existing systems in Go and how re-written or new implementations integrate with the existing ones. It also really depends, but from my experience companies that were switching to Go were keeping legacy part and people who were able to maintain it. In the meantime they re-written what was worth to be re-written. Without touching old part too much.

In that case yo can stay with a situation where you have some developers of old technology and some of the new one. But AFAIK it was not a major issue.


Also as the context, most of these companies where PHP, Python, Ruby or NodeJS. In that case migration to Go had a lot of clearly visible benefits.


More of a question when you start a new project, no?


> 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.


Funny that you mention generics and inheritance in the same sentence - with all the hand-wringing going on about the lack of generics in Go, I would have thought more people would also complain about the lack of inheritance, but that doesn't seem to be a thing - except for the people who don't consider Go object-oriented because it doesn't have inheritance, but that's another topic...

But yeah, the design decisions taken for Go tend to lead to software that is easier to refactor, because you don't end up building an elaborate type system that boxes you in and is difficult to change when requirements change (as they always do).


> with all the hand-wringing going on about the lack of generics in Go, I would have thought more people would also complain about the lack of inheritance, but that doesn't seem to be a thing

Given that use of inheritance is typically discouraged even in languages that support it, that's hardly surprising. OTOH, generics are typically pervasive in languages that support them (and don't cause problems in the same way).


Inheritance is hardly used anymore - honestly I think that we've thrown the baby out with the bath water a little here - at the cost of avoiding abuse by bad / inexperienced developers.

Go is pretty simple, nice enough language. Is it just a "better C" but without the benefits (and compile times) of Rust?


I have heard some co-workers complain but I think it is one of their best decisions.


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 agree. For the most part, I rarely use anything but slices ("dynamic arrays"). Once in a while I'll use a map, but only for large Ns, since maps don't have useful zero-values and they tend to incur more allocations (which are relatively expensive in Go) than slices (and generally lookups are slower for small N values).


I thought dynamic arrays might be a more recognizable term to people who aren't already Gophers.


For sure; I wasn't trying to correct you--I just used both terms for clarity.


Applications don't need generics, but libraries do.

The lack of generics is the reason for a lack of many useful libraries that I miss when writing applications in Go.

Things I missed lately: Sets, maps with arbitrary keys, lists, sorting, removing duplicates, priority queues, parser combinators, list transformation functions (map, filter), transactions.

Some of these exist for Go, but with awkward APIs.


The Go idiom for set is a map with bool values and given that it'll just return false for keys that aren't initialized I really don't see what you're missing compared to having a dedicated type. I'm not sure what your complaint is about maps, lists, or sorting. There are some specialized types you might occasionally want and I miss FP constructs too, but I don't think these are really serious impediments to productivity.


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.


Thanks for mentioning this experience -- I was really excited about Go early on but went with other languages after a while so didn't make any huge projects with it (and definitely not at a large employer/go shop).

Did you have to do a lot of stuff that is DDD-related? Like building out abstractions for adapters and connection points, or did you use libraries that did that part for you mostly? I know of some stuff out there like go kit[0] which is quite pragmatic and does some of the 80% use-cases (serialization, transports, etc) DDD stuff for you.

I think for the most part it's rare to actually need to write a lot of your own DDD pieces for CRUD-y apps, and the parts where the complexity would be worth it are often already done for you by the libraries/frameworks used.

[0]: https://gokit.io/


>Like building out abstractions for adapters and connection points

I'm not sure I'm on the same page here but we often use Repository Pattern were abstractions are needed (obvious example would be some data storage).

We don't have too many libraries in our code. No DDD stuff for sure.

CRUDy apps basically mean you have some domain related structs and a few database methods to connect those structs to data.


Yeah you are -- repository pattern, maybe some built-in serializers/adapters is what I'd expect to be the extent of DDD for most CRUD-y projects. Generally the less libraries/frameworks the better.

> CRUDy apps basically mean you have some domain related structs and a few database methods to connect those structs to data.

Yup fully agreed here -- I was trying to see if there were cases where they poster had to build the abstractions you'd want for a DDD-like system. Normally you don't have to so I was wondering if their experience matched up with my ideas about DDD to start with.


I think you mix some things up. go-kit is very far away from DDD. It even says on their website: "Focus on your business logic.". It's basically everything but not the domain code.

For CRUD-y apps, you usually wouldn't need the DDD patterns at all, as there's no complex business logic to model.

> the parts where the complexity would be worth it are often already done for you by the libraries/frameworks used.

I'm really confused about this. The most complexity comes from complex business scenarios you need to handle somehow in code. No framework is flexible enough to do it for you. Using DDD, you would keep a "pure" layer of domain code that does just that.


> you would keep a "pure" layer of domain code that does just that.

This is pretty much the only way I write 'business' software now. Even for small apps domain complexity gets gummed up with presentation/side effects really fast and I have a hard time disentangling them in my mind. Worth noting: most business apps I've worked on really don't have a lot of external dependencies, but those dependencies tend to sprawl out over the code quickly.

I'm really glad to see this approach getting traction in various forms (hexagonal programming, FP effect systems, Redux-style reducers) and don't have much of a horse in the race other than an abstract notion of a pure component, which may or may not be stateful, that accepts and sends a domain-specific set of messages and events.


> I think you mix some things up. go-kit is very far away from DDD. It even says on their website: "Focus on your business logic.". It's basically everything but not the domain code.

Paradoxically, I think you might actually be mixing some stuff up -- the only reason you can "focus on your business logic" is because the stuff at the edges is taken for you. DDD is the way of thinking that strives to let people focus on business logic by separating and abstracting the non-business layers. If you look for "ddd onion" you'll see the usual images of how this works.

Go kit is valuable because it adheres to this. For example:

> Pluggable serialization and transport — not just JSON over HTTP

This is basically what pragmatic DDD looks like. Worry about what you need to say (the business logic), not how it's said.

> For CRUD-y apps, you usually wouldn't need the DDD patterns at all, as there's no complex business logic to model.

Agreed on this -- this is why I asked whether the app was mostly CRUD-y or not.

> I'm really confused about this. The most complexity comes from complex business scenarios you need to handle somehow in code. No framework is flexible enough to do it for you. Using DDD, you would keep a "pure" layer of domain code that does just that.

Ahh, that quote was referring to a mostly CRUD-y app -- as in I was noting how how DDD would not be useful in a mostly CRUD app (as you've noted, it isn't) mostly because it's already done for you (ex. go kit).


Ah, I get it now. You meant in in the context of onion/clean/hexagonal architecture. :)


yeah! Am I using the a better name for this? I thought of it as "DDD" but maybe I should call it the onion/hexagonal architecture in conversation from now on to make sure the right point gets across


I think that's not uncommon to happen, because there's definitely overlap in the communities/people talking about this stuff. Using/thinking in DDD tends to lead naturally towards onion/hex/ports-and-adapters (whatever you want to call it)


I think it's probably closer to the onion architecture, as DDD doesn't concern much about infrastructure. But it's kind of mixed up. You could also call it just SRP or separation of concerns.

I think the advantage of sticking to a specific "architecture" pattern is you don't waste time discussing where to put what, you just agree to one way and do it.


> go-kit is very far away from DDD.

Bizarre. Go kit is basically the reference model for DDD in Go.


GoKit looks fantastic, thanks for mentioning that. It is very close to my imagined platonic ideal of writing business logic wholly separate from all the side effecting glue code.


No problem! I'm not a go dev so there's probably much better stuff out there, but I take note of good stuff when I see it, no matter the language. In no particular order:

In that area, you might want to also check out:

- https://github.com/goadesign/goa

- https://github.com/emicklei/go-restful

- https://github.com/asim/go-micro

- https://github.com/ribice/gorsk


I like the sentiment. As I get older I find it more interesting to hoard good ideas, which often requires keeping up with multiple ecosystems at once, as each ecosystem has a different set of aesthetics.

For example, a few years ago, I was a big fan of how C# was dealing with persistence via so-called micro-ORMs vs the massive ORMS that most other languages fixated on.


I’m not the authors and don’t even really know what ddd means, sorry


No problem -- "DDD" refers to Domain Driven Design[0]

[0]: https://en.wikipedia.org/wiki/Domain-driven_design


I've had the same experience building both big and small systems. It's a feature of go, not something that's missing imo.


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.


Generics are about algorithms, not modeling. Not having generics becomes especially tedious in Go when you start trying to handle channels properly (e.g. proper prioritization and cleanup).


Generics isn't about the modeling of data, though.


I agree - its more about how you draw relations between types and work with them. If you are finding that a whole class of types can be processed or related in exactly the same way, then these are probably the same type.

The motivation for using generics falls away when every domain type is fundamentally different in its shape and requires ad-hoc relations to be made on a contextual basis.


> If you are finding that a whole class of types can be processed or related in exactly the same way, then these are probably the same type.

This is like saying "if a lot of different fruits can be processed the same way, then these are probably the same fruit"

An example that makes it very clear is stringifying (e.g. for logging). You can stringify a user, you can stringify a database connection and you can stringify a boolean. But these three things are obviously not the same type at all.

Therefore...

> The motivation for using generics falls away when every domain type is fundamentally different in its shape and requires ad-hoc relations to be made on a contextual basis

is wrong. However, bringing up the term "ad-hoc" is great! Because this is precisely what many languages are missing and where generics are absolutely needed. The languages that have the feature that you describe usually call it "type classes". Look up type classes in Haskell or Scala (if you are a JVM guy) or Rust where they call/implement it with traits (if you are a low-level guy).


I think there's a distinction between the kind of "processing" you refer to and the kind generics are a good tool to solve.

I'm interpreting what you describe as something like the methods on an object (in OO land), e.g., the "ToString()" method in Java. Generics aren't good for all such use cases (though they are for more than many might believe). The more specific the operation, the less applicable (obviously?) generics are.

Generics are useful for abstract operations regardless of the type. As an example consider insertions, deletions, and access of members of a container such as a list or map. As such the underlying type/data model isn't really relevant to their utility.


It is related in some way.

Since Go has no generics, it does not have datatypes like Sets, Maps (go has maps, but only supports a few types as keys), or Graphs. It also does not have immutable collections.

This complects data modeling.


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.


Can you share an example of this approach? Because "writing CRUD structs and junk" is what my whole company does all day. Wire representation, controller layer, database layer, storage representation. Typing out the big list of fields over and over with slight variations. Unit testing the mappers between them. Table-driven testing all the error branches in lists of sequential operations. Nobody ever wants to abstract the notion of data because "it's not type safe." I'd love to see a credible take on it, besides "shove everything in map[string]interface{}." It seems everything in Go's design is against this kind of abstraction.


The first step is to represent the data source (eg database) in the code, so you can ask: what is the schema here? This goes beyond just the database schema, but also intent. Is this a soft delete table? How is it audited? Child/Parent relationships.

So when you pass around data, it isn't "just in a map[string]interface{}", but a data buffer that knows about itself. Then you can tell it to do operations on itself, and pipe data from one system (ui) to another system (database or api).

This is what Go is REALLY about. You make machines to handle your work. You don't loose meta-data; but you don't loose sleep about typing (Go is typed but not rich typing like Rust). Go is all about the tooling.

When you make a line of business application, write the tool first.


Am I understanding correctly that a "data buffer" would be like a serialized representation with some kind of schema at runtime? That makes sense. I just don't know how to shake the standard that the fields of business entities become the fields of Go structs.


> 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.

Which frameworks have you used successfully?


> Use a framework that understands your schema / data storage system.

The whole supposed premise of golang is that it shuns frameworks. What frameworks do you recommend in golang for such applications?

You're still going to end up writing mapping logic back and forth between your RPC layer, internal layer, and DTOs. It's extremely verbose and error prone in golang to manually write them.


I'm not suggesting you use a web-framework.

If you write a business application, first have an application framework, specific to your application. And no, you won't have mapping logic between RPC and internal and DTO.

Most people don't understand how to build an application. They think in terms of RPC and encoding, not in terms of Screens, Actions, and Workflows.


> Most people don't understand how to build an application. They think in terms of RPC and encoding, not in terms of Screens, Actions, and Workflows.

What are Screens, Actions, and Workflows?


> first have an application framework, specific to your application.

So we're reinventing the wheel each time no?

> And no, you won't have mapping logic between RPC and internal and DTO.

How would it work then? Use the same DTO across the board?


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.


> 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.

Agreed, and I have to confess that it takes a lot of restraint for me to not suggest that people just change the languages they do generics and type programming in (in favor of haskell/rust). Proper algebraic data types, typeclasses + constraints instead of inheritance, good concise syntax -- there are better type systems out there but most people have only used Java/C# (which is fair since most enterprise software is written in those languages).


I get that. The important bit is that there are a lot of other important concerns besides the type system--tooling, ecosystem, ease of finding/onboarding collaborators, etc. Personally I think Rust is the only of these languages which ticks most of these boxes, and even then Rust makes you think about ownership all the time. I think there's space for a GC'ed Rust ("Rust-lite") or an OCaml with a familiar syntax and really great support (last I checked, ReasonML is still a confusing mess). Maybe Go will get generics and sum types and it will be close enough?


> I get that. The important bit is that there are a lot of other important concerns besides the type system--tooling, ecosystem, ease of finding/onboarding collaborators, etc. Personally I think Rust is the only of these languages which ticks most of these boxes, and even then Rust makes you think about ownership all the time. I think there's space for a GC'ed Rust ("Rust-lite") or an OCaml with a familiar syntax and really great support (last I checked, ReasonML is still a confusing mess).

Yeah, I personally use Typescript when I want something that I can work with others but not slow down newcomers/other team members too much. Perfect world it's Haskell or Rust but in the real world usually Typescript.

> Maybe Go will get generics and sum types and it will be close enough?

I think this is what will happen -- basically Go is doing the mongo thing where you just launch with something that is mostly good (Go is way more than mostly good for what it sets out to be), and you just iterate till you get something that is way better, and you just have to out-market/last your competitors.

A bit of a detour but most people have never heard of RethinkDB which was like Mongo, but more correct (see Jepsen tests), could shard easily out of the box, came with an admin UI, and did live streaming of updates. By all accounts a better product, but Mongo just marketed their way to dominance and iterated/bought their way out of a bad product (see: WiredTiger).


> Proper algebraic data types, typeclasses + constraints instead of inheritance, good concise syntax -- there are better type systems out there but most people have only used Java/C#

What's ironic is that both Java and C# are getting ADTs (sum types) and pattern matching. Java already gives you exhaustive pattern matching with the upgraded switch statement. I think C# has a proposal for an HKT-like capability, though not sure how far along that is or how much of a priority it is.


It's like merecedes and the car world -- the good features get passed down from the research languages to the pragmatic get-shit-done and enterprise languages, this is how the system is supposed to work, I guess.


True. What's sad is that we had already known that generics, proper error handling, and enums were a good thing when golang was first written, yet they were purposefully ignored.


As if type systeme is necessary to build "enterprise-grade" software, "enterprise-grade" sounds to me like bloated Java with terrible framework like hibernate.


If you haven't tried a type system better than what Java used to be, you owe it to yourself to try one of the other options out there.

Also, type systems are almost proven at this point to produce better and easier to maintain code in the long term. Startups may not need it while they're iterating super quick, but when they do that rewrite 1-3 years in or whatever, they'll be better served picking a language with a good type system.

[EDIT] - Oh also, ORMs are bad, just don't use em unless you really need the velocity. Hooking classes up to objects is just about all I use ORMs for -- write your migrations in SQL and write all queries that aren't braindead-easy (i.e. repository pattern stuff) in SQL/query builder.


> If you haven't tried a type system better than what Java used to be, you owe it to yourself to try one of the other options out there.

But those languages are not used for Enterprise Software development.


Well if we followed this rule I think we'd all still be using punchcards? Or FORTRAN?

It really depends on what your Enterprise looks like but I do agree that many corporations are going to be better served by more mainstream languages.

That said, if you're worried about creating good maintainable software then types are the way to do it -- a lot of the changes that are landing in Java/Python/Ruby/etc came from the niche languages with the good type systems. The renaissance of typing (typescript, mypy, sorbet) is people essentially collectively coming to their senses and realizing that they want to know what their function is expected to be fed, at compile time.


F# would disagree :) It's one of the nicest languages for Enterprise software dev


> 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.


> consultancy that wrote the book

I'm not really sure I know of a lot of people that are writing books for any other reason than furthering their own careers.

> Just don't push it on everyone

You're free to not read the content, or use the techniques and processes.

> This kind of marketing is doing real harm to Go newbies

Not sure I see the connection. But I imagine the people searching for "golang +ddd" will be happy this book exists.


Hey, thanks for the comment. I hope this doesn't seem like we say it's the only valid way to build applications. We mention throughout the book where some patterns make sense, and where they're not needed. We also write about more patterns than just DDD and CQRS, so we don't include these in the book's title.


Book's title is fine with me, post's title isn't. Felt like clickbait once I realized its promoting patterns that aren't useful in most of "business applications built in Go". If you already decided to use CQRS/DDD for whatever reason - yeah, its a decent resource.


> I realized its promoting patterns that aren't useful in most of "business applications built in Go"

I don't really agree with this. I would say most of the patterns are quite useful in majority of business applications. Unless you're dealing with trivial domains, but I believe it's not that common.

Describing the book's content as DDD/CQRS is too specific, as we touch on many different patterns.


>Describing the book's content as DDD/CQRS is too specific (...)

We'll just have to disagree here. You can combine 10 guides into a "book" and if the whole thing ends up being about building an app using a certain approach, then in my eyes that's what the book is about.

Perhaps my standards are too high - still felt like clickbait. Especially considering that you're requiring signing up to a newsletter to get access to it.


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.


Yes it arrived, was too impatient :) Thank you


Big Threedots guy.


Pleasure is all mine!


I’d rather pay for the book than give you my email.


You can still read the articles on our blog, if you're interested. The book is based on them.


You know gmail has a spam button, really easy to use btw.


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.)


A heads up, the book is essential the content of their website:

https://threedots.tech/series/modern-business-software-in-go...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: