Home Ecommerce DDD - Hands-on Domain-driven Design and Event Sourcing - 2/6
Post
Cancel

Ecommerce DDD - Hands-on Domain-driven Design and Event Sourcing - 2/6


In the previous post, I presented the project and discussed Domain-driven Design. Now, it’s time to start checking some implementation.


It depends on the context


Ubiquitous language

Before jumping into Strategic and Tactical Design, you must know that Domain-driven design doesn’t work if you can’t describe your domain accurately. For that, you need a particular language; the domain language, as known in DDD as ubiquitous language, describing everything inside and out your models, their relations and outcomes.

This vocabulary has to be provided by a domain expert, actively working with developers so they can write the code using the same terminology and understand the workflow of the business in depth. It needs intensive collaboration and knowledge exchange. Over time, developers master the domain intricacies and become very knowledgeable in that niche. Commonly, companies hire developers experienced in a specific domain.

When working with domain experts, I find it very practical to document this language through a glossary of the domain dialects and terms they use and reach out to it as much as possible before coding. Depending on the level of experience working in that field, if seen from an outsider’s perspective, developers may look like domain experts themselves. Ultimately, knowledgeable developers should be able to transfer the business concepts to joiners with ease, and that’s a powerful metric to validate if the overall design is correct.

The setting in which a word or a statement appears that determines its meaning

To effectively set the mind in the domain-driven design using the ubiquitous language, as developers, we also need to accept that the given same name can have multiple meanings, which is challenging because we correlate good code with wide reusability. Knowing that the vocabulary will change the meaning depending on the context, you may duplicate things here and there, which will avoid adding wrong coupling through God models.

Bounded contexts

Alongside ubiquitous language, Bounded contexts are central pieces of the strategic design that will drive everything. In a few words, bounded contexts are explicit boundaries that define the scope of a particular model or language, and they allow different parts of a system to have distinct and separate models.

For this project, I defined the following bounded contexts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── CustomerManagement: 
|     Manages customer-related information.
├── InventoryManagement
|     Tracks availability of products in the warehouse.
├── ProductCatalog
|     Manages product-related information, such as price and description.
├── QuoteManagement
|     Manages the creation, processing, and tracking of price quotes for customers.
├── OrderProcessing
|     Deals with the processing of orders, from placement to fulfillment.
├── PaymentProcessing
|     Manages the processing of payments for customer orders.
└── ShipmentProcessing
      Manages the logistics for delivering the order. 

Core domain and Subdomains

As the name suggests, the core domain represents the heart of the business, and it’s the very reason why it exists. Most of the attention should be on designing this core domain strategically and technically very well since it correlates with a competitive advantage in the market where the product is for.

With the structure above, I could say that the OrderProcessing is the Core domain, once the central focus of this system is processing customer orders, while it depends on supporting Subdomains to fulfill the flow that started when an order is placed, such as Product Catalog, Inventory Management, Payment Processing, until it’s delivered through Shipping Processing, for example.

Defining subdomains within limited boundaries can be handy, especially when dealing with complex projects; where the domain is too abstract, you must divide it into smaller pieces to conquer. Usually, it grows as granular as you learn about the domain itself.

Keep in mind that the domain representation in this project is naive and inaccurate, with no pretension to look like a real e-commerce solution or its departments. It’s important for you to know that conceptually, the relationship between bounded contexts and subdomains is not necessarily one-to-one like it may look like from what I’ve implemented here. A subdomain can perfectly contain multiple bounded contexts, as it can happen that a single bounded context can span multiple subdomains if its nature is too broad.


Strategic and Tactical Design


For implementing DDD as the development approach, you must use both Strategic and Tactical design concepts to streamline the design from different aspects and concerns.

The Strategic design focus on the high-level concepts for the whole solution, such defining the Bounded contexts, Context mapping, and Subdomains. This will define the overall organization of the entire system.

The Tactical design is concerned in defining the details of the Aggregates, Value Objects, Entities, Repositories, Services, etc.

Different techniques can be used to drive this design concept, including Event storming, a workshop-based session aiming to explore the domain intrisecaties to the light right from the domain experts experts in collaboration with developers, stakeholders and other roles, simillarily to brainstorming.

I don’t intend to cover any of these techniques in depth with the series. I also want to mention that Event storming, Domain Events and Event sourcing are different things, although they have to be used together for a more comprehensive approach of the system design.

Do Microservices limit boundaries?

When looking at the project architecture, one thing to be careful of is not to assume that each microservice must be a container for a bounded context. It’s the other way around. See how I grouped the solution folders instead, and imagine that each bounded context is a larger dome scoping everything inside, meaning it can and have multiple microservices under one bounded context representing different aggregates or subdomains.

For this specific project, I only needed a microservice per bounded context, but once the csproj has a similar name of the bounded context, I felt I needed to clarify that relation to avoid confusion. These are the namespaces representing each bounded context, with their respective microservice inside:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── Services
│   ├── CustomerManagement
|   |     └── EcommerceDDD.CustomerManagement
│   ├── InventoryManagement
|   |     └── EcommerceDDD.InventoryManagement
│   ├── ProductCatalog
|   |     └── EcommerceDDD.ProductCatalog
│   ├── QuoteManagement
|   |     └── EcommerceDDD.QuoteManagement
│   ├── OrderProcessing
|   |     └── EcommerceDDD.OrderProcessing
│   ├── PaymentProcessing
|   |     └── EcommerceDDD.PaymentProcessing
│   └── ShipmentProcessing
│         └── EcommerceDDD.ShipmentProcessing

Important: Microservices have nothing to do with DDD. DDD doesn’t specify architectural styles for its technical implementation.

Hands-on Tactical Design


Now you can look at the EcommerceDDD.Core, more specifically the Domain folder, where I placed all the seed work for we need to implement the code for each underlying project. I kept as abstract and clear as I could. You will find abstractions like AggregateRoot, Entity, ValueObject, and so on.

Now let’s check the Product aggregate root, located in the ProductCatalog bounded context, the simplest one in this project.

I chose this specific bounded context because it’s the only one I’m not implementing Event Sourcing, and that’s part of the beauty of microservice architecture; you don’t need to apply or overuse the same architectural patterns religiously when they don’t fit. Overusing Event Sourcing is an anti-pattern you must keep your eyes open to avoid.

As you progress through the series, you’ll realize that Domain-driven Design is not about only modeling but also placing behavior and logic into the domain object, rather than the typical anemic model. The product is just this simple because I only wanted a seed to be referenced to more complex subdomains but not to manage the product catalog, at least for now.

This is a shortlist with key characteristics I repeated for other aggregates over the whole solution:

  • I understand Product is an aggregate root in its bounded context.
  • It has a strongly typed Id. ProductId.
  • It is the entry point for its underlying dependencies, either entities or value objects.
  • It ensures the fields have private setters, meaning they cannot be changed from outside.
  • It ensures its creation is always valid against invariants in a static method named Create.
  • The class constructor is private, meaning that client code has to use the building method before reaching it.

With the unit price being of Money value object type, it encapsulates the used currency for the base price, which is fundamental for the exchange rate conversion later on, and performs some validation checks. We couldn’t have it so handy if we only used primitive types. Value objects also help to avoid the terrible effects of what’s called primitive obsession, which is why I implemented specific objects representing Ids, performing their validations and allowing changing their primitive types more easily if needed.

For overall domain validations, I’m throwing a BusinessRuleException type in case of invalidity, but I encourage you to have very specific exception types named with the ubiquitous language in mind. Every time you find a BusinessRuleException, a domain business rule is violated, while an ApplicationLogicException is thrown in application use-cases.

Avoiding God models

Notice I have copies of Money and Currency value objects intentionally everywhere to comply with having different meanings of a word by context, despite knowing I’m using Money and Currency. This extends to some other objects as well. A given entity or even an aggregate root in one context may be a value object elsewhere. If we were designing a system in the domain of minting coins, we would expect that a coin or banknote has a serial number, meaning its identifiable regardless of its value is the same as others; hence, it could be an entity or even an aggregate root.


Final thoughts


Now that we have a good idea of the foundations of the Domain-driven Design, we can advance with more complex models in the next article. I will finally start talking about Domain events and Event Sourcing. See you there!


Check the project on GitHub



This post is licensed under CC BY 4.0 by the author.