祥云平台做网站如何,网站优化建设哈尔滨,金属行业网站模板下载,喷码机营销型网站一.DDD分层架构介绍本篇分析CQRS架构下的Equinox开源项目。该项目在github上star占有2.4k。便决定分析Equinox项目来学习下CQRS架构。再讲CQRS架构时#xff0c;先简述下DDD风格#xff0c;在DDD分层架构中#xff0c;一般包含表现层、应用程序层(应用服务层)、领域层(领域服… 一.DDD分层架构介绍 本篇分析CQRS架构下的Equinox开源项目。该项目在github上star占有2.4k。便决定分析Equinox项目来学习下CQRS架构。再讲CQRS架构时先简述下DDD风格在DDD分层架构中一般包含表现层、应用程序层(应用服务层)、领域层(领域服务层)、基础设施层。在DDD中讲到服务这个术语时比如领域服务,应用层服务等这个服务是指业务逻辑而不是指任何技术如wcf,web服务。 下图是从经典三层构架演变为DDD下的分层架构图 1.表现层 表现层前端往后端post的数据称输入模型(InputModel)后端控制器传给前端要显示的数据称视图模型(ViewModel)大多时候视图模型与输入模型是重合的,所在在下面要介绍的开源项目中作者在应用服务层只定义了ViewModels文件夹。例如在MVC中控制器里只是编排任务调用应用程序层。在控制器中代码块应该尽可能轻薄主要作用是找出层与层之间的分离控制器只是业务逻辑占位符。 在表现层中与运行环境密切相连表现层需要关注的是http上下文、会话状态等。 2. 应用服务层 可以在应用服务层引用领域层和基础设施层是在领域层之上编排业务用例的服务。该层对业务规则一无所知不会包含任何与业务有关的状态信息。该层关键特点 (1) 该层是针对不同的前端。该层与表现层有关,是为表现层服务。不同的表现层(移动webapi, web)都有自己的应用服务层。该层与表现层属于系统的前端。 (2) 应用服务层可能是有状态的至少就UI任务进度而言。 (3) 它从表现层获取输入模型然后把视图模型返回去。 3. 领域层 领域层是最重要和最复杂的一层。在DDD的领域模型架构下。该层包含了所有针对一个或多个用例业务逻辑领域层包含一个领域模型和一组可能的服务。 领域模型大多时候是一个实体关系模型可以由方法组成。是拥有数据和行为。如果缺少重要行为那就是一个数据结构称为贫血模型。领域模型是实现统一语言和表达业务流程所需的操作。 领域层包含的服务是领域服务是涉及多个领域模型而无法放个单个领域模型中的领域逻辑。领域服务是一个类包含了多个领域模型实体的行为。领域服务通常也需要访问基础设施层。 在DDD的CQRS架构下使用二个不同的领域层而不是一个(在Equinox项目中混合成一个)。这种分离把查询操作放在一层(查询领域层)把命令操作放在另一层(命令领域层)。在CQRS里查询栈仅仅基于SQL查询可以完全没有模型、应用程序层和领域层。查询领域层只需要贫血模型类DTO来做传输对象。 4. 基础设施层 这层使用具体技术有关的任何东西O/RM工具的数据访问持久层、IOC容器的实现(Unity)、以及很多其它横切关注点的实现,如安全(Oauth2)、日志记录、跟踪、缓存等。最突出的组件是持久层。二.CQRS概述 1.简介 CQRS是DDD开发风格下对领域模型架构的一种简化改进。任何业务系统基本都是查询与写入对应CQRS是指命令/查询责任分离查询不以任何方式修改系统状态只返回数据。另一方面命令(写入)则修改系统的的状态但不返回数据除了状态代码或确认信息。在CQRS里查询栈仅基于sql查询可以完全没有模型应用程序层和领域层。CQRS方案还可以为命令栈和查询栈准备不同的数据库(读与写)。 2.CQRS的好处 (1)是简化设计降低复杂性对于查询来说可以直接读取基础设施层的仓储。 (2)是增强可伸缩性的潜能。比如读取是主导操作可以引入某种程序的缓存极大减少访问数据库的次数。比如写入在高峰期减慢系统可以考虑从经典的同步写入模型换到异步写入甚至命令队列。分离了查询和命令可以完全隔离处理这两个部分的可伸缩性。 3.CQRS实现全局图 在全局图中右图通过虚线表示双重分层架构分开了命令通道和查询通道每个通道都有独立架构。在命令通道里任何来自表现层的请求都会变成一个命令并加入到处理器队列。每个命令都携带信息。每个命令都是一个逻辑单元可以充分地验证相关对象的状态智能的决定执行哪些更新以及拒绝哪些更新。处理命令可能会产生事件(事件通常是记录命令发生的事情)这些事件会被其它注册组件处理。三. Equinox开源项目总览 1.准备环境 (1) Github开源地址下载。Full ASP.NET Core 2.2 application with DDD, CQRS and Event Sourcing (2) 在sqlserver里执行sql文件GenerateDataBase.sql。 (3) 修改appsettings.json中的ConnectionStrings的数据库连接地址。 2.项目分层说明 表现层Equinox.UI.Web、Equinox.Services.Api 应用服务层: Equinox.Application 领域层: Equinox.Domain、Equinox.Domain.Core 基础设施层: Equinox.Infra.Data(EF持久化) 基础设施层下的横切关注点 Equinox.Infra.CrossCutting.Bus(事件和命令总线) Equinox.Infra.CrossCutting.Identity(用户管理如登录、注册、授权) Equinox.Infra.CrossCutting.IoC(控制反转的服务注入) 3. 项目架构流程梳理图四.表现层分析 在表现层是Equinox.UI.Web和Equinox.Services.Api 服务。在Equinox.UI.Web下主要是用控制器中的CustomerController来演示CQRS框架的实现以及AccountController和ManageController的用户登录、注册、退出和用户信息管理。 对于AccountController和ManageController两个控制器关联着Equinox.Infra.CrossCutting.Identity项目。Identity项目包括了需要用的视图模型、对系统的授权、自定义用户表数据、用户数据同步到数据库的迁移版本管理、邮件和SMS。对于授权方案通过Equinox.Infra.CrossCutting.IoC来注入服务。如下所示 // ASP.NET Authorization Polices services.AddSingletonIAuthorizationHandler, ClaimsRequirementHandler(); Equinox.Services.Api项目实现的功能与Web站点差不多是通过暴露Web API来实现。下面是表现层的二个项目五. 应用服务层分析 Equinox.Application应用服务层包括对AutoMapper的配置管理通过AutoMapper实现视图模型和领域模型的实体互转。定义ICustomerAppService服务接口供表现层调用由CustomerAppService类来实现该接口。项目包含了Customer需要的视图模型。还有事件源EventSource。 由CustomerAppService类来实现表现层的查询、命令、获取事件源。项目结构如下六.领域层Domain.Core分析 领域层是项目分层架构中最重要的一层也是相对复杂的一层。该层作者用了二个项目包括Domain.Core和Domain。Domain.Core项目结构如下所示 对于Domain.Core项目主要是定义命令和事件的基类。源头是定义的抽象类Message。对于命令和事件,任何前端都会发送消息给应用程序层, Message消息就是数据传输对象通常消息定义为一个Message基类开始作为数据容器。 这里使用MediatR中间件作为命令和事件的实现。MediatR支持两种消息类型Request/Response和Notification。先看下Message消息基类定义 //注入服务 services.AddMediatR(typeof(Startup)); /// summary/// Message消息 /// 放入通用属性,甚至是普通标记没有属性/// /summarypublic abstract class Message : IRequestbool {/// summary/// 消息类型实现Message的命令或事件类型/// /summarypublic string MessageType { get; protected set; }/// summary/// 聚合ID/// /summarypublic Guid AggregateId { get; protected set; }protected Message() { MessageType GetType().Name; } } 消息有二种命令和事件。两种消息都包含了数据传输对象。命令和事件有些微妙差别命令和事件都是Message派生类。 /// summary/// Event 领域消息/// 事件类是不可变的它表示已经发生的事情意味着只有私有set,没有写入方法。/// 事件存放通用属性例如事件触发时间触发的用户数据版本号。/// /summarypublic abstract class Event : Message, INotification {public DateTime Timestamp { get; private set; }protected Event() {//事件时间 Timestamp DateTime.Now; } } /// summary/// Command领域命令(增删改)不返回任何结果(void)但会改变数据对象的状态。/// /summarypublic abstract class Command : Message {public DateTime Timestamp { get; private set; }//DTO绑定验证使用Fluent API来实现public ValidationResult ValidationResult { get; set; }protected Command() {//命令时间 Timestamp DateTime.Now; }//实现Command抽象类的DTO数据验证public abstract bool IsValid(); } Domain.Core项目还定义了领域实体和领域值对象的基类实现。例如在领域实体基类中实现了相等性、运算符重载、重写HashCode。对于实体和值对象主要区别是实体有明确的身份标识如主键IDGUID。 public abstract class Entity public abstract class ValueObjectT where T : ValueObjectT Domain.Core项目中的Notifications消息文件夹用来确认消息发送后的处理状态。下面是表现层发送更新命令后IsValidOperation()确认消息处理的状态情况。 [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); } Domain.Core项目中的Bus文件夹用来做命令总线和事件总线的发送接口由Equinox.Infra.CrossCutting.Bus项目来实现总线接口的发送。七.领域层Domain分析 下面是Domain项目结构如下 在上面结构中Commands和Events文件夹分别用来存储命令和事件的数据传输对象是贫血的DTO类也可以理解为领域实体。例如Commands文件夹下命令数据传输对象定义 /// summary/// Customer数据转输对象抽象类放Customer通过属性/// /summarypublic 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; } } /// summary/// Customer注册命令消息参数/// /summarypublic class RegisterNewCustomerCommand : CustomerCommand {public RegisterNewCustomerCommand(string name, string email, DateTime birthDate) { Name name; Email email; BirthDate birthDate; }/// summary/// 命令信息参数验证/// /summary/// returns/returnspublic override bool IsValid() { ValidationResult new RegisterNewCustomerCommandValidation().Validate(this);return ValidationResult.IsValid; } } 当在应用服务层发送命令(Bus.SendCommand)后由领域层的CommandHandlers文件夹下的类来处理命令再调用EF持久层来改变实体状态。下面梳理下命令的执行流程由表现层开始一个customer新增如下所示 当在表现层点击Create后调用应用服务层Register方法触发一个新增事件代码如下 /// summary/// 新增/// /summary/// param namecustomerViewModel视图模型/parampublic void Register(CustomerViewModel customerViewModel) {//将视图模型 映射到 RegisterNewCustomerCommand 新增命令实体var registerCommand _mapper.MapRegisterNewCustomerCommand(customerViewModel); Bus.SendCommand(registerCommand); } 当SendCommand发送命令后由领域层CustomerCommandHandler类中的Handle来处理该命令如下所示 /// summary/// Customer注册命令处理/// /summary/// param namemessage/param/// param namecancellationToken/param/// returns/returnspublic Taskbool Handle(RegisterNewCustomerCommand message, CancellationToken cancellationToken) {//对实体属性进行验证if (!message.IsValid()) { NotifyValidationErrors(message);return Task.FromResult(false); }//将命令消息转成领域实体var customer new Customer(Guid.NewGuid(), message.Name, message.Email, message.BirthDate);//如果注册用户邮件已存在,发起一个事件if (_customerRepository.GetByEmail(customer.Email) ! null) { Bus.RaiseEvent(new DomainNotification(message.MessageType, The customer e-mail has already been taken.));return Task.FromResult(false); }//由Equinox.Infra.Data.Repository来实现数据持久化。事件是过去在系统中发生的事情。该事件通常是命令的结果. _customerRepository.Add(customer);//新增成功后使用事件记录这次命令。if (Commit()) { Bus.RaiseEvent(new CustomerRegisteredEvent(customer.Id, customer.Name, customer.Email, customer.BirthDate)); }return Task.FromResult(true); } 下面是注册customer的信息以及注册产生的事件数据如下所示 在领域层的Interfaces文件夹中最重要的包括IRepositoryTEntity接口是通过Equinox.Infra.Data.Repository来实现接口来进行数据持久化。下面是领域层仓储接口 /// summary/// 领域层仓储接口定义了通用的方法/// /summary/// typeparam nameTEntity/typeparampublic interface IRepositoryTEntity : IDisposable where TEntity : class {void Add(TEntity obj); TEntity GetById(Guid id); IQueryableTEntity GetAll();void Update(TEntity obj);void Remove(Guid id);int SaveChanges(); } /// summary/// Customer仓储接口在基数仓储上扩展/// /summarypublic interface ICustomerRepository : IRepositoryCustomer { Customer GetByEmail(string email); } Interfaces文件夹中还定义了IUser和IUnitOfWork接口类也是需要Equinox.Infra.Data.Repository来实现。八. 基础设施层分析 Equinox.Infra.Data项目是EF用来持久化命令和事件以及查询数据的仓储结构如下 其中UoW文件夹下的UnitOfWork类用来实现领域层的IUnitOfWork使用Commit保存数据。 public bool Commit() {return _context.SaveChanges() 0; } Repository文件夹下的类用来实现领域层的IRepository接口使用EF的DbSet来操作EF TEntity对象再调用Commit提交到数据库。 public virtual void Add(TEntity obj) { DbSet.Add(obj); } Repository文件夹下还包含EventSourcing事件源存储到StoredEvent表中。九.命令总线分析 Equinox.Infra.CrossCutting.Bus项目中使用了中间件MediatR定义了InMemoryBus类来实现领域层的IMediatorHandler命令总线接口发送,使用SendCommand (T)和RaiseEvent (T)方法发送命令和事件。 MediatR是用于消息发送和消息处理的解耦MediatR是一种进程内消息传递机制。 支持以同步或异步的形式进行请求/响应命令查询通知和事件的消息传递并通过C#泛型支持消息的智能调度。 其中IRequest和INotification分别对应单播和多播消息的抽象。 例如在领域层中Message消息实现IRequest代码如下 /// summary/// Message消息 /// 放入通用属性,甚至是普通标记没有属性。IRequestT - 有返回值/// /summarypublic abstract class Message : IRequestbool 最后Equinox.Infra.CrossCutting.Identity主要做用户管理授权迁移管理。Equinox.Infra.CrossCutting.IoC做整个解决方案下项目需要的服务注入。参考文献 Introduction-to-CQRS Microsoft.NET企业级应用架构设计 第二版原文地址https://www.cnblogs.com/MrHSR/p/10820545.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com