首页 > 其他分享 >ddd领域驱动设计模型 及 Net6使用MediatR完成领域事件发送

ddd领域驱动设计模型 及 Net6使用MediatR完成领域事件发送

时间:2022-10-25 15:44:11浏览次数:106  
标签:set 聚合 MediatR get class 发送 Net6 public ddd

十年河东,十年河西,莫欺少年穷

学无止境,精益求精

1、序言

领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。 

2、ddd 领域驱动模型介绍

参考:https://www.zhihu.com/question/481820861 和  https://zhuanlan.zhihu.com/p/91525839

3、ddd 领域模型VS事务脚本

事务脚本其实就是程序员依照业务逻辑进行自然的代码构造

比如下订单

        public void 订单()
        {
            保存订单();
            发送邮件();
            增减积分();
        }

        public void 保存订单()
        {

        }

        public void 发送邮件()
        {

        }

        public void 增减积分()
        {

        }

这种写法,把大量的业务逻辑写在方法内,一旦更改需求,就必须修改代码,待业务足够复杂时,代码量都聚集在一个方法内,难以维护扩展。违背了设计模式的开闭原则

何为领域模型呢?如果通过领域模型解决上述问题?可参考DDD的四种 Domain 模式,

  1. 失血模型
  2. 贫血模型
  3. 充血模型
  4. 胀血模型

详见:https://zhuanlan.zhihu.com/p/91525839

4、ddd 实体与值对象 

值对象:没有标识符的对象,也有多个属性,依附于某个实体存在。

以订单为例,一般情况下,我们设计订单状态时,一般将订单状态字段设计为 Int 类型,例如:0:待支付 1:已支付 2:已取消

以code first为例,新建一个数据库实体,如下:

    internal class OrderDto
    {
        public long uid { get; set; }
        public string? orderNo { get; set; }
        public int orderStatus { get; set; }
    }

上述实体中的orderStatus 不仅仅可以取值为 0 、1 、2、还可以取值为:100 、 200 、 888 等,这样设计并不符合DDD的设计原则,那么怎么设计实体才符合DDD的设计原则呢?

    internal class OrderDto
    {
        public long uid { get; set; }
        public string? orderNo { get; set; }
        public OrderStatusEnum orderStatus { get; set; }
    }

    public enum OrderStatusEnum
    {
        待支付,已支付,已取消
    }

上述定义的枚举类型即为实体的值对象

再或者,以商家为例

用户要想快速的找到商家,商家就必须拥有经纬度属性,方便用户导航

一般情况下,我们都是这样定义商家

  public class Shop
    {
        public long uid { get; set; }
        public string? shopName { get; set; }
        /// <summary>
        /// 纬度
        /// </summary>
        public double lat { get; set; }
        /// <summary>
        /// 经度
        /// </summary>
        public double lgt { get; set; }

        //.........其他字段
    }

按照ddd的思想,我们可以将经纬度单独抽出来,如下

    public class Shop
    {
        public long uid { get; set; }
        public string? shopName { get; set; }
        
        public latlgt latlgt { get; set; }

        //.........其他字段
    }
    public class latlgt
    {
        public bool CheckLatlgt()
        {
            if (lat < -90 || lat > 90)
            {
                return false;
            }
            if (lgt < -180 || lat > 180)
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// 纬度
        /// </summary>
        public double lat { get; set; }
        /// <summary>
        /// 经度
        /// </summary>
        public double lgt { get; set; }

        //.........其他字段
    }

单独抽出来的好处是重用、并且符合设计模式的单一职责模式,

5、聚合与聚合根

一个上下文内可能包含多个聚合,每个聚合都有一个根实体,叫做聚合根,一个聚合只有一个聚合根。

这里面最重要的原则是:只有聚合根才能被外部访问到,聚合根维护聚合的内部一致性。

以订单和订单详情为例

在code first 中,我们定义订单和订单详情通常这样定义

    internal class OrderDto
    {
        public long uid { get; set; }
        public string? orderNo { get; set; }
        public OrderStatusEnum orderStatus { get; set; }
        public List<OrderDtlDto> OrderDtls { get; set; }
    }

    public enum OrderStatusEnum
    {
        待支付,已支付,已取消
    }
  
    public class OrderDtlDto
    {
        public long uid { get; set; }
        public long orderId { get; set; }
        //..其他字段
    }

上述的订单就是聚合根,订单详情属于聚合根的从属实体。

关于聚合和聚合根,可参考:https://zhuanlan.zhihu.com/p/146488464

6、领域服务、应用服务

以EfCore CodeFirst进行说明

领域服务是指:在同一个DbContext下,相同聚合根或不同聚合根之前的调用称之为领域服务,领域服务工作在同一个进程中,执行结果具有强一致性

应用服务是指:不同微服务之间的相同调用,他们之间的调用是基于网络接口的形式,应用服务不在同一个进程内工作,执行结果不具有强一致性,属于分布式的范畴

上述表述是根据B站杨老师的视频总结出来的,不完全准确,不需勿喷。

7、Net6 实现领域事件

在net6项目中引入Nuget包

MediatR.Extensions.Microsoft.DependencyInjec

注册MediatR

builder.Services.AddMediatR(Assembly.GetAssembly(typeof(NotificationModel)));//当前程序集:Assembly.GetExecutingAssembly()

注意:注册方法 AddMediatR 中的参数是命名空间,共MediatR扫描继承INotification接口的类

发送方实体

在项目中新建发送方相关类,发送方相关类继承自INotification接口

    /// <summary>
    /// 发送方内容  注册MediatR时,扫描该类所属命名空间
    /// </summary>
    public class NotificationModel : INotification
    {
        public string body { get; set; }
    }

发送方发送事件

在webApi中新建Action,进行事件发送

using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using swapModels.MediatrModels;

namespace swap.Controllers
{
    [AllowAnonymous]
    public class MediartController : BaseController
    {
        private readonly IMediator mediator;

        public MediartController(IMediator mediator)
        {
            this.mediator = mediator;
        }

        [HttpGet]
        public async Task<IActionResult> Test(CancellationToken cancellation=default)
        {
           await mediator.Publish<NotificationModel>(new NotificationModel() { body="hello"+DateTime.Now},cancellation);
            return Ok();
        }


    }
}
View Code

mediator 提供了两个方法,一个是Publish,一个是Send,Publish 以广播的形式进行事件发送,可以有多个接收方。send 只能有一个接收方,属于点对点模式。

接收方代码

    /// <summary>
    /// 接收方1
    /// </summary>
    public class NotificationHandler : INotificationHandler<NotificationModel>
    {
        public async Task Handle(NotificationModel notification, CancellationToken cancellationToken)
        {
            await Task.Run(() =>
            {
                Console.WriteLine("接收方1"+notification.body);
            });
        }
    }   /// <summary>
        /// 接收方2
        /// </summary>
    public class NotificationHandler2 : INotificationHandler<NotificationModel>
    {
        public async Task Handle(NotificationModel notification, CancellationToken cancellationToken)
        {
            await Task.Run(() =>
            {
                Console.WriteLine("接收方2" + notification.body);
            });
        }
    }

当运行项目,点击swagger上Test方法时,将会有两个接收方接收到发送事件发送的信息

 

 8、DDD集成事件的发送

集成事件属于跨微服务之间的事件,工作在不同的线程内【微服务工作在不同服务器上】,因此使用上述的MediatR 就不能满足需求了,我们需要借助第三方的MQ中间件。

比如,Redis/KafKa/RabbitMQ等

 

标签:set,聚合,MediatR,get,class,发送,Net6,public,ddd
From: https://www.cnblogs.com/chenwolong/p/ddd.html

相关文章

  • net6 读取api文件参数的内容
    在net6的api接口中,有的参数是文件类型,读取文件的内容。 1///<summary>2///导入3///</summary>4///<returns></return......
  • Net6 EfCore 值对象类型和从属实体类型
    十年河东,十年河西,莫欺少年穷学无止境,精益求精想申请微软MVP,无奈只有博客园有贡献,今天加了一个现任的微软MVP,据他所说,目前微软MVP申请比以前严格,仅仅博客园分享微软知识是......
  • DDD初步了解
    拆分领域模型,使用充血模式(与贫血模式相反,我们MVC模式中的model/DO/DTO,只包含属性和get、set方法,就是贫血模式);各领域独立发展,领域间的交互,使用领域服务来实现;封装变化;实......
  • Net6 定时调度Quartz.AspNetCore(3.5.0)的使用
    十年河东,十年河西,莫欺少年穷学无止境,精益求精1、概述Quartz.Net是根据Java的Quartz用C#改写而来,Quartz.NET是一个开源的作业调度框架,非常适合在平时的工作中,定时轮询数......
  • 【番外篇】Rust环境搭建+基础开发入门+Rust与.NET6、C++的基础运算性能比较
    前言:突然想打算把Rust作为将来自己主要的副编程语言。当然,主语言还是C#,毕竟.NET平台这么强大,写起来就是爽。缘起:之前打算一些新的产品或者新的要开发的东西,由于没有历史包......
  • .NET6 EF CORE实现全局过滤查询
    1、设置一个基类(BaseEntity),包含IsDeleted属性publicclassBaseEntity{[Key,DatabaseGenerated(DatabaseGeneratedOption.None)]publiclongI......
  • .net6 webApi IoC SqlSugar的日常使用
    .Net6WebApi使用SqlSugar1、Nuget先引入:SqlSugarCore2、NetIOC1、注入ISqlSugarClient.NET自带的IOC使用也很方便 先封装一个操作类//建一个扩展类publicstat......
  • DDD20 End-to-End Event Camera Driving Dataset: Fusing Frames and Events with Dee
    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布!AcceptedinThe23rdIEEEInternationalConferenceonIntelligentTransportationSystems(SpecialSession:......
  • 记录自己使用.net6
    首先依赖注入懒得下载autofac了直接用程序集进行批量注入privatestaticWebApplicationBuilderbuilder;internalstaticvoidLoad(WebApplicationBuilder......
  • MediatR:EF Core中发布领域事件
    领域事件大部分发生在领域模型的业务逻辑方法上或者领域服务上,我们可以在一个领域事件发生的时候立即调用IMediator的Publish方法来发布领域事件。我们一般在聚合根的实体......