DDD学习总结

什么是DDD
1、DDD(Domain-driven design,模型驱动设计)是一个很好的应用微服务架构的方法论。
2、在项目的全生命周期内,所有岗位的人员都基本对业务的相同的理解来展开工作。所有人员站在用户的角度、业务的角度去思考问题,而不是站在技术的角度去思考问题。
3、诞生于2004年,兴起于2014年(微服务元年)。


DDD缺点

1、每个人对与领域的理解不同,对与复杂业务概念,增加了开发人员的理解成本,对开发人员能力要求高;

2、不同领域协作成本高,因为理解有偏差,会出现各说各有理的情况,必须有一个强有力的人来决策(某些老板可能不善于决策),否则就是没完没了的沟通;

 https://www.dianjilingqu.com/483391.html

https://www.fengnayun.com/news/content/317670.html 推荐

 

领域

1、"领域"(Domain):一个组织做的事情,子领域
2、领域的划分(以手机公司为例):
核心域:解决项目的核心问题,和组织业务紧密相关。
支撑域:解决项目的非核心问题,则具有组织特性,但不具有通用性。
通用域:解决通用问题,没有组织特性。
3、领域的不同分类决定了公司的研发重点。

 

领域模型(Domain Model)
1、对于领域内的对象进行建模,从而抽象出来模型。以银行为例。银行网点子系统:柜员、客户、ATM、排队机.....
2、我们的项目应该开始于创建领域模型,而不是考虑如何设计数据库和编写代码。使用领域模型,我们可以一直用业务语言去描述和构建系统,而不是使用技术人员的语言。

 

实体(Entity)
1、“标志符”用来唯一定位一个对象,在数据库中我们一般用表的主键来实现“标志符”。主键和标志符的思考角度不同。
2、实体拥有唯一的标志符,标志符的值不会改变,而对象的其他状态则会经历各种变化,标志符用来跟踪对象状态变化,一个实体的对象无论怎样变化,我们都能通过标志符定位这个对象。
3、实体一般的表现形式就是EF Core中的实体类。

 

值对象(Value Object)
1、值对象:没有标志符的对象,也有多个属性,依附于某个实体对象而存在。比如“商家”的地理位置、衣服的RGB颜色。
2、定义为值对象和普通属性的区别:体现整体关系。

 

值对象和实体区别:是否有标志符,值对象不能独立于实体而存在

值对象好处:
1、把有紧密关系的属性打包为一个独立的类型
2、把领域知识放到类的定义中
class ShangPin{
    long id;
    string name;
    Weight weight;
}
public class Weight{
    double Value;
    WeightUnit Unit;
}
enum WeightUnit{G,KG}

efcore配置:builder.OwnsOne(x=>x.Weight);

record MultiLangString(string? Chinese,string? English);
public MultiLangString Name{get;set;}
builder.OwnsOne(c=>c.Name,nb=>{
    nb.Property(e=>e.English).HasMaxLength(20).IsUnicode(false);
    nb.Property(e=>e.Chinese).HasMaxLength(20).IsUnicode(true);
});


枚举数据库中保存为字符串类型,efcore配置:builder.Property(e=>e.Currency).HasConversion<string>();

 

聚合(Aggregate)、聚合根
1、目的:高内聚,低耦合。有关系的实体紧密协作,而关系很弱的实体被隔离。
2、把关系紧密的实体放到一个聚合中,每个聚合中有一个实体作为聚合根(Aggregate Root),所有对于聚合内对象的访问都通过聚合根来进行,外部对象只能持有对聚合根的引用。
3、聚合根不仅仅是实体,还是所在聚合的管理者。

聚合的判断标准:
实体是否是整体和部分的关系,是否存在相同的生命周期;

聚合的意义:
聚合体现的是现实世界中整体和部分的关系,比如订单和订单明细。整体封装了对部分的操作,部分与整体有相同的生命周期。部分不会单独与外部系统单独交互,与外部系统的交互都由整体来负责。

聚合的判断标准:实体是否是整体和部分的关系,是否存在着相同的生命周期,部分不会单独与外部系统单独交互,与外部系统的交互都由整体来负责。

聚合的划分原则
1、尽量把聚合设计的小一点,一个聚合只包含一个聚合根实体和密不可分的实体,实体中只包含最小数量的属性。
2、小聚合有助于进行微服务的拆分。
一般一个聚合中包含1到3个实体,大部分聚合是一个实体;

跨表查询
1、所有跨聚合的数据查询都应该通过领域服务的协作来完成,而不应该是直接在数据库表之间进行join查询。会有性能损失,需要做权衡,不是死规矩。
2、对于统计、汇总等报表类的应用,则不需要遵循聚合的约束,可以通过执行原生SQL等方式进行跨表的查询。

 

领域服务、应用服务
1、聚合中的实体中没有业务逻辑代码,只有对象的创建、对象的初始化、状态管理等个体相关的代码。
2、对于聚合内的业务逻辑,我们编写领域服务(Domain Service),而对于跨聚合协作以及聚合与外部系统协作的逻辑,我们编写应用服务(Application Service)。
3、应用服务协调多个领域服务、外部系统来完成一个用例。

实体中的逻辑代码:管理实体的创建、状态等非业务逻辑。
领域服务:聚合内的业务逻辑。
应用服务:聚合间、和外部系统的业务逻辑。  

职责的划分
1、领域模型与外部系统不会发生直接交互,即领域服务不会涉及数据库操作。
2、业务逻辑放入领域服务,而与外部系统的交互由应用服务来负责(保存数据到数据库应在应用服务中操作)

3、领域服务不是必须的,在一些简单的业务处理中(比如增删改查)是没有领域知识(也就是业务逻辑)的,这种情况下应用服务可以完成所有操作,不需要引入领域服务。这样可以避免过度设计。

 

领域事件、集成事件

1、DDD中的事件分为两种类型:领域事件(Domain Events)和集成事件(Integration Events)。

领域事件:在同一个微服务内的聚合之间的事件传递。使用进程内的通信机制完成。通过MediatR实现
集成事件:跨微服务的事件传递。使用事件总线实现。(EventBus、CAP)通过Redis、RabbitMQ(推荐)、Kafka、ActiveMQ等消息中间件实现

优点:关注点分离、容易扩展、容错性好

 

充血模型、贫血模型
充血模型:一个类中既有属性、成员变量、也有方法;缺点:更复杂,用起来难,优点:代码更加优美,可维护性更好
贫血模型:一个类中只有属性或者成员变量,没有方法

 

EFCore基于性能和对特殊功能支持的考虑,EFCore在读写属性的时候,如果可能,它会直接跳过get、set,而直接操作真正存储属性值的成员变量。

属性的get、set编译器最终会编译成get_xxx()和set_xxx()方法,属于语法糖

EFCore会尝试按照命名规则去直接读写属性对应的成员变量,只有无法根据命名规则找到对应成员变量的时候,EFCore才会通过属性的get、set代码来读写属性值

 可以在FluentAPI中通过usePropertyAccessModel()方法来修改默认的这个行为。 

成员变量没有对应属性,但需要映射为数据表中的列
实现:efcore配置时builder.Property("成员变量名")

从数据列中读取值的只读属性:
在配置实体类的代码中,使用HasField("成员变量名")来配置

private string? remark;
public string? Remark{
    get{
        return this.remark;
    }
}
builder.Property(e=>e.Remark).HasField("remark");

 

“仓储”(Repository)、“工作单元”(Unit Of Work)
1、仓储负责按照要求从数据库中读取数据以及把领域服务修改的数据保存回数据库。
2、聚合内的数据操作是关系非常紧密的,我们要保证事务的强一致性,而聚合间的协作是关系不紧密的,因此我们只要保证事务的最终一致性即可。
3、聚合内的若干相关联的操作组成一个“工作单元”,这些工作单元要么全部成功,要么全部失败。

原则
1、工作单元是由应用服务层来确定,其它层不应该调用SaveChangesAsync方法保存对数据的修改。
2、可以开发一个在控制器的方法调用结束后自动调用SaveChangesAsync的Filter:UnitOfWorkAttribute、UnitOfWorkFilter
public class UnitOfWorkAttribute:Attribute
{
    public Type[] DbContextTypes{get;init;}
    public UnitOfWorkAttribute(params Type[] dbContextTypes){
        this.DbContextTypes=dbcontextTypes;
    }
}

 

防腐层(ACL Anti Corruption Layer)
外部服务(短信服务、邮件服务、存储服务等)的变化会比较频繁,把这些服务定义为接口,在内层代码中我们只定义和使用接口,在外层代码中定义接口的实现。

 

Domain服务:实体类、值对象、枚举、事件、防腐层接口、仓储接口、领域服务
Infrastructure(基础设施):实体类的数据库配置、DbContext、防腐层接口实现、仓储接口实现
WebAPI(应用服务):Controller、事件(领域事件、集成事件)的响应类、工作单元的控制、事务控制、领域服务调用

原则:
1、领域模型、领域服务中只是定义了抽象的实体、防腐层和仓储,我们需要在基础设施中对它们进行落地和实现。
2、实体类、值对象的定义是和持久机制无关的,而它们需要通过EFCore的配置、上下文等建立和数据库的关系。
3、上下文等也是和持久层相关的,也放到基础设施层

 

 应用层职责

1、应用层主要进行的是数据的校验、请求数据的获取、领域服务返回值的显示等处理,并没有复杂的业务逻辑,因为主要的业务逻辑都被封装在领域层。
2、应用层是非常薄的一层,应用层主要进行安全认证、权限校验、数据校验、事务控制、工作单元控制、领域服务的调用等。从理论上来讲,应用层中不应该有业务规则或者业务逻辑。

 

对于使用了init关键字的属性,允许调用方在初始化的时候对其赋值一次,之后如果再次赋值,就会出现编译错误

Get开头的方法,一定不返回null
Find开头的方法,可能返回null

聚合内外键保留,聚合间没有外键。

微服务结构优缺点
优点:耦合性低,易于开发和维护;可以用不同技术栈;可以单独扩容;互相隔离,影响小;部署周期短;
缺点:对运维能力要求高;运行效率会降低;技术要求高,需要处理事务最终一致性等问题;

posted @ 2023-07-02 21:04  事理  阅读(26)  评论(0)    收藏  举报