Understanding Domain-Driven Design (Part 1)

·

10 min read

Understanding Domain-Driven Design (Part 1)

Photo by Kaleidico on Unsplash

Introduction

Hey there! Today, I’m explaining a good way of developing enterprise software. I’ve spent roughly 10 years in the industry and experienced many different architectures.

Let me be honest with you, this series won’t be useful if you’re only looking for short-term–rapid application development. I emphasize “short-term” because this is actually faster in the long run. Why? Well, as your product grows to a couple dozen of use cases (if not a hundred), you’ll find yourself fixing a lot of issues. This can slow down your progress in many ways: the release of new features, and bug fixes, onboarding of a new programmer, refactoring, and eventually, creating a bad experience for both you and your users. We’ll explore these issues in detail.

What is “Domain” in Domain-Driven Design?

In Domain-Driven Design (DDD), “domain” means the main area or topic that a software system is about. For instance, Facebook is about the social media domain, while Duolingo is about language and education. It’s the area we want to turn into software. This domain represents the real-world concepts, rules, and processes related to that space. The goal is to understand and model the complexities of that space in software. We want to ensure the software meets the requirements and behaves correctly, according to the domain.

In short, Domain-Driven Design helps you understand your business and users, and create software around their needs. Not influenced by your favorite framework or the preferences of IT people. End-users will never say “Oh look, they’re using the latest JavaScript framework!” unless your target audience consists solely of web developers.

Who is a Domain Expert?

Domain experts are individuals with extensive knowledge in a particular field. In healthcare, these experts could be doctors or nurses. In finance, they might be economists. These experts give useful advice during the software design process. Close collaboration with others helps software developers understand the specific knowledge required to create software, instead of just imagining how things should work. This teamwork ensures that the software aligns with real-world needs and functions optimally. If you’re involved in creating a product for a certain time and making decisions within that area, you too are a domain expert!

Why is Domain-Driven Design Important?

Domain-Driven Design (DDD) is important because it helps people to understand key questions like, “What are we solving?”, “Why are we solving this?”, “Who is this for?”. It helps you to understand that you’re solving people’s problems, not computer problems. By abstracting the domain and using it as the foundation for a software project, we can avoid being tied to specific technical terms, frameworks, or libraries. We can change any other parts of the project, as our foundation — the abstracted domain will remain strong. This means our software reflects real-world situations. We can have a shared understanding between our business and technical teams. This method makes it easier for software architectures to adapt and be modular, letting software systems change as requirements change.

Practical Examples: Design a Blogging Platform with Domain-Driven Design (DDD)

Throughout some examples, you’ll realize that you have more freedom than you might expect, particularly when compared to a framework-centric approach.

We’ll be considering a few different strategies, including Screaming Architecture (also known as Use-case Driven Approach), Test-Driven Development (TDD), and a bit of Command Query Responsibility Segregation (CQRS). Don’t worry if you don’t know these terms, they sound fancy — they’re all tools designed to help solve software design problems.

As for the examples, I’ll be using PHP, which is my language of choice. However, the beauty of DDD is that it’s not about the language or the framework. It’s about designing a software system. So feel free to use any language you’re comfortable with. But before we start, please forget about how you learned to develop software with frameworks. The first thing you will do would be to install a framework because you will need routing, authentication, or validation, right? Not really, you don’t even have one user, yet. First, you need to abstract your domain away. You don’t need any frameworks or libraries other than a testing framework. Something that helps you to prove your use case works as expected.

File Structure

Software design isn’t just about playing with files and folders, arranging them like they’re puzzle pieces. Many developers get stuck in the habit of thinking that designing software is all about arranging files in a certain pattern. What about frameworks? They’re very flexible but they still force their own semantics. They provide a predefined structure, rules, and many different tools, no questions asked, whether you’re building a big blogging platform or a calculator app.

We’re not jumping into picking a framework right off the bat. Sure, it’s convenient to have folders like App/Models, App/Controllers, or App/Events for your small projects. It’s like a cozy house where everything has its place. But when it comes to expanding an enterprise-level project with hundreds of use cases, this cozy house rapidly turns into a complex maze. Can you imagine a newcomer trying to navigate through this labyrinth? The confusion would lead to endless questions and slow progress.

So, instead of starting with a framework, we’ll take a different route. We’ll start by defining our domain model and Bounded Contexts.

Bounded Context: The Building Blocks

When we talk about Domain-Driven Design (DDD), we use the term “Bounded Context” to describe different modules of our software system. Each Bounded Context is unique, focusing on its own tasks, rules, responsibilities, and concepts.

Let’s consider designing a blogging platform. What are the main components? You might think of elements like the blog posts, the authors, comments, and social interactions such as giving a like or dislike. Each of these elements addresses specific aspects. Let’s inspect each one more closely and give them specific names.

  • Content Management: This context manages all the content-related entities such as Blog Posts and Tags.

  • Comment: This context handles all entities related to comments, such as Comments, Comment Replies, and Comment Feedback (Likes and Dislikes).

  • Author Profile: This context manages the Author profile page (Bio, profile picture, etc.), and Followers.

  • Social Interaction: This context covers interactions such as Bookmarking, Post Feedback (Likes and Dislikes), and Subscriptions (Follow and Unfollow).

  • User Management: This context takes care of user-related entities such as Settings, Privacy, Notifications, and Premium Subscription.

A couple of Insights

Something is interesting, the User represents the same person, but we treat them differently in different Bounded Contexts. Why is that?

  • In the User Management context, a User can modify their personal settings, adjust privacy preferences, and manage notification settings.

  • In the Content Management context, the same User is represented as an Author who creates, edits or deletes blog posts.

  • On the other hand, in the Social Interaction context, a User might be someone who bookmarks posts, likes posts or follows other authors.

These actors may even share the same name, but they are three distinct actors, each with their own attributes, relationships, and rules within their respective contexts.

Visualizing Bounded Contexts: Project Structure

One major change we’re making is reconsidering how we organize files and folders. Traditionally, developers have followed specific patterns for arranging them, which is largely dictated by the chosen framework. This approach is not the way for every software project. As we’ve discussed, Domain-Driven Design (DDD) focuses on the purpose of the software, which means our file structure needs to reflect our domain model, not the other way around. So, how do we translate the idea of Bounded Contexts into a practical file structure?

Let’s begin by creating a separate folder for each Bounded Context in our blogging platform. These folders will serve as the individual homes for each context’s unique rules, tasks, and concepts.

└── src
    ├── AuthorProfile
    ├── Comment
    ├── ContentManagement
    |   ├── Post.php
    |   ├── PostRepository.php
    ├── SocialInteraction
    └── UserManagement

Here, each folder represents a Bounded Context. Even from this high-level view, you can understand the domain of this application without needing to dive deep into the codebase. This is the essence of Screaming Architecture. So, rather than screaming “This is a Laravel app!”, it screams “This is a blogging platform!”.

But hold on, what about routing? Don’t we need a framework or library for that? Let’s take a step back and remember that we are still in the process of defining our domain and use cases. We are not serving any users, yet.

DDD Layers: Domain, Application, Infrastructure

In the DDD world, usually, the applications are layered into the domain, the application, the infrastructure, and perhaps the presentation. We’ve already discussed the domain layer—it’s the heart of our software, encapsulating the business rules and entities.

The application layer is like the conductor of our software orchestra; it uses the building blocks provided by the domain layer to accomplish specific tasks. Consider a task like PlayMusic, the application layer coordinates the domain objects like Music or Artist to execute this task, guiding the process from start to finish.

The infrastructure layer, on the other hand, is like the stage where our software orchestra performs. It provides the instruments required by the domain and application layers to function effectively. It supports the performance without dictating the music.

The presentation layer is where you perform your gig. It's the concert hall.

Imagine a scenario where your orchestra is restricted to playing in only one place—a stage where you can't move or replace a chair or an instrument. That’s what it feels like when you start your project by choosing a framework. You’re tied to that stage, and any attempt to change a component could break the whole setup. Instead, you should be able to replace infrastructural components at any time without causing a catastrophic failure.

Now, let’s address a common misconception: while these layers are key to designing your software, they don’t necessarily need to directly translate into your folder structure.

└── UserManagement
    ├── Application
    ├── Domain
    └── Infrastructure

Many developers, myself included, have understood these layers as something to be explicitly represented in the codebase, resulting in folder structures like the one above. While this structure is often more manageable than a purely framework-based one or the traditional MVC folders, it still misses the point: DDD is not about folder structures, it’s about software design. The layers should guide your software design as a principle in your mind; it doesn’t have to be a literal representation in your folders. Simply put, you should have a clear mental picture of this layered architecture; it doesn’t have to be exactly reflected in a folder structure.

Now, let’s take a moment to consider the folder structures above – whether you’re an experienced developer or not, think about which one truly makes it easier to maintain the code?

Our First Use Case: Creating a Blog Post

The first step in designing our blogging platform using Domain-Driven Design (DDD) is to focus on the use cases. Before we dive into routing, logging, or caching, let’s remember that these are infrastructural concerns and can be easily replaced. We shouldn’t be bound to specific tools; after all, tools are meant to assist us in achieving our goals.

Let’s start by learning the blueprint of our application. The journey begins from a Bounded Context and then drills down into specific use cases. You have the option to create additional folders to separate sub-contexts, like src/ContentManagement/Post/CreatePost, instead of src/ContentManagement/CreatePost. That’s totally fine! The key here is ensuring the folder structure is easy to understand and makes sense to anyone who looks at the codebase. It should focus on the domain logic rather than being overly technical or grouped by file types. We want it to be clear and straightforward so that developers can quickly grasp how everything is organized. With this approach, we can gain a clearer view of our initial design before refining it further.

└──📁src
    └──📁Bounded Context
        ├──📁Use Case 1
        ├──📁Use Case 2
        ├──📄Context Object 1 (e.g. Domain Entity)
        ├──📄Context Object 2 (e.g. Repository Interface)
        └──📄Context Object 3 (e.g. Aggregate Root)

# This translates into something like the following in real-world projects.

└──📁src
    └──📁ContentManagement
        ├──📁CreatePost
        |     ├──📄CreatePost.php
        |     ├──📄PostCreated.php
        ├──📁GetPost
        ├──📄Author.php
        ├──📄Post.php
        ├──📄PublishedPosts.php
        ├──📄PostRepository.php
        └──📄PostRepositoryUsingDoctrine.php

Conclusion

Great job finishing Part 1! We’ve learned important concepts like Domain, and Bounded Context. Let me know what you think in the comments! I hope to publish every week, on Sunday. In the next part, I'm aiming to provide more examples and practical use cases. I want to ensure everything is easy to understand.

If you enjoyed this article, let’s connect on https://twitter.com/akmandev and https://www.linkedin.com/in/ozanakman for more content like this!