How to Implement Domain-Driven Design: Common Mistakes You Should Avoid.

Practical DDD Example: Avoid Technical Code Organization

In today’s chapter of my Domain-Driven Design journey, I want to dive into how we structure our code. This topic holds a special place for me. You see, when code grows, it can easily become a jumbled mess, making it hard to read, tricky to manage, and tough for anyone to get their head around. And if there’s one thing I’ve learned, it’s that if our code doesn’t mirror the real world, it’s missing the mark. After all, a big part of DDD is about creating models that everyone can understand, from developers to stakeholders.

Over the past 15 years, I have been able to apply DDD to a variety of projects and try different approaches. Some were, well, not the best, while others hit the nail on the head. If you’ve been following my writings, you’ll know I’m big on clean code. For me, messy code filled with confusing abbreviations and insider jargon is a total nightmare. It’s something that drives me crazy.

The Struggle with Technical Structuring: Where We Often Go Wrong

To kick things off, let’s delve into a recurring question: Why do developers frequently lean towards structuring their code around technical aspects rather than focusing on the domain?

I believe this inclination primarily stems from the lens of an engineer. It’s the way they’ve been trained to view systems. Yet, at its core, Domain-Driven Design seeks to rectify this mismatch. DDD encourages everyone involved — not just developers but all stakeholders — to adopt a shared language and vision for the system. This shared understanding should ideally be mirrored in the way we structure our code, aligning it closely with the domain model.

Adding fuel to the fire, numerous tutorials and even some prominent frameworks advocate this technical perspective. A notable example is Microsoft’s ASP.NET MVC framework.

For context, here’s a typical ASP.NET MVC folder structure:

- Controllers
    - HomeController.cs
    - AccountController.cs
- Models
    - HomeModel.cs
    - AccountModel.cs
- Views
    - Home
        - Index.cshtml
    - Account
        - Login.cshtml
- Scripts
- Content

To put it simply: The way we’ve been taught to organize our code often doesn’t make sense when we stop and think about it in practical, everyday terms.

The Drawbacks of Technical Code Structuring

At first glance, slotting aggregates, entities, and value objects into their respective folders might feel clean and organized. It’s like neatly arranging your socks, shirts, and trousers in separate drawers. But let’s peer a little closer and uncover the potential pitfalls.

The Absence of Real-world Analogies

Consider an art museum. If the curators decided to sort paintings not by artist or era, but by the type of paint or canvas used, the experience would be, to put it mildly, jarring. Visitors come to see Van Gogh or Renaissance art, not “oil on canvas” or “watercolors”. The same logic applies to code. Grouping by technical categories often doesn’t align with the intent or the real-world usage of those seeking to understand or modify the system.

Let’s picture our codebase like a library. Over time, as the collection grows, if you had to dig through an “Entities” section hoping to find a single book related to “Banking”, you’d probably wish there was a dedicated “Banking” section instead.

Issues of Cohesion and Coupling

Fragmenting related domain ideas across multiple folders leads to a spiderweb of dependencies and references. This distribution not only makes code navigation a challenge but can also amplify maintenance troubles. Cohesive items that belong together are scattered, and unrelated items might end up too tightly bound. In .NET namespaces, for example, entities that have nothing to do with each other from a technical point of view would then be located in the same namespace.

For a clearer picture, let’s look at the conventional structure:

- Aggregates
    - BankAccount.cs
    - UserProfile.cs
- Entities
    - Transaction.cs
    - UserHistory.cs
- ValueObjects
    - BankAccountNumber.cs
    - UserPreferences.cs

Having understood the limitations of traditional structuring, it’s time to pivot our gaze towards an approach that’s not just technically sound but also intuitively aligned with how we think and operate in the real world.

Prefer a Feature-based Structure

Consider the following folder structure:

- BankAccount
    - Account.cs
    - Transaction.cs
    - BankAccountNumber.cs
- Profile
    - UserProfile.cs
    - UserHistory.cs
    - UserPreferences.cs

Now, this isn’t just a different way to arrange your code — it’s a paradigm shift that brings with it a slew of advantages:

Natural Cohesion

With this structure, everything related to a particular concept, say ‘BankAccount’, sits together. Just like chapters in a book, you get a comprehensive, start-to-finish view of the domain. All intricacies of the BankAccount domain — its entities, value objects, and other nuances — are nestled together, making comprehension a smoother experience.

True Encapsulation

One of DDD’s pillars is the emphasis on aggregates maintaining consistency. By viewing the aggregate as a container of sorts and aligning closely associated entities within the same folder, this vision of encapsulation gets amplified. It’s akin to viewing a family portrait where every member, despite their distinct roles, is seen as part of a single unit.

Swift Navigation

Imagine trying to put together a puzzle with all the pieces that belong together lined up. That’s exactly how this structure feels. Want to learn everything about “Profile”? Just switch to the appropriate folder. No more digging through separate entity or value object folders.

Flexibility with Evolving Features

As your application evolves, its functions change. With a function-based structure, extending or optimizing these functions is simplified. You want to introduce a new aspect in “Profile”? Just add it to the “Profile” folder. This approach ensures that the encapsulation and cohesion of your features is maintained as they evolve.

Take a closer look and you’ll see that the feature-based approach isn’t just about cleanliness. It’s about synchronizing your code with real-world logic, making maintenance, extension, and collaboration more intuitive and less cumbersome.

Wrapping it up

Finally, let’s take a step back and think about our daily lives. If you separated all the square from all the round, and stored all the things in your home by color but not function, that would be pretty weird, right? Just as we organize our homes to make our daily routines run more smoothly, our code deserves the same thoughtful arrangement.

Choosing a feature-based structure is similar to having a well-organized closet where everything you need for a particular occasion is together. It’s about making our lives (and the lives of those who work on the code after us) a little easier, more intuitive, and, dare I say it, more enjoyable.

Remember, programming isn’t just about making sure machines understand us. It’s also about making sure that those around us can still follow our thought processes months or even years later. So let’s say goodbye to labyrinthine, technical structures and turn to a more natural, domain-specific approach.


Subscribe to Rico Fritzsche

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.