I. Introduction to DDD layered architecture

This article examines Equinox open source projects under the CQRS architecture. The project has a 2.4K star on Github. I decided to analyze the Equinox project to learn about the CQRS architecture. When talking about CQRS architecture, we will briefly describe the DDD style first. In THE DDD layered architecture, it generally includes the presentation layer, application layer (application service layer), domain layer (domain service layer) and infrastructure layer. When the term service is used in DDD, such as domain service, application layer service, etc., the service refers to business logic, not to any technology such as WCF, Web service.

The following diagram shows the evolution from a classic three-tier architecture to a tiered architecture under DDD:

1. The presentation layer

The data posted by the front and back end of the presentation layer is called “InputModel”, and the data sent by the back end controller to the front end to display is called “ViewModel”. Most of the time, the ViewModel and the InputModel overlap, which is in the open source project to be introduced below. The author defines only the ViewModels folder in the application services layer. In MVC, for example, the controller just orchestrates tasks and calls the application layer. Blocks of code in a controller should be as thin as possible. The main purpose is to find the separation between layers, and the controller is just a placeholder for business logic.

It is closely linked to the runtime environment in the presentation layer, which is concerned with HTTP context, session state, and so on.

2. Application service layer

The domain layer and infrastructure layer can be referenced in the application services layer and are services that orchestrate business use cases on top of the domain layer. This layer knows nothing about business rules and does not contain any business-relevant state information. Key features of this layer:

(1) This layer is for different front ends. This layer is related to and serves the presentation layer. Different presentation layers (mobile, WebAPI, Web) each have their own application services layer. This layer and the presentation layer belong to the front end of the system.

(2) The application services layer can be stateful, at least in terms of UI task progress.

(3) It takes the input model from the presentation layer and returns the viewmodel.

3. The domain layer

The domain layer is the most important and complex layer. Under the domain model architecture of DDD. This layer contains all the business logic for one or more use cases, and the domain layer contains a domain model and a set of possible services.

The domain model is mostly an entity-relationship model and can consist of methods. It’s having data and behavior. If there is a lack of important behavior, it is a data structure called an anaemic model. Domain models are the operations required to implement a unified language and express business processes.

The services contained in the domain layer are domain services, which involve multiple domain models and cannot be placed in a single domain model. A domain service is a class that contains the behavior of multiple domain model entities. Domain services typically also need access to the infrastructure layer.

Under DDD’s CQRS architecture, two different domain layers are used instead of one (blended into one in the Equinox project). This separation puts query operations in one layer (query domain layer) and command operations in another (command domain layer). In CQRS, the query stack is based solely on SQL queries and can have no model, application layer, or domain layer at all. Querying the domain layer only requires the anaemic model class DTOS as transport objects.

4. Infrastructure layer

This layer uses anything related to the specific technology: the data access persistence layer for THE O/RM tools, the implementation of the IOC container (Unity), and the implementation of many other crosscutting concerns such as security (Oauth2), logging, tracing, caching, and so on. The most prominent component is the persistence layer.

2. CQRS overview

1. Introduction

CQRS is a simplified improvement of the domain model architecture under the DDD development style. Any business system is basically query and write. Corresponding CQRS refers to the separation of command/query responsibilities. Query does not modify the system state in any way, but only returns data. Commands (writes), on the other hand, modify the state of the system, but return no data except a status code or acknowledgement. In CQRS, the query stack is based solely on SQL queries and can have no model, application layer or domain layer at all. The CQRS scheme can also prepare different databases (read and write) for the command stack and the query stack.

2. The benefits of CQRS

(1) Simplify the design and reduce the complexity. For queries, the storage of the infrastructure layer can be read directly.

(2) is the potential to increase scalability. For example, if read is the dominant operation, some kind of program caching can be introduced to greatly reduce the number of database accesses. For example, when writing to a system that slows down at peak times, consider switching from the classic synchronous write model to asynchronous writes or even command queues. By separating queries and commands, you can completely isolate the scalability of processing these two parts.

3.CQRS realizes global graph

In the global diagram, the right figure represents a double layered architecture with dotted lines separating the command channel from the query channel, with each channel having its own architecture. In the command channel, any request from the presentation layer becomes a command and is queued to the processor. Each command carries information. Each command is a logical unit that fully validates the state of the relevant objects, intelligently deciding which updates to perform and which to reject. A processing command may generate events (events are usually logging what happened to the command) that are handled by other registered components.

Overview of Equinox open Source projects

1. Prepare the environment

(1) Github open source address download. Full ASP.NET Core 2.2 Application with DDD, CQRS and Event Sourcing

Generatedatabase.sql; generateDatabase.sql;

(3) Modify the database connection address for ConnectionStrings in AppSettings. json.

2. Hierarchical description of the project

Application Domain: Equinox.domain, Equinox.domain.Core Infrastructure layer: Cross-cutting concerns under the Equinox.Infra.Data(EF Persistence) infrastructure layer: Equinox. Infra. CrossCutting. Bus (events) and command Bus Equinox. Infra. CrossCutting. Identity (such as login, registration, user management, authorization) Equinox. Infra. CrossCutting. The IoC (service injection of inversion of control)Copy the code

3. Project architecture process diagram



Flow chart correction: Domain layer Equinox. Domain does not need to refer to infrastructure layer event Bus Equinox. Infra. CrossCutting. Bus. In the DDD style, the domain layer is independent and in principle independent of other layers.

Iv. Performance level analysis

At the presentation layer are the Equinox.ui.Web and Equinox.services. Api Services. The CustomerController in the Controller is used to demonstrate the implementation of the CQRS framework under Equinox.ui.web, as well as user login, registration, logout, and user information management for the AccountController and ManageController.

For the AccountController and ManageController two controllers associated with Equinox. Infra. CrossCutting. Identity project. The Identity project covers the required viewmodel, authorization of the system, custom user table data, migration versioning for user data synchronization to the database, mail, and SMS. For authorization scheme through the Equinox. Infra. CrossCutting. The IoC to inject services. As follows:

// ASP.NET Authorization Polices
 services.AddSingleton<IAuthorizationHandler, ClaimsRequirementHandler>();
Copy the code

The Equinox.services. Api project implements much the same functionality as a Web site, by exposing the Web Api. Here are the two projects for the presentation layer:

5. Application service layer analysis

The Equinox.Application Application services layer includes configuration management for AutoMapper, which enables entity transfer between the view model and domain model. Defines the ICustomerAppService service interface to be invoked by the presentation layer and implemented by the CustomerAppService class. The project contains the viewmodel that Customer needs. There’s also the EventSource, the EventSource.

The CustomerAppService class implements the query, command, and event source of the presentation layer. The project structure is as follows:

6. Domain-level Domain.Core analysis

Domain layer is the most important and relatively complex layer in the project hierarchy. For this layer the author uses two projects including: domain.core and Domain project structure as shown below:

For the domain-core project, this is the base class that defines commands and events. The source is the defined abstract class Message. For commands and events, any front end sends messages to the application layer. A Message is a data transfer object, and usually a Message is defined to start with a Message base class that acts as a data container.

MediatR middleware is used here as the implementation of commands and events. MediatR supports two message types: Request/Response and Notification. Let’s take a look at the Message base class definition:

  // Inject the service
    services.AddMediatR(typeof(Startup));
Copy the code
/// <summary>
    ///The Message the Message
    ///Put in generic attributes, even generic tags, no attributes
    /// </summary>
    public abstract class Message : IRequest<bool>
    {
        /// <summary>
        ///Message type: The type of command or event that implements Message
        /// </summary>
        public string MessageType { get; protected set; }

        /// <summary>
        ///The aggregation ID
        /// </summary>
        public Guid AggregateId { get; protected set; }

        protected Message(){ MessageType = GetType().Name; }}Copy the code

There are two kinds of messages: commands and events. Both messages contain data transfer objects. Commands and events are subtly different; they are both Message-derived classes.

/// <summary>
    ///Event Domain messages
    ///The event class is immutable; it represents what has happened, meaning there are only private sets and no write methods.
    ///Events store common attributes, such as when the event was triggered, by whom it was triggered, and the data version number.
    /// </summary>
    public abstract class Event : Message.INotification
    {
        public DateTime Timestamp { get; private set; }

        protected Event()
        {
            // Event timeTimestamp = DateTime.Now; }}Copy the code
/// <summary>
    ///Command Field Command that returns no result (void) but changes the state of the data object.
    /// </summary>
    public abstract class Command : Message
    {
        public DateTime Timestamp { get; private set; }

        //DTO binding verification, using Fluent API to implement
        public ValidationResult ValidationResult { get; set; }

        protected Command()
        {
            // Command time
            Timestamp = DateTime.Now;
        }

        // Implement DTO data validation for the Command abstract class
        public abstract bool IsValid();
    }
Copy the code

The domain-core project also defines base class implementations of Domain entities and Domain value objects. For example, equality was implemented in the domain entity base class, operator overloading, and HashCode was overwritten. The main difference between an entity and a value object is that the entity has a clear identity such as a primary key ID and a GUID.

 public abstract class Entity
      public abstract class ValueObject<T> where T : ValueObject<T>
Copy the code

The Notifications message folder in the domain-.core project is used to confirm the processing status of the message after it is sent. Here is the status of the message processing confirmed by IsValidOperation() after the presentation layer sends the update command.

[HttpPost]
        [Authorize(Policy = "CanWriteCustomerData")]
        [Route("customer-management/edit-customer/{id:guid}")]
        [ValidateAntiForgeryToken]
        public IActionResult Edit(CustomerViewModel customerViewModel)
        {
            if(! ModelState.IsValid)return View(customerViewModel);

            _customerAppService.Update(customerViewModel);

            if (IsValidOperation())
                ViewBag.Sucesso = "Customer Updated!";

            return View(customerViewModel);
        }
Copy the code

Bus in the Domain. The Core project folder to do send command the Bus and the Bus interface, the Equinox. Infra. CrossCutting. Bus project to realize the Bus interface.

Domain level Domain analysis

The following is the Domain project structure:

In the above structure, the Commands and Events folders are used to store data transfer objects for Commands and Events, respectively, which are anaemic DTO classes that can also be understood as domain entities. For example, the Commands data transfer object definition in the Commands folder:

/// <summary>
    ///The Customer data is passed to the object abstract class, and the Customer is put through the property
    /// </summary>
    public abstract class CustomerCommand : Command
    {
        public Guid Id { get; protected set; }

        public string Name { get; protected set; }

        public string Email { get; protected set; }

        public DateTime BirthDate { get; protected set; }}Copy the code
/// <summary>
    ///Customer Registers the command message parameter
    /// </summary>
    public class RegisterNewCustomerCommand : CustomerCommand
    {
        public RegisterNewCustomerCommand(string name, string email, DateTime birthDate)
        {
            Name = name;
            Email = email;
            BirthDate = birthDate;
        }

           /// <summary>
        ///Verify command information parameters
        /// </summary>
        /// <returns></returns>
        public override bool IsValid()
        {
            ValidationResult = new RegisterNewCustomerCommandValidation().Validate(this);
            returnValidationResult.IsValid; }}Copy the code

When a command is sent at the application services layer (bus. SendCommand), the command is handled by a class under the CommandHandlers folder at the domain layer, and the EF persistence layer is called to change the entity state. The following describes the execution process of the following commands. Starting from the presentation layer, a new customer is added as follows:

When you click Create in the presentation layer, call the application Services layer Register method and trigger a new event with the following code:

/// <summary>
        ///new
        /// </summary>
        /// <param name="customerViewModel">The view model</param>
        public void Register(CustomerViewModel customerViewModel)
        {
            / / the view model is mapped to the entity RegisterNewCustomerCommand new orders
            var registerCommand = _mapper.Map<RegisterNewCustomerCommand>(customerViewModel);
            Bus.SendCommand(registerCommand);
        }
Copy the code

After SendCommand sends the command, Handle in the Domain-level CustomerCommandHandler class handles the command as follows:

/// <summary>
        ///Customer Registration command processing
        /// </summary>
        /// <param name="message"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task<bool> Handle(RegisterNewCustomerCommand message, CancellationToken cancellationToken)
        {
            // Validate entity attributes
            if(! message.IsValid()) { NotifyValidationErrors(message);return Task.FromResult(false);
            }

            // Turn command messages into domain entities
            var customer = new Customer(Guid.NewGuid(), message.Name, message.Email, message.BirthDate);

            // Initiate an event if the registered user mail already exists
            if(_customerRepository.GetByEmail(customer.Email) ! =null)
            {
                Bus.RaiseEvent(new DomainNotification(message.MessageType, "The customer e-mail has already been taken."));
                return Task.FromResult(false);
            }

            / / by the Equinox. Infra. Data. The Repository for Data persistence. An event is something that happened in the system in the past. This event is usually the result of a command.
            _customerRepository.Add(customer);

            // After the new command is added successfully, use the event to record the command.
            if (Commit())
            {
                Bus.RaiseEvent(new CustomerRegisteredEvent(customer.Id, customer.Name, customer.Email, customer.BirthDate));
            }

            return Task.FromResult(true);
        }
Copy the code

The following is the information about the registered customer and the event data generated by the registration, as shown below:

In the domain layer Interfaces folder, one of the most important including IRepository interface, is through the Equinox. Infra. Data. The Repository to implement the interface, for Data persistence. The following is the domain level warehouse interface:

/// <summary>
    ///Domain - level warehouse interfaces define common methods
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public interface IRepository<TEntity> : IDisposable where TEntity : class
    {
        void Add(TEntity obj);
        TEntity GetById(Guid id);
        IQueryable<TEntity> GetAll();
        void Update(TEntity obj);
        void Remove(Guid id);
        int SaveChanges();
    }
Copy the code
/// <summary>
    ///Customer warehousing interface, extended over base warehousing
    /// </summary>
    public interface ICustomerRepository : IRepository<Customer>
    {
        Customer GetByEmail(string email);
    }
Copy the code

Interfaces folder also defines IUser and IUnitOfWork interface classes, and it takes Equinox. Infra. Data. The Repository.

Viii. Infrastructure layer analysis

The Equinox.infra. Data project is EF’s repository for persistent commands and events, as well as query Data, structured as follows:

The UnitOfWork class in the UoW folder is used to implement IUnitOfWork in the domain layer. Commit is used to save data.

 public bool Commit()
        {
            return _context.SaveChanges() > 0;
        }
Copy the code

The classes in the Repository folder implement the domain-level IRepository interface, use EF DbSet to operate on EF TEntity objects, and call Commit to the database.

  public virtual void Add(TEntity obj)
        {
            DbSet.Add(obj);
        }
Copy the code

The Repository folder also contains the EventSourcing event sources, which are stored in the StoredEvent table.

Command bus analysis

Equinox. Infra. CrossCutting. Used in the middleware MediatR Bus project, The InMemoryBus class is defined to implement domain-level IMediatorHandler command bus interface sending, using SendCommand (T) and RaiseEvent (T) methods to send commands and events.

MediatR is a decoupling for message sending and message processing. MediatR is an in-process messaging mechanism. Supports synchronous or asynchronous request/response, command, query, notification, and event messaging, and supports intelligent scheduling of messages via C# generics. IRequest and INotification correspond to unicast and multicast message abstractions respectively.

For example, in the domain layer, Message implements IRequest as follows:

/// <summary>
    ///The Message the Message
    ///Put in generic attributes, even generic tags, no attributes. IRequest<T>- Has a return value
    /// </summary>
    public abstract class Message : IRequest<bool>
Copy the code

The Equinox. Infra. CrossCutting. Identity mainly for user management, authorization, migration management. Equinox. Infra. CrossCutting. The IoC service is needed to do the projects in the overall solution injection.