Show HN: We wrote a book about building business applications in Go
threedots.tech
threedots.tech
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.
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.
“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.
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.
It really isn't.
> that is easily misused.
If a tool is misused frequently enough, it really isn't the fault of the users.
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.
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.
Even if this was the case years ago - it is not now with an LSP.
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.
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.
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.
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.
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.
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.
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.
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.
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...
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.
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.
Um. Alright.
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.
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.
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.
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?
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/
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.
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).
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.
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.
Bizarre. Go kit is basically the reference model for DDD in Go.
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.
> 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.
In that area, you might want to also check out:
- https://github.com/goadesign/goa
- https://github.com/emicklei/go-restful
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 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.
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).
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).
Go is pretty simple, nice enough language. Is it just a "better C" but without the benefits (and compile times) of Rust?
"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.
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.
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.
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?
What are Screens, Actions, and Workflows?
Which frameworks have you used successfully?
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.
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.
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?
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.
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.
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.
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).
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.
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.
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_...
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.
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
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.
Thanks for the attempt at discussion, but looks like there's no more productive discussion to be had.
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! :)
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.
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".
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.
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!)
https://web.archive.org/web/20180924092925/http://discuss.jo...
"Nobody really buys hammers anymore. They're kind of old fashioned."
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.
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
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.
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).
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 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".
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.
But those languages are not used for Enterprise Software development.
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.
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.
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.
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.
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.
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)
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.
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.
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'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.
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.
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.
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.
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.
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).
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.
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).
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.
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.
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.
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.
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.
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.
https://github.com/ThreeDotsLabs/wild-workouts-go-ddd-exampl...
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
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.texWhy 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.
> 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'll add information to subscribe confirmation page about the delay.
https://threedots.tech/series/modern-business-software-in-go...