Domain Driven Design - bradyclifford/sdlc GitHub Wiki

Overview

  • Core Domain: strategic advantage and differentiate us from competitors; so critical and fundamental to the business that it gives you a competitive advantage.

Identify a Core Domain

  1. What makes the system worth writing?
  2. Why not buy it off the shelf?
  3. Why not outsource it?

Many of these concepts have been derived from Microsoft's Tackle Business Complexity in a Microservice with DDD and CQRS Patterns article.

Within .Net Core Application

There are also layers within a .Net Core Application.

Resources

Definitions

  • Invariant: a function, quantity, or property which remains unchanged when a specified transformation is applied.

Context Mapping

Resources

Domain vs Anemic

Nothing wrong with using an anemic model for simple business cases.

However, with more complicated business rules, Domain and Anemic models cannot co-exist.

  • First blush it looks like the real thing
  • Connected with the rich relationships and structure that true domain models have
  • But, there is hardly any behavior on the objects, making them little more than bags of getters and setters, or even DTOs.

Anemic Domain Model is just a procedural style design. Anemic entity objects are not real objects because they lack behavior (methods). They only hold data properties and thus it is not object-oriented design. By putting all the behavior out into service objects (the business layer) you essentially end up with spaghetti code or transaction scripts, and therefore you lose the advantages that a domain model provides.

If you only require simply a persistence model, then the anemic domain model is a good fit because you have intentionally created an entity with only data for CRUD purposes.

If you need to tackle the complexity of a domain that has a lot of ever-changing business rules, this anemic domain model becomes an anti-pattern.

For details, see Microsofts: Rich Domain Model Vs. Anemic

Domain Layer

Eric Evans's from book Domain Driven Design:

Responsible for representing concepts of the business, information about the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure. This layer is the heart of business software.

Domain Model Layer

Domain model layer includes:

  • Repository contracts (interfaces) that are the infrastructure requirements of your domain model

See Implementing a domain model architecture in .NET Core

Domain Model

Objects containing data which are directly tied to it and encapsulates operations (domain behavior logic) to make sure once created, it will never find itself in an invalid state.

Related:

These objects are typically not used for transferring data between application boundaries (i.e. client and server, bounded context).

  1. No setters, utilize constructor for setting state.
  2. Try to keep values private, don't make them readonly because that prevents them from being rehydrated
  3. Identify consistency boundaries; which domain objects go together.
  4. Name them using a ubiquitous language; their name should reflect that of the business and the bounded context
  5. Identify commands, use them as your public operations/methods.
  6. No persistence logic
  7. If a class or interface is not a part of your ubiquitous language, you shouldn’t introduce it in your domain model; avoid utility interfaces
  8. Keep in mind the tell, don't ask principle providing encapsulation. It turns this:

It turns the example below:

if (personObject.GetSex() == "Male") { ... }
// into this:
if (personObject.IsMale()) { ... }

Persist these classes under a new project and namespace called something like Ffm.Entities, organizing them using folders name as boundaries. This will help in encouraging good behavior as well with developers; not to mix DTOs with the Domain Model.

Note

IEquatable doesn't provide any additional value here. The main use case for this interface is structs: it allows you to avoid unnecessary boxing/unboxing operations. Reference types can just use basic Equals(object) method.

Domain Objects (Entities)

  1. Focus on behavior, not data!
  2. Has an identity
  3. Use the same unified identifier type across the whole bounded context for simplicity
  4. Compared by Id, not by value in most cases
  5. Inherits a BaseEntity interface/abstract class that defines a public T Id property and assists with equality comparisions
  6. Try to extract common logic to a Value Object instead
Usages

Domain objects are class definitions that include the business logic that enables and constrains state changes. Typically when you're just retrieving data for display, you probably wouldn't use your domain model at all. You'd probably just project from your data storage right into a query result DTO.

Say you have a situation where your BFF is supposed to retrieve some data for display to the user. When it calls-through to your service, it probably passes along an ID or search parameters. Your service then may retrieve a fully-fledged domain object from storage, serialize it and return it to your BFF. Your BFF may then deserialize it using the very same domain class definitions. However, in this case, the BFF shouldn't be calling any mutation methods on that domain object; it should treat it as just plain data, in which case, the shape (e.g. the class definitions) is likely to want to diverge from your domain model over time.

Similarly, if your service is exposing your objects directly from your domain model, you wouldn't want to expose that service to any other bounded context, because then you get stuck with the need to maintain backward compatibility with that shape of your model.

That all relates to queries. Commands is where you more likely use your domain model. And there, you want to retrieve the object, mutate it, and persist it within a just one layer of your architecture (i.e. the service, BFF). When processing a command, it can get messy to try to mutate a domain object within a different layer than the one that is doing data access. That's when you get into the problem of validation and maintaining invariants.

Value Objects

Value Object is a full member of your domain mode. The only difference between Value Object and Entity is that Value Object doesn’t have its own identity. Two Value Objects with the same property set should be considered the same whereas two Entities differ even if their properties match.

  1. Has no identity; i.e. no Id property
  2. Immutable - must not allow them to change during the object’s lifetime
  3. Can't exist without a parent entity owning them; a composition relationship
  4. Compared by value not by identity
  5. Implement equality comparison on the values
  6. Has no impact outside of itself

When should a value object be created?

You can inherit a ValueObject abstract class that assists with equality comparisons, but that boxing and reflection is not needed. Just use ReSharper to code-generate those comparisons.

An example of a ValueObject could be an address. It shouldn't matter how many times you create it. It is the same when compared to another with the same values; always! It doesn't have an identity. To modify it, your throw away old and generate a new one.

However, in an application for an electric power utility company, the customer address could be important for the business domain. Therefore, the address must have an identity so the billing system can be directly linked to the address. In that case, an address should be classified as a domain entity.

Value objects are hard to manage in relational databases and ORMs like EF, whereas in document oriented databases they are easier to implement and use.

You usually define an aggregate based on the transactions that you need. A classic example is an order that also contains a list of order items. An order item will usually be an entity. But it will be a child entity within the order aggregate, which will also contain the order entity as its root entity, typically called an aggregate root.

The purpose of an aggregate root is to ensure the consistency of the aggregate.

  • It should be the only entry point for updates to the aggregate through methods or operations in the aggregate root class.
  • You should make changes to entities within the aggregate only via the aggregate root.
  • Maintaining consistency is the main purpose of the aggregate root. _It is the aggregate’s consistency guardian, considering all the invariants and consistency rules you might need to comply with in your aggregate. If you change a child entity or value object independently, the aggregate root cannot ensure that the aggregate is in a valid state. It would be like a table with a loose leg.

Taxonomy of an aggregate root:

  • Root Entities have global identity. Entities inside the boundary have local identity, unique only within the Aggregate.
  • Nothing outside the Aggregate boundary can hold a reference to anything inside, except to the root Entity. The root Entity can hand references to the internal Entities to other objects, but they can only use them transiently (within a single method or block).
  • Only Aggregate Roots can be obtained directly with database queries. Everything else must be done through traversal.
  • Objects within the Aggregate can hold references to other Aggregate roots.
  • A delete operation must remove everything within the Aggregate boundary all at once
  • When a change to any object within the Aggregate boundary is committed, all invariants of the whole Aggregate must be satisfied.

See Strengthening Your Domain a Primer

  • Implement an empty IAggregateRoot interface to help distinquish between a normal entity and an aggregate
  • The IRepository<T> interface should be persisted in the domain model layer implementing a IUnitOfWork
public interface IRepository<T> where T : IAggregateRoot
{
    IUnitOfWork UnitOfWork { get; }
}

Domain Model Validation

Domain model is best kept lean with the use of exceptions in your entity’s behavior methods, or by implementing the Specification and Notification patterns to enforce validation rules.

http://www.kamilgrzybek.com/design/domain-model-validation/

Data annotations and the IValidatableObject interface can still be used for model validation during model binding, prior to the controller’s actions invocation as usual, but that model is meant to be a ViewModel or DTO and that’s an MVC or API concern not a domain model concern.

Use two-step validation. Use field-level validation on your command Data Transfer Objects (DTOs) and domain-level validation inside your entities. You can do this by returning a result object instead exceptions in order to make it easier to deal with the validation errors.

For validation that needs to take place in both the domain and the UI, you can utilize decorators.

Avoid as much as possible any kind of validation that changes the state of the system and THEN validates. My validation should take place before I attempt to change state, not after.

Emphasis on moving as much validation as possible into the methods of the domain entities because they are responsible for preventing the entity from ever entering an "invalid state", and remembering that basic data-entry validation ends up getting repeated (not re-used) in the UI layer.

Also, remember to keep a separation between the incoming DTOs that carry command data and actual command classes.

See Validate Commands & Validation and DDD for additional details)

The infrastructure layer is how the data that is initially held in domain entities (in memory) is persisted in databases or another persistent store.

  • Third-Party resources, like other Apis, also live within this layer

You must keep the domain model entity classes agnostic from the infrastructure that you use to persist data!

Separated Interface Pattern

Separated Interface pattern enables the application layer (in this case, the Web API project for the microservice) to have a dependency on the requirements defined in the domain model, but not a direct dependency to the infrastructure/persistence layer. In addition, you can use Dependency Injection to isolate the implementation, which is implemented in the infrastructure/ persistence layer using repositories.

  • Define one repository per aggregate
  • Updating, aggregate must use its repository, however it is ok to query the database through other channels
  • Use dapper for simple SQL statements in querying, outside of repositories; much more flexible and aren't restricted by the rules from aggregates
  • Should return domain models, not DTOs
  • Should not have save method, this is the responsibility instead of the Unit Of Work
  • Should not return IQueryable, use query handlers instead

Related: https://programmingwithmosh.com/net/common-mistakes-with-the-repository-pattern/

One aggregate root per repository

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}

public interface IRepository<T> where T : IAggregateRoot
{
    //....
}

Eric Evans's from book Domain Driven Design:

Defines the jobs the software is supposed to do and directs the expressive domain objects to work out problems. The tasks this layer is responsible for are meaningful to the business or necessary for interaction with the application layers of other systems. This layer is kept thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down. It does not have state reflecting the business situation, but it can have state that reflects the progress of a task for the user or the program.

This layer is kept thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down.

It includes queries if using a CQRS approach, commands accepted by the microservice, and even the event-driven communication between microservices (integration events)

Data Transfer Objects

Since the queries are performed to obtain the data needed by the client applications, the returned type can be specifically made for the clients, based on the data returned by the queries. These models, or Data Transfer Objects (DTOs), can be named ViewModels, ApiModels, Response, or Request, Command classes.

These objects are intended to carry data between layers. This could be between the client and the server or between the UI and the domain layer, etc.

  1. Exposes fields or properties (getters and setters) publicly
  2. Has no logic
  3. They are only used as transfer objects between layers
  4. Aggregate boundaries and constraints can be ignored in DTO creation

There will be cases where a domain model (an entity and value object) can be both a model itself and a DTO. But be careful not to allow them to seep to deeply into your wire protocal types.

Related

Transform between Domain / Value Object and DTO

Since DTOs can not contain behavior, one would assume the transform logic would need to live within the domain/value object. Do not do this. We do not want to couple such logic with the object.

The alternative to deserializing your domain entities is actually to create another kind of DTO. A sort of command object that you feed to an entity factory class in your domain. The approach results in a bunch of extra code an potential for bugs just to maintain a "pure" domain model.

  1. DTO assembly should not be the responsibility of the DTO itself, a domain object, nor a repository
  2. Use something like Dapper to rehydrate domain entities from storage and or even AutoMapper from other boundaries usually doesn't force you to make too many concessions in your class definitions.
  3. Going from a DTO to a domain entity should be done by the root aggregate and its methods

If you want to specify response types for Swagger, you need to use explicit DTO classes as the return type. Therefore, predefined DTO classes allow you to offer richer information from Swagger. That improves the API documentation and compatibility when consuming an API.

Related:

Are CQRS Commands part of the Domain Model?

So, are CQRS commands part of the domain model? They are. image

Commands belong to the core domain (just like domain events). They play an important role in the CQRS architecture – they explicitly represent what the clients can do with the application. Just like events represent what the outcome of those actions could be.

Commands and events are two ends of the same stick, they reside at the same abstraction level. Commands are what triggers a reaction in the domain model. And events are the result of that reaction.

By the way, queries are not on these diagrams because they don’t belong to the onion architecture. Domain modeling is for writes, not reads. Reading data is simple, you don’t need DDD to do that.

Commands should not be used as DTOs

public IActionResult EditPersonalInfo([FromBody] StudentPersonalInfoDto dto)
{
    var command = new EditPersonalInfoCommand
    {
        Email = dto.Email, Name = dto.Name, Id = id
    };
    var handler = new EditPersonalInfoCommandHandler(_unitOfWork);
    Result result = handler.Handle(command);

    return result.IsSuccess ? Ok() : Error(result.Error);
}
  • Commands are part of the core domain model. Just like events
  • Commands shouldn’t have any invariants attached to them
  • Commands represent what the clients can do with the application. Just like events represent what the outcome of those actions could be
  • Commands are what triggers a reaction in the domain model. Events are the result of that reaction
  • Commands follow the push model. Events – the pull one
  • Have both commands and DTOs by default
  • View commands as serializable method calls
  • View DTOs as data contracts that help you achieve backward compatibility
  • You can use commands in place of DTOs if you don’t need backward compatibility
  • Commands can return a Result

See Are CQRS Commands part of the Domain Model?

Event is something that happened in the past. A Command is something that is happening.

Using domain events makes the concept explicit, because there is a DomainEvent and at least one DomainEventHandler involved. Domain events help express, explicitly, the domain rules, based in the ubiquitous language provided by the domain experts. Domain events also enable a better separation of concerns among classes within the same domain.

Domain events are similar to messaging-style events, using AMPQ, a message is always sent asynchronously and communicated across processes and machines. This is useful for integrating multiple Bounded Contexts, microservices, or even different applications. However, with domain events, you want to raise an event from the domain operation you are currently running, but you want any side effects to occur within the same domain.

  • Use domain events for inter-application communication only. For inner-application communication, use explicit program flow instead.
  • Dispatch before committing is when you dispatch an event before the database transaction is completed. Avoid this type of domain events as you won’t be able to use it for inter-application communication.
  • Commit before dispatching is when you dispatch an event after the database transaction is completed. This approach is good in most cases. There’s still a small chance of inconsistency with this implementation.
  • Persist domain events along with domain objects if you need 100% consistency and can’t use an ORM. This approach requires more work.
  • Raising a domain event is not the same as dispatching an event
  • Raising a domain event is the responsibility of the domain model and an aggregate root
  • Dispatching a domain event is the responsibility of the application services layer
  • Events are DTOs which should not contain Entities, but can contain Value Objects
  • Event dispatcher is a mediator between event producers and event consumers.
  • Don't merge domain events on the producer side, instead reduce domain events in the event dispatcher before dispatching

image image image image

Related Links:

Other tidbits

  • Handling domain events is an application concern
  • Event happens in the past
  • Try to make events idempotent
  • Event's class name should be past-tense verb
  • Domain events change things within the same domain, while integration events change things outside the domain or microservice
  • Domain events can be synchronous or asynchronous
  • Integration events always have an event and handler (named specific to the behavior's ubiquitous language)
  • Integration events should always be asynchronous
  • Use eventual consistency between aggregates within the same domain
  1. Send a command (for example, CreateOrder).
  2. Receive the command in a command handler.
  3. Handle domain events (within the current process) that will execute an open number of side effects in multiple aggregates or application actions. For example:
  4. Create and send a related integration event to the event bus to propagate states across microservices or trigger external actions like sending an email to the buyer.
  5. Handle other side effects
  • Command handlers are only processed once (only one consumer handler of the command; single reciever), while event handlers can process zero to many
  • Try to make commands idempotent
  • Command Class name is an action
  • Command handler is what rehydrates the domain models through the root aggragate
  • Asynchronous commands are not encouraged (fire and forget)

The important point here is that when a command is being processed, all the domain logic should be inside the domain model (the aggregates), fully encapsulated and ready for unit testing. The command handler just acts as a way to get the domain model from the database, and as the final step, to tell the infrastructure layer (repositories) to persist the changes when the model is changed.

When command handlers get complex, with too much logic, that can be a code smell. Review them, and if you find domain logic, refactor the code to move that domain behavior to the methods of the domain objects (the aggregate root and child entity).

  1. Use the Mediator Pattern. Allows for cross-cutting implementation; i.e. Logging, validation, etc.

As an example, you can use FluentValidation library as a pipe.

or

  1. With an asynchronous message queue, in between controllers and handlers.

Command & Event Structure

A domain command and event have a simple DTO, almost.

  • Immutable. Includes a constructor to set the data

Domain Model Isolation

You make the code that comprises the core part of your application pure while pushing the side effects and the work with external dependencies. Domain model is responsible for making business decisions while the app services layer converts those decisions into visible bits, such as changes in the database.

Domain model isolation brings two benefits: it reduces code complexity and enables better testability.

An isolated domain model is a closed one. All operations on the domain model should be closed under its entities and value objects. In other words, the arguments of those operations, be they explicit or implicit, and their return values should only consist of primitive types or the domain classes themselves.

Isolation is only applicable to entities and value objects. They are the heart of any domain model.

The relationship between Person and Address is fine and doesn’t contradict the concept of domain model isolation. However, you can also see that the Person entity refers to the corresponding repository, and the Address value object uses a location API. Both of these two classes are gateways to the external world, and thus interactions with them break the domain model’s isolation.

The relations you see on this diagram point upwards, from the inner core of the onion to the external layers. At the same time, the onion architecture tells us that all communications should go in the backward direction only, from the upper layers to the inner ones. Violation of this guideline tells us that the domain model is leaking.

See Domain Model Isolation - Vladimir & Identify Domain Model Boundaries

What is domain Logic

While the application services layer can contain quite a lot of code, none of it should be about making any business-critical decisions.

  • Domain logic (aka business logic, business rules, and domain knowledge) is the logic that makes business-critical decisions.
  • All other types of logic orchestrate the decisions made by the domain model and transform them into side-effects: save them to the data store, show to the user, or pass to 3rd-party services.
  • It’s important to separate domain logic from other types of logic as it helps keep the overall code base simpler.

Example of domain logic in action GitHub DDDInAction.

See What is Domain Logic - Vladimir

When to extract logic into domain model?

You can notice a pattern in most codebases that adhere to such a guideline. Their execution flow goes as follows:

  1. Prepare all information needed for a business operation: load participating entities from the database and retrieve any required data from other external sources.
  2. Execute the operation. The operation consists of one or more business decisions made by the domain model. Those decisions result in either changing the model’s state, generating some artifacts (amountWithCommission value in the sample above), or both.
  3. Apply the results of the operation to the outside world.

Only the 1st and the 3rd steps involve the work with external dependencies. The 2nd step is closed under the data retrieved in the first step. The arguments it accepts and the output it generates consist of entities, value objects, and primitive types only.

Note that in simple CRUD applications, there’s no 2nd step because there are no decisions to make. In this case, all operations can be performed solely by application services, no need to delegate them to a domain model. In fact, there can be no rich domain model whatsoever. An anemic domain model would work just fine in such situations.

  • Domain services carry domain knowledge; application services don’t (ideally).
  • Domain services hold domain logic that doesn’t naturally fit entities and value objects.
  • Introduce domain services when you see that some logic cannot be attributed to an entity/value object because that would break their isolation.

See Domain Services vs Application Services - Vladimir

⚠️ **GitHub.com Fallback** ⚠️