A couple of years ago, I worked in a SaaS company that suffered from probably all possible issues with software development. Code was so complex that adding simples changes could take months. All tasks and the scope of the project were defined by the project manager alone. Developers didn’t understand what problem they were solving. Without an understanding the customer’s expectations, many implemented functionalities were useless. The development team was also not able to propose better solutions.
Even though we had microservices, introducing one change often required changes in most of the services. The architecture was so tightly coupled that we were not able to deploy these “microservices” independently. The business didn’t understand why adding “one button” may take two months. In the end, stakeholders didn’t trust the development team anymore. We all were very frustrated. But the situation was not hopeless.
I was lucky enough to be a bit familiar with Domain-Driven Design. I was far from being an expert in that field at that time. But my knowledge was solid enough to help the company minimize and even eliminate a big part of mentioned problems.
Some time has passed, and these problems are not gone in other companies. Even if the solution for these problems exists and is not arcane knowledge. People seem to not be aware of that. Maybe it’s because old techniques like GRASP (1997), SOLID (2000) or DDD (Domain-Driven Design) (2003) are often forgotten or considered obsolete? It reminds me of the situation that happened in the historical Dark Ages, when the ancient knowledge was forgotten. Similarly, we can use the old ideas. They’re still valid and can solve present-day issues, but they’re often ignored. It’s like we live in Software Dark Ages.
Another similarity is focusing on the wrong things. In the historical Dark Ages, religion put down science. In Software Dark Ages, infrastructure is putting down important software design techniques. I’m not claiming that religion is not important. Spiritual life is super important, but not if you are suffering from hunger and illness. It’s the same case for the infrastructure. Having awesome Kubernetes cluster and most fancy microservices infrastructure will not help you if your software design sucks.
Software Dark Ages as a system problem
Software Dark Ages is a very strong self-perpetuating system. You can’t fix the systemic problem without understanding the big picture. System thinking is a technique that helps to analyze such complex issues set. I used this technique to visualize the Software Dark Ages.
See the full version.
You can see one big loop of things that accelerate each other. Without intervention, problems become bigger and bigger. How can Domain-Driven Design fix that?
Unlike most famous programming gurus, we don’t want you to just believe in our story. We could just make it up. Fortunately, we can explain why it works by science. More precisely, with the excellent Accelerate: The Science of Lean Software and DevOps book based on scientific research. The research described in the book mentions the characteristics of the best, and worst-performing teams. One of the most critical factors is loosely coupled architecture.
If you think that microservices may give you loosely coupled architecture – you are very wrong. I’ve seen microservices that are more coupled than a monolith multiple times. This is why we need something more than just infrastructure solutions. This is the time when Domain-Driven Design (DDD) comes into play.
This key architectural property enables teams to easily test and deploy individual components or services even as the organization and the number of systems it operates grow—that is, it allows organizations to increase their productivity as they scale.
(…) employing the latest whizzy microservices architecture deployed on containers is no guarantee of higher performance if you ignore these characteristics.
Architectural approaches that enable this strategy include the use of bounded contexts and APIs as a way to decouple large domains into smaller, more loosely coupled units, and the use of test doubles and virtualization as a way to test services or components in isolation.
DDD doesn’t work
Maybe you know someone who tried DDD, and it didn’t work for them?
Maybe you worked with a person who didn’t understand it well, tried to force these techniques, and made everything too complex?
Maybe you’ve seen on Twitter that some famous software engineer said that DDD doesn’t work?
Maybe for you, it’s a legendary Holy Grail that someone claims work for them – but nobody has seen it yet.
Let’s not forget that we are living in the Software Dark Ages. There’s one problem with ideas from the previous epoch – there is a chance that some people may miss the initial point of DDD. That’s not surprising in the context of 2003 when DDD was proposed for the first time.
It’s not easy to enter the DDD world. A lot of books and articles are missing the most important DDD points by oversimplifying them. They are also often explained in abstract examples that are detached from reality. It’s also not rare to see too long and too complex examples that are impossible to understand.
Let me try to explain DDD in the simplest way.
From the Dark Ages to the Renaissance
DDD techniques can be split into two parts. Tactical and strategic patterns. Tactical Domain-Driven Design patterns are about how to implement the solution in the code. There is no rocket science in the Tactical DDD – it’s all about Object-Oriented Programming good practices. But before writing the code, you need to know what to implement. That’s where Strategic DDD patterns come into the game.
Many sources describing DDD spend most of the time covering tactical patterns. Sometimes, they even skip strategic patterns. You can practice DDD by using just the strategic patterns. In some projects using tactical DDD patterns is even overkill. Unfortunetly, most people are doing the totally opposite thing. They use just tactical patterns without the strategic part. That’s super horrifying.
If anybody asked me if any silver bullet exists in product software engineering, I’d have only one candidate: Domain-Driven Design Strategic Patterns. Strategic DDD helps us to get the answer about:
- what problem you are solving?
- will your solution meet stakeholders and users expectations?
- how complex is the project?
- what features are not necessary?
- how to separate services to support fast development in the long term?
These questions are essential while implementing a new project, adding new functionality, or doing refactoring. Strategic DDD patterns give us a way to answer these questions consistently and predictably.
Some engineers tell me they are “just engineers”. They don’t care too much about who uses their software or why. They are just implementing, say, JIRA tasks – building services with some bytes on the input and some bytes on the output. Such a mindset leads to a big disconnection between engineers and their clients whose problems we, as engineers, are trying to solve. Without proper communication, it’s much harder to create solutions that help clients in the right way. This is the goal in the end – not simply to process bytes.
It’s tempting to spend a small amount of time on the planning phase of the project. To start coding as soon as possible and finish earlier. When anybody has such doubt, I like to say that “With 5 days of coding, you can save 1 day of planning”. The unasked questions will not magically disappear.
The best way to overcome the Software Dark Ages is to attack it from multiple angles. Let’s see how DDD patterns can attack the system.
Event Storming is a game-changer for Strategic DDD patterns and software development in general. I can’t believe why it’s not adopted by every team in the world yet.
Event Storming is a workshop during which people with questions (often developers) meet with people with answers (often stakeholders). During the session, they can quickly explore complex business domains. In the beginning, you are focusing on building an entirely working flow based on Domain Events (orange sticky notes). Event Storming is a super flexible method. Thanks to that, you can verify if your solution meets expected requirements. You can also explore data flow, potential problems, or UX depending on the session’s goal.
Verifying if the solution has no gaps and is about what users asked takes minutes. Introducing changes and verifying ideas in the developed and deployed code is hugely more expensive. Changing a sticky note on the board is extremely cheap.
A civil engineer or a rocket engineer can quickly see the result of a mistake. They can see that something is clearly wrong before finishing the building process. It is not so simple with software because it’s not easily seen. Most of our critical decisions will not hurt anybody. Problems with features development and maintenance will not appear in a day.
Event Storming works when you are planning both a big project or just a single story. It’s just a matter of how much time you would like to spend. When we are using it for one story, it can be something between 10 minutes to a couple of hours. We tend to spend between one day to a couple of days of sessions for a bigger functionality.
After the session, you should have the correct answer for the questions about:
- what problems are you trying to solve – instead of guessing what may be useful for the end-user or assuming that “we know everything better”,
- if stakeholders are happy with a proposed solution – rather than verifying that half-year later with the implementation,
- complexity of the problem is easily visible – it makes clear why adding one button may require a ton of work,
- initial idea of how you can split your microservices by responsibilities – instead of blindly grouping “similar things”.
In the end, you will end up with much more trust from stakeholders because you are planning a solution together. It’s a much better approach than isolated coding in the basement.
What’s excellent about Event Storming is that the outcome of a properly done session can be mapped directly to the code. It should help you avoid many discussions during development and speed up your work a lot.
You have to start with a clear purpose. Time can fly on a project, and before you know it, you have spent half a year on a project only to find that it’s not useful to anyone. Have you experienced that? It happens more often than you might think, which is why some people lose trust in “engineers” and how we can end up as developers without any autonomy.
It’s common to fear how much time we need to “lose” for running the session. Thinking about time lost for running session is not the right approach. You should instead think about the benefits that you will lose if you will not run the session. I heard the story when running one Event Storming session stopped the implementation of the project for a couple months. It may sound bad, but during the session the team found that current assumptions are totally invalid. Continuation of the project would lead to a complete failure. Even if the session may look time-consuming in the short term, the company avoided a couple of months of useless development.
In 2018 Adam Dymitruk proposed the Event Modeling technique. The notation and idea are heavily based on the Event Storming technique but adds a couple of new features. It also puts an extra emphasis on the UX part of the session.
In general, these techniques are pretty compatible. Even if you stay with Event Storming, you may find some valuable approaches from Event Modeling that you may use.
You can read more about the technique on eventmodeling.org.
Bounded Context and transaction boundaries (aggregates)
Bounded Context is another Strategic DDD pattern that helps us split big models into smaller, logical pieces.
It’s a key for achieving proper services separation. If you need to touch half of the system to implement and test new functionality, your separation is wrong.
Alternative to the wrong separation is lack of separation. Often, a symptom of lack of separation is god objects (huge objects that know too much or do too much). In that case, changes will primarily affect one service. The cost of that approach is a higher risk of big system failure and higher complexity of changes.
In other words – in both cases, it will be harder to develop your project.
A great tool that helps with the discovery of Bounded Context and Aggregates is (of course) Event Storming.
As a result of the session, you can visually see how you should split your services and touchpoints between them.
Ubiquitous Language is a Strategic DDD pattern that covers building up a common language between developers, operations, stakeholders, and users. It is the most underestimated Strategic DDD pattern. Because who cares about the language, right?
It took me time to see how many communication issues between developers and non-developers are because of using a different language. And how painful it is. I’d encourage you to pay attention to that as well. Because of miscommunication, developers aren’t solving the right problem as nobody understands what’s expected of them.
Would you be surprised if I told you that Event Storming will help you develop Ubiquitous Language? Running the session together with stakeholders forces you to talk with them. It’s hard to build a solution together when you can’t understand each other. That’s why it’s critical to not miss your stakeholders at the workshop!
Does DDD solve all problems?
Even if DDD is great, it doesn’t solve all problems that we have. It’s important to manage your expectations. Even if we use these techniques on a high level in my team, we still have doubts if the created design is good enough. Sometimes we don’t know how to approach the problem. Sometimes we go back from the code to the design phase. Sometimes we make bad decisions. All of these are perfectly fine situations. There is no team in the world without these issues. It’s better to assume that it will happen and not be surprised. But we know that without DDD, these issues would be much more significant.
While using DDD, you should pay special attention to avoid:
- Big Design Up Front,
- implementing code “for the future”,
- trying to create anything perfect,
Instead, you should:
- Focus on delivering the MVP to the user in a short time (by short, I mean rather 1 month than 6 months).
- If you need to implement something “for the future” because it will be harder to add it later – that’s a very bad sign. You should think about how to make it easy to add it later.
- Reconcile that even if you do your best, your design will not be perfect from the beginning – it’s much better to improve it over time.
For some teams, it may need a lot of work to go to this level. But I can promise you from my experience that it’s possible. And the fun that you will have from delivering software again is worth it!
It’s hard to go very deep with presented techniques within one article. My goal was rather to inspire you to question the status quo. This is not how our industry should work. If you are not okay with the status quo, I hope that I inspired you to learn new techniques. This is the best way to fight with the Software Dark Ages.
If you want to learn about Tactical DDD patterns, you should check our previous articles.
The examples are written in Go, but they can be easily ported to any language. Go has a very low entry point as well. Who knows, maybe you will find a new favorite language? 😉
What about Strategic DDD patterns? This article is actually an introduction to the next part of our article series. In the following months, we will deeply cover the most important Strategic DDD patterns on a fully working example project.
Who are you in the Software Dark Ages?
Just an ordinary hick who blindly follows the rules imposed by others?
Inquisition, who will try to hate and stifle any unusual approach?
Alchemist, trying to create gold? Even if it’s not scientifically backed?
Or maybe you are secretly reading forbidden books in your cellar? Maybe you are looking with us to finish the Software Dark Ages and to begin the Software Renaissance?Let us know in the comments!