March 9, 2021

Domain-Driven Design DDD Concepts

Domain-Driven Design DDD Concepts

I want to share some concepts that I learned in my last course. I'd like to say that when I started with this model I didn't know anything about and I wanted to learn why everybody (Nice companies that I met before) is using that.

Here we go, Domain-Driven Design (DDD) is an approach of software development to try to minimize complexity by domain driver, this approach helps us to build code clear and easy to maintain. Another thing that I like about DDD is that it is highly micro-services oriented.

Core Domain: is the most important concept in DDD, it is directly related to the business. An example is an E-commerce Store, for instance, security is an important part, but it's not the most important part, the core domain is the customer experience. Core Domain is the most important part of the system, where we have to put all of the best people and resources into which determines our success as an organization.

Ubiquitous Language: It is used between business folks, domain experts and software developers, Ubiquitous means "found everywhere". Engineers should use that language in their own code and when communicating with the domain experts. It makes the software much clear and communicable.

Bounded Context: is the central pattern in Domains-Driven Design, Bounded Context is a logical boundary. For instance, we can have two: "Sales Contexts" and "Support Contexts". Another example is: You have in your application two micro-services, the first one is used for authentication and the other one is used for creating transactions in the cart of your website.

A Bounded Context is a boundary that surrounds a particular model. This keeps the knowledge inside the boundary consistent whilst ignoring the noise from the outside world.

Context Map: is the global view of the application as a whole. Each Bounded Context fits within the Context Map to show how they should communicate amongst each other and how data should be shared.

There are some ways to communicate between bounded contexts:

  • Partnership: it's the better way, each team should at least understand some of their partner’s Ubiquitous Language, namely the things that are interesting to their own context.
  • Customer/Supplier: This approach puts 2 bounded contexts into an upstream and downstream, where the upstream is the supplier and has to try and meet the expectations of the customer (downstream).
  • Conformism: This relationship describes the relationship of 2 bounded contexts, where the upstream has no interest in supporting the downstream for whatever reason.
  • Separate ways: There are no integrated with each other.
  • Shared kernel: 2 or more bounded contexts can share a common model. In code terms, you may have a shared library or a service. This is generally a small codebase but difficult to maintain.
  • Anticorruption Layer (ACL): This is another upstream/downstream relationship at a lower level, where the downstream bounded context implements a layer between itself and the upstream. This layer is responsible for translating the objects given by the upstream into its own models. It's used when we need to integrate with a legacy system.
  • Open Host Service (OHS) / Published Language (PL): This is built on top of the Conformist approach from earlier, where the downstream is a lot more tolerable. The upstream will also need to provide version support. Typically the upstream bounded context will support multiple clients and have no interest in especially supporting a particular ones. For example, to conform to Amazon APIs, the downstream will have confidence in the integration by understanding the documentation Amazon provides.

Modules: A Module serves as a container for a specific set of classes of your application. The code within a module should be highly cohesive and there should be low coupling between classes of different modules.

There are 4 important things to remember when thinking about Modules.

  • Derived from the Ubiquitous Language: Any member of the team should be able to tell you what role and responsibilities any particular Module should possess, given its name.
  • Contains one or more cohesive Aggregates: A Module will typically contain one, or sometimes multiple Aggregates that are highly cohesive. If you have multiple Aggregates in a Module, but one is not cohesive with the others, it’s time to break it out into its own Module.
  • Organised by the Domain: Modules should be organized by concepts from the Domain, and not by the type of class. For example, don’t group all of your Aggregates, Services, and Factories into different Modules based upon the type of class. Instead, each Module should have the appropriate classes to model the concept and functionality of that specific aspect of the Domain.
  • Modules should be loosely coupled: Models should be loosely coupled as standalone aspects of your project. You should be able to abstract the code out of your project and test it in isolation without having to cut ties or pull a tangle of interrelated knots with your other modules.

Note: It's pretty important to name correctly our modules.

Entity: Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity. Think about a person, each person is different, but there are common attributes like everybody has a name, last name, age. The Person is always the same entity (not class, that’s different).

Value Objects: Many objects have no conceptual identity. These objects describe the characteristics of a thing. For instance, we have a value seven and another value seven, and we don't care about the entity. They are immutable objects. An example could be Money, it has currency and amount, and we can be sure that it doesn't change in the life cycle.

Aggregates: Composition of other objects and it's completely up to us to determine what are the aggregates in our domain. Aggregates draw a boundary around one or more Entities. An Aggregate enforces invariants for all its Entities for any operation it supports. One thing that aggregate does is it protects business rules or business and variance. Note: use of repository for aggregates.

Services: are first-class citizens of the domain model. When concepts of the model would distort any Entity or Value Object, a Service is appropriate. From Evans’ DDD, a good Service has these characteristics:

  • The operation relates to a domain concept that is not a natural part of an Entity or Value Object
  • The interface is defined in terms of other elements in the domain model
  • The operation is stateless

Services exist in most layers of the DDD layered architecture:

  • Application
  • Domain
  • Infrastructure

An Infrastructure Service would be something like our IEmailSender, that communicates directly with external resources, such as the file system, registry, SMTP, database, etc.

Domain services are the coordinators, allowing higher-level functionality between many different smaller parts. These would include things like OrderProcessor, ProductFinder, FundsTransferService, and so on.

In many cases, Application Services are the interface used by the outside world, where the outside world can’t communicate via our Entity objects, but may have other representations of them. Application Services could map outside messages to internal operations and processes, communicating with services in the Domain and Infrastructure layers to provide cohesive operations for outside clients.

It's pretty hard to understand without coding, however, if you want that I create a new post about it, let me know.