Skip to content

Domain Layer

The Domain Layer contains the domain entities or business entities. These entities provide the state and behavior to support the business requirements. They consist of properties that describe how they are, and methods that describe their behavior.

Avoid modeling anemic entities, those having only getters and setters. This is know as an anemic domain model, which is considered an anti-pattern, as stated by Martin Fowler here.

Put the domain logic inside the domain entities, and use domain services only when some interaction with other domain entities is needed to complete the requirement, and that logic cannot belong to a single domain entity, and does not belong into the application layer also.

Entities

The Suite Framework supplies a set of pre-defined entities with its proper behavior and state attributes as a starting point when modeling new entities.

In the future new implementations will be able to support commonly used aspects like audit trails, soft delete, etc. Currently we have:

  1. BaseEntity<TId>: base entity to derive from (our classic one from old ITsynch.Common)

Example

Using ServiceOrder entity for illustration purposes an example of how to arrange components in this layer is provided.

C#
public class ServiceOrder : BaseEntity<long>
{
    // state
    public string Description {get; set;}
    public ServiceOrderState State {get; set;}
    ...

    // behavior
    public void ChangeStateTo(ServiceOrderState newState)
    {
        // do some validations
        if (this.State == ServiceOrderState.Closed) {
            throw new InvalidOperationException("ServiceOrder is closed and its state cannot be changed");
        }

        this.State = newState;
    }
}
  • ServiceOrder entity extends BaseEntity<TId> which provides basic state and behavior for Aggregate Roots.
  • long is the primary key (id) type
  • Business rules are implemented in the domain entity, except in such cases when interaction between several objects is required, if it happens please move the logic to a less entity related service class, but keep it in the domain layer.

Most likely you would put ServiceOrderState enum in Domain.Shared project, because it's likely to be used as reference in other projects.

Repositories

If custom repositories are required, with custom methods like finding by properties, etc. Those should be declared as interfaces in the Domain Layer, and it's implementation on the Infrastructure Layer.

Doing so this way, allows you to split the infrastructure implementation which in turn allows you to unit test more easily.

The Suite Framework provides implementation for repositories to perform regular CRUD operations. For more information please refer to Repository implementations section.

Specifications

The Suite Framework follows the Specification pattern to wrap and define certain business rules that are used for different purposes on the Application and Domain layers.

Rationale

The use of the specification pattern pursuits the following main points:

  • Provides a standard way to define business rules that can be used in different places, in different ways.
  • Encapsulates expressions that can be used for querying.
  • Prevents direct IQueryable manipulation in our repositories.
  • Allows and eases the testing of these rules, in their different uses.
  • Follows SOLID's principles, mainly the separation of responsibilities.

This pattern is focused on separating the logic of which models to select in a query from the model which we are querying over, and provides us with a standard way of declaring such logic, which results useful in cases where:

  • It is necessary to select a group of models that meet certain criterion.
  • It is necessary to ensure that a group of models comply with a certain rule.
  • It is necessary to describe what an model can do without indicating how it does it.

The advantage of using specifications is the decoupling we can achieve in our application by defining in the Domain layer the criteria to be used in searches in our repositories on the Persistance layer, or the rules to comply to our logic on the Application layer.

Additionally, as mentioned before, capturing these criteria/rules in classes allows us to test both their correct functioning as well as their uses, which would turn to be impossible otherwise.

For more information please refer to Specification implementations section.