Part6 – (6) DDD之实体 值对象
实体 (Entity)
1、“标识符”用来唯一定位一个对象,在数据库中我们一般用表的主键来实现“标识符”。主键和标识符的思考角度不同。
2、实体:拥有唯一的标识符,标识符的值不会改变,而对象的其他状态则会经历各种变化。标识符用来跟踪对象状态变化,一个实体的对象无论怎样变化,我们都能通过标识符定位这个对象。
3、实体一般的表现形式就是EF Core中的实体类。
值对象(Value Object)
1、值对象:没有标识符的对象,也有多个属性,依附于某个实体对象而存在。比如“商家”的地理位置、衣服的RGB颜色。
2、定义为值对象和实体的区别:体现整体关系。
Part6 – (7) DDD之聚合、聚合根
聚合 (Aggregrate)
1、目的:高内聚,低耦合。有关系的实体紧密协作,而关系很弱的实体被隔离。
2、把关系紧密的实体放到一个聚合中,每个聚合中有一个实体作为聚合根(Aggregate Root),所有对于聚合内对象的访问都通过聚合根来进行,外部对象只能持有对聚合根的引用。
3、聚合根不仅仅是实体,还是所在聚合的管理者。
聚合的意义
1、为什么聚合可以实现“高内聚,低耦合”。
2、聚合体现的是现实世界中整体和部分的关系,比如订单与订单明细。整体封装了对部分的操作,部分与整体有相同的生命周期。部分不会单独与外部系统单独交互,与外部系统的交互都由整体来负责。
聚合的划分很难
1、系统中很多实体都存在着不同程度的关系,这些关系到底是设计为聚合之间的关系还是聚合之内的关系是很难的。
2、聚合的判断标准:实体是否是整体和部分的关系,是否存在着相同的生命周期。
3、订单与订单明细?用户与订单?
聚合的划分没有标准答案
1、不同的业务流程也就决定了不同的划分方式。
2、新闻和新闻的评论?
聚合的划分原则
1、尽量把聚合设计的小一点,一个聚合只包含一个聚合根实体和密不可分的实体,实体中只包含最小数量的属性。
2、小聚合有助于进行微服务的拆分。
Part6 – (8) DDD之领域服务与应用服务
概念
1、聚合中的实体中没有业务逻辑代码,只有对象的创建、对象的初始化、状态管理等个体相关的代码。
2、对于聚合内的业务逻辑,我们编写领域服务(Domain Service),而对于跨聚合协作以及聚合与外部系统协作的逻辑,我们编写应用服务(Application Service)。
3、应用服务协调多个领域服务、外部系统来完成一个用例。
订单系统微服务,采购系统微服务,库存微服务 –> 订单聚合:订单(聚合根实体)、订单明细(普通实体)
//实体中的逻辑代码:管理实体的创建,状态等非业务逻辑
//领域服务:聚合内的业务逻辑
//应用服务:聚和间、和外部系统的业务逻辑
class 订单
{
string id;
DateTime CreateTime;
int TotalAmount;
List<订单明细> Items;
public 订单()
{
this.id=Guid.NewGuid().ToString();
this.CreateTime=Datetime.Now();
}
public AddDetail(string shangpinId, int count)
{
//这个商品是否存在于Items,如果存在,则更新对应条订单明细单Count
//if no, 则向Items中增加一个对象
订单明细 item = Items.SingleOrDefault(i=>i.ShangPinId=shangpinId);
if(item!=null)
{
item.Count+=count;
//更新TotalAmount
}
else
{
item = new 订单明细();
item.ud = GUid.NewGuid().ToString();
item.parentId = this.id;
item.ShangPinId=shangpinId;
item.Count = count;
item.add(item);
//更新TotalAmount
}
}
}
class 订单明细
{
string id;
string parentId;
string ShangPingId;
int Count;
}
DDD典型用例的处理流程
第一步,准备业务操作所需要的数据。
第二步,执行由一个或者多个领域模型做出的业务操作,这些操作会修改实体的状态,或者生成一些操作结果。
第三步,把对实体的改变或者操作结果应用于外部系统。
职责的划分
1、领域模型与外部系统不会发生直接交互,即领域服务不会涉及数据库操作。
2、业务逻辑放入领域服务,而与外部系统的交互由应用服务来负责。
3、领域服务不是必须的,在一些简单的业务处理中(比如增删改查)是没有领域知识(也就是业务逻辑)的,这种情况下应用服务可以完成所有操作,不需要引入领域服务。这样可以避免过度设计。
“仓储”(Repository)和“工作单元”(Unit Of Work)
1、仓储负责按照要求从数据库中读取数据以及把领域服务修改的数据保存回数据库。
2、聚合内的数据操作是关系非常紧密的,我们要保证事务的强一致性,而聚合间的协作是关系不紧密的,因此我们只要保证事务的最终一致性即可。
3、聚合内的若干相关联的操作组成一个“工作单元”,这些工作单元要么全部成功,要么全部失败。
Part6 – (9) DDD之领域事件和集成事件
事务脚本处理“事件”
1、“当发生某事件的时候,执行某个动作”。
2、当有人回复了用户的提问的时候,系统就向提问者的邮箱发送通知邮件。事务脚本的实现:
void 保存答案(long id,string answer)
{
保存到数据库(id,answer);
string email = 获取提问者邮箱(id);
发送邮件(email,"你的问题被回答了");
}
问题1:代码会随着需求的增加而持续膨胀。比如增加功能“如果用户回复的答案中有涉嫌违法的内容,则先把答案隐藏,并且通知审核人员进行审核”。怎么做?
问题2:代码可扩展性低。比如把“发送邮件”改成“发送短信”,怎么办?
“开闭原则”:对扩展开放,对修改关闭。在不修改已有代码的情况下,增加新功能。
问题3:容错性差。外部系统并不总是稳定的。
采用事件机制的伪代码
优点:关注点分离;容易扩展;容错性好;
void 保存答案(long id,string answer)
{
long aId = 保存到数据库(id,answer);
发布事件("答案已保存",aId,answer);
}
[绑定事件("答案已保存")]
void 审核答案(long aId,string answer)
{
if(检查是否疑似违规(answer))
{
隐藏答案(aId);
发布事件("内容待审核",aId);
}
}
[绑定事件("答案已保存")]
void 发邮件给提问者(long aId,string answer)
{
long qId = 获取问题Id(aId);
string email = 获取提问者邮箱(qId);
发送邮件(email,"你的问题被回答了");
}
两种事件
1、DDD中的事件分为两种类型:领域事件(Domain Events)和集成事件(Integration Events)。
2、领域事件:在同一个微服务内的聚合之间的事件传递。使用进程内的通信机制完成。
3、集成事件:跨微服务的事件传递。使用事件总线(EventBus)实现。
本文版权归个人技术分享站点所有,发布者:chaoqiang,转转请注明出处:https://www.zhengchaoqiang.com/1589.html