Domain Driven Design

How development should be done

Not a long time ago, I was developing applications and features using frameworks, tools and all kinds of libraries provided to solve all kinds of different problems (even those I did not have).

I achieved some remarkably milestones in my career as a web developer: at least two content management systems, an one-on-one appointment scheduling system, several online shops, payment service provider connections, a PDF Generator for conference paper handouts, a language learning platform and so on.

My main focus was to get things to work by quickly building something that does what I want it to do. Not by hacking but just writing down stuff. Once something worked how I thought it was supposed to work, I continued adding features that I tested by clicking through websites or executing scripts over and over again. This testing routine grew larger and larger over time. Sometimes I even forgot to test some tiny parts of the application just because I was busy testing others.

Sooner or later, errors occurred during that process and I sometimes had some trouble figuring out, what the problem was. So I started debugging, printing out error messages, adding die()-statements to the end of a controller action. When I finally found an unset array index or a misspelled database field in a result set I was relieved that this waste of time was over and I could go on.

But why do some bugs hide themselves so well? Maybe because they occur in code I did not write or know how it worked. Like code from a framework. Code that needs to be configured with some weirdly formatted files.

I took all those things for granted for a long period of time. Customers were used to having bugs on their websites and pay extra to get them resolved. Even if some bugs reoccured. In large scale systems, that have been productive for several years, where many different developers of all skill levels added features and tried to refactor stuff, the issues get even worse. No one knows, what developer A was thinking 5 years ago or which feature stakeholder B was requesting, when developer C implemented it. Consider using something like Zend Framework 1, the go-to framework of 2010, 2011, with so much global state - there are no boundaries of violating almost all aspects of clean code: using business logic in templates, accessing the database in controllers, having tightly coupled classes that are used via static method calls, you name it.

It is a mess.

In comes Domain Driven Design. There's a guy out there, Stefan Priebsch, a consultant and software architect. He was approached by my employer to coach developers to not only write better and clean code, but to write code that represents the business requirements and is still understandable and meaningful years from now without making it the next legacy software. The first session required all participants to provide some code examples of something they did in the past, so Stefan can prepare himself and learn to know the skill levels of the devs. I thought of a menu management system, so I coded a generic example with the composite pattern, with multi recursive leaves and components, so I could iterate over items that might be present.

This example lead Stefan to the opinion, that my skills may be ok but I did not really know why I was doing this. What is a menu inside an application? How should it behave? Well, I just wanted to provide some nicely looking code and kinda felt rejected.

So, how do we go from here?

Domain Driven Design is not about code in the first place (it surely is ultimately), it is about how to get to the point of where to write code that meets the business requirements.

The coaching session with Stefan continued by finding something we could implement. Something that was needed, not just some generic requirement with no business value. There were rumours, that the company needed a new checkout process, because the current one was so stuffed with if-constructs to support all kinds of different products, that come from several database sources. Developers were afraid of changing or adding things to this monolith, so no new features have been accepted for quite a long time. The main focus was to keep it alive and fix bugs. We started to concept a new checkout by thinking about minimum business requirements like having a shopping cart, shopping cart items, shipping and payment methods and so on.

So, that's not different from how I worked in the past, right? Well, not quite. I would now start to implement something that will work. A Cart class and maybe internally some items can be stored as an array. And then add something to store a shipping method and an address. Then run a script that outputs something. Or even assemble a simple html form that adds stuff to a cart. How to add a form? Setup a framework skeleton application, so browser testing is possible and routing works, adding logic to controllers and maybe implement a model that represents the database structure.

*INSERT BUZZER SOUND HERE*

Let's start from a different perspective, the perspective of the business, the requirements and the value that is delivered at the end of the day. Going back to the requirements: a shopping cart. Let's just do that. Nothing else, concentrate on that. Meet expectations.

Being in contact with domain experts (people who specify what is needed business-wise and know how things work in the real world) all the time during development, we know what to do and can get feedback all the time. We create the domain model together and derive, what the code needs to look like. What functionality is needed, to fulfill requirements. How communication to other contexts looks like. How parts of the processes look like and how do they behave. So a common language is established, that experts and developers speak (ubiquitous language). If a developer talks about his code, he uses the same words and phrases as a domain expert uses, when he describes a business process.

In comes another big part of writing clean, domain driven code: testing. By writing tests first, we form expectations our code needs to meet. And nothing else, we do what is needed to get the tests to pass. Test driven development massively improves code quality and reduces generic, bug-vulnerability. Plus, we ensure that the code does, what the business requirements demand and nothing more.

Alright, back to the shopping cart. During a session with the domain experts, we found out that there are items, that can be added to and removed from a cart. So we implement a class ShoppingCart that has functions like add() and remove(). When regarding a user's perspective, like when he browses the shop, added an item to his cart and then wants to view his cart all together, we need to retrieve all items from the cart and display a price. So, getItems(), getPrice() can be added to our class. There a plenty of other functions a cart inside a shop checkout process can have, but the developer will not just implement those on good will. Instead, all functions are talked about with the business, the experts. Developers will present a working version or show them the test results. Iterations over those prototypes will form a finer model of the application and precise the requirements. Obscurities will get eliminated by talking about them, giving feedback, receiving feedback.

Combine DDD and its tools (Commands, Entities, Aggregates, Value Objects, even Domain Events and so on) with SOLID principles and clean code patterns, you will have maintainable, scalable and loosely coupled code, that is easy to understand, easy to extend and it represents real world requirements. You won't drown in bugs you cannot fully understand but instead focus on delivering value to your customers by going hand in hand with them during development.

Verdict:

Going full DDD requires developers to know the tools they need for it. So having dealt with frameworks in the past, knowing how HTTP and databases work, avoiding quirks of a programming language and dealing with side effects of infrastructural details is definitely a plus.

It is quite a steep learning curve to move away from generic approaches and find the right way by using value objects instead of passing scalar values and arrays through your code. It is also not always easy to find meaningful method and class names, but the real world kind of already does that for you.

In the end you will write clean, meaningful code that is tested, which can be shown to your domain experts and customers, refactoring is safe because your tests are your safety net, your core domain logic does not depend on infrastructure like frameworks or databases, bugs (if they occur) can be understood and resolved quickly and everyone is happy because the software works.

Stefan opened my eyes. He showed me the way I wanted to go in my future as a developer.

Getting there takes time. Implementation takes time. But bugs and refactoring should not take much time anymore. You'll feel confident in your code, as will your customer.

return home