ctapobep / blog

My personal blog on IT topics in the form of GitHub issues.
6 stars 0 forks source link

DDD and its inability to handle complex projects #18

Open ctapobep opened 1 year ago

ctapobep commented 1 year ago

Domain Driven Design is considered by many OOP followers as a way-to-go to implement business logic in Web/Enterprise projects. But in my experience DDD can be used only in systems with trivial business logic. Once we start implementing something a little more complicated, DDD quickly becomes a burden rather than a solution.

Complex business logic and huge Aggregates

After discussing DDD with other engineers, I came to conclusion that there are roughly 2 types of systems: those with low and high connectivity between Entities.

image

An example of a projects with low connectivity is StackOverflow. There are Users, Questions, Tags - these entities are barely connected. This means that when working with Users and their Profiles for instance, the number of invariants that we have to keep track of is low. They don't have a lot of impact e.g. on Tags. So different parts of app can be split into different Aggregates - and those will have limited responsibilities.

Now let's take another type of projects - where the whole product is built around a single huge entity. Here's a real life example: a chemist creates an Experiment to synthesize 1000 molecules. That Experiment is actively filled with more and more data for weeks:

  1. First, the chemist designs it: specifies what reactants, reagents, solvents and their proportions to use; what type of vials/containers will be used to dissolve and transfer substances to; what type of robotics will be used.
  2. Then the app does calculations, and the chemist can order actual physical substances
  3. Then they come back from the store (possibly, in wrong order and different amounts; some vials will be empty due to the mistake or other factors)
  4. Then chemist runs his protocols: dissolves, mixes stuff, runs the robotics, etc.
  5. Then the analysis phase happens and another instrumentation is used
  6. Then final calculations are done and the data is submitted to the downstream systems

What's important here is that there are many invariants that must be enforced between the steps (and within the steps). E.g. if we're at step#4, and we decide to modify things at step#1 - we must ensure that this is possible and we must cascade the change to all the subsequent steps.

image

In this scenario Experiment contains everything else (could be 20 000 of objects underneath). So it's possible that there are hundreds of invariants that we must keep an eye on. In DDD world this means that our Aggregate is going to be gigantic. The whole project is going to reside within this one single Aggregate. Which is simply impractical.

Events, Sagas, Eventual Consistency

One of the solutions suggested by DDD folks is to split the project into smaller Aggregates where none of them is responsible for a complete set of invariants. So when a change happens on Step1, the Aggregate1 enforces its own invariants and then generates an event. That event is then going to be consumed by the Aggregate2, which in turn will generate yet another event.

This architecture usually is distributed in nature, or at least it spans multiple DB transactions. So after the change is made by Aggregate1, it gets committed leaving the data in inconsistent state. Ideally, Aggregates 2-6 quickly finish their part and the state becomes consistent. But because there are moments when the data is in inconsistent state, we now must add even more complexity so that user has a way out if something breaks in the subsequent Aggregates:

  1. Our code needs to be able to function in the inconsistent state e.g. by showing user a button "Update Step2". But this means that we'll have to introduce a lot of additional checks so that errors don't happen when some guarantees aren't met.
  2. It's also not clear what to do if Aggregate2 finds the change unacceptable (its invariants failed). Do we have to generate a yet another type of events back to Aggregate1, meaning we would have to keep the old (consistent) version of data for some time to be able to roll back? Or maybe we want to present the user with this state and ask him to solve that problem?

So we either end up with poor UX or a very complicated technical solution. And seems like the only reason for this is because we really wanted to stick with DDD.

DDD vs OOP

DDD is a subset of techniques that OOP gives us. So why stop there? If DDD doesn't work for us, why not choose simpler path with a full-blown OOP (or whatever paradigm you like)?

The fact that DDD can't handle complex problems seems like a dead end. What's the point of Aggregates if they can only solve simple problems? But of course DDD isn't only about Aggregates - Ubiquitous Language is still an important and a universal principle that we probably should stick to. Or should we? ;) Well, that would be a topic for another time.