关于后端:浅谈DDD中的聚合

35次阅读

共计 5865 个字符,预计需要花费 15 分钟才能阅读完成。

简介:在我看来并不是 MVC 的根底上减少畛域层,应用充血模型,解耦根底服务,我的代码就合乎 DDD 了。

作者 | 李宇飞 (菜尊) 起源 | 阿里开发者公众号在我看来并不是 MVC 的根底上减少畛域层,应用充血模型,解耦根底服务,我的代码就合乎 DDD 了。为什么要应用 DDD?DDD 分为策略局部跟战术局部,置信大家都认同 DDD 的外围在策略而非战术。而策略方面的外围我认为在业务建模,畛域划分、对立语言等都在为业务建模服务。为什么业务建模重要? 以前的开发流程有什么问题?先说论断,开发人员交付的程序对业务方,产品人员,测试人员来说就是一个黑盒子。除了开发人员本人,没人晓得盒子里有什么。当新的需要退出来,需求方,产品人员,甚至测试人员都认为可行,开发人员却给出相同论断。回顾一下以前的开发流程 大抵能够归结为以下步骤:(开发跟测试人员最好能参加需要剖析)

业务方形容形象需要产品将需要转化为可落地的产品 (需要具像化,PRD) 开发人员依据产品的 PRD 开发测试人员依据产品的 PRD 测试产品人员验收业务方验收按照教训来说, 在整个流程中开发人员是耗时最长的。与此同时测试人员可能在编写测试用例,而业务方跟产品人员在这段时间内是阻塞的。最终的程序品质靠测试人员来保障。开发人员实现开发后: 测试人员关注测试用例是否通过。产品人员确认展示进去的性能是否合乎当初的 PRD。业务方确认程序是否合乎预期。举一个我开发我的项目的例子一个审批零碎产品的 PRD 形容了一个三层模型:流程实例, 流程节点, 审批工作。流程实例启动创立审批节点,审批节点触发审批工作,工作实现创立下一个节点 …。我是这样做的:流程实例, 审批工作。流程实例启动创立 (一批) 审批工作, 工作被实现后创立后续工作或者流程完结。至于流程节点不存在,不是问题,从工作中提取信息虚构一个进去。第一版交付实现。产品在第一版后追加需要流程节点能够被非审批人评论。我 …. 过后业务方,产品,测试都认为这是一个正当的需要。只有我一脸懵,因为我的程序中没有流程节点这个货色,需要又不能回绝,无奈给出一个远超他们预期的开发计划。gap 就出在 他们认为流程节点是一个的确存在的货色,而只有我晓得这节点是虚构的,没有标识,不能跟其余信息做关联。业务建模怎么解决这个黑盒子问题?DDD 引入了业务专家这个角色 (在我看来就是业务方,产品)。假如业务专家听不懂 什么叫类,什么是办法,设计模式,他只晓得他的业务,两方人马齐全不在同一频道,这个时候就须要“明确上下文”,“对立语言”了。(不仅仅开发人员与业务专家达成了共识,也蕴含整个开发团队达成共识) 业务建模,用例分析法、事件风暴、四色建模等看看开始整上。最终达到划分畛域,辨认聚合的目标。业务建模落地。开发人员开发过程中,应恪守曾经建设的业务模型来编写代码。至此终于实现了,业务专家可通过业务模型窥探到开发人员的代码实现。对立语言、业务模型在业务专家跟开发人员两头充当了沟通的桥梁。(有点像适配器)当追加新的需要时,业务专家能正当评估需要的可行性。让非开发人员参加到开发中对立语言,业务建模,模型充血(OOP)。这一系列伎俩都是为了实现让非开发人员参加到开发中这一最终目标。与其说 DDD 是一种架构,不如认为他是领导开发的方法论。好比盖房子,以前只有把房子交付了能住人就行。当初业务专家是设计师,业务模型是设计图,代码则是建材,程序员就是工地搬砖的,盖起来的房子得跟设计师给出的设计图一样。业务模型落地的问题?这是一个让我纠结的问题。我感觉还没有找到合乎我冀望的答案。业务模型具现到代码中,就是一个个聚合。这些聚合合乎 OOP 的思维,通过聚合根,实体,值对象的组合来表白业务模型。以网络上常见的 demo 为例:// 订单明细实体
public class OrderItemEntity {
//id
private Long id;
// 商品(值对象)
private Product product;
// 数量(值对象)
private Count count;
// item 总价(值对象)
private ItemAmount itemAmt;

public void modifyCount(Count count) {

this.count = count;
this.itemAmt = new ItemAmt(this.product.getPrice()*count.get());

}

}

// 订单聚合根
public class OrderAggregate {
// 聚合惟一标识
private Long id;
// 订单号(值对象)
private OrderNo orderNo;
// 总价 (值对象)
private Amount amt;
// 订单明细
private List<OrderItemEntity> items;

public void modifyItemCount(Product product,Count count) {

// 找到商品
this.items.stream().filter(product::equals).findAny().get();
// 批改数量 返回 Item 总价
item.modifyCount(count);
ItemAmount itemAmt = item.getItemAmt();
// 批改订单总价
this.amt = new Amount(this.amt.get()+itemAmt.get());

}

}

// 要订单明细中 名称叫电脑的商品数量批改 100

Product product = new Product(“ 电脑 ”);
Count count = new Count(100);
Amount amt = orderAggregate.modifyItemCount(product, count); 实践与事实的矛盾从代码上能够看出这一段 1:n 关系的代码齐全基于内存,十分的 OOP,也就是说,咱们在取得 orderAggregate 时,曾经加载整个聚合(包含 List),这里就隐含了一个条件内存无限大。假如 OrderItemEntity 的量级是十万级,百万级,显著这段代码是不能上线的,实践与事实呈现了矛盾。征询了很多大佬 + 集体了解(以下办法为我本人命名)模型晋升法(有限套娃)大佬倡议:“如果真有这种场景,就须要调整聚合,比方: 将 OrderItem 晋升为 Order, Order 晋升为 BatchOrder”思路:创立 BatchOrderAggregate,BatchOrderAggregate 持有 OrderEntity。创立 OrderAggregate 持有局部 OrderItemEntity,通过分治的形式化整为零。思考:整体上合乎业务模型,而且没有下限,即如果 BatchOrderAggregate 不能解决问题,那就祭出 BatchBatchOrderAggregate。BatchBatchOrderAggregate 持有 BatchOrderEntity。BatchOrderAggregate 持有 OrderEntity。OrderAggregate 持有局部 OrderItemEntity。持有仓储法(暗藏了数据库查问,然而直觉上有点反模式)大佬倡议:“聚合中构建索引,须要时再加载置换”思路:聚合根持有存储援用,须要时加载到内存中。能够退出一层接口隔离。public class OrderAggregate {
// 聚合惟一标识
private Long id;
// 订单号(值对象)
private OrderNo orderNo;
// 总价 (值对象)
private Amount amt;
// 关联对象接口(接口实现在根底服务层,在实现中操作数据库)
private OrderItemRel orderItemRel;

public void modifyItemCount(Product product,Count count) {

List<OrderItemEntity> items = orderItemRel.find(product);
// 找到商品
OrderItemEntity item = items.stream().filter(product::equals).findAny().get();
// 批改数量 返回 Item 总价 这里有分支,item 批改是否应该在 modifyCount 中长久化
//1.modifyCount 中长久化 item 那么数据库事务将被加载 AppcationService 层,容易产生大事务问题。//2.modifyCount 中不长久化 item 
//2.1 写入音讯总线,当 OrderAggregate 通过 Reponsitory 长久化时刷出音讯长久化
//2.2 OrderAggregate 中减少 List<OrderItemEntity> items,modifyCount 将 item 退出 items。item.modifyCount(count);
ItemAmount itemAmt = item.getItemAmt();
// 批改订单总价
this.amt = new Amount(this.amt.get()+itemAmt.get());
return this.amt;

}
}自我催眠法思考:“持有仓储法”思路, 实现也统一,感觉反模式的起因是: 聚合中含有数据库操作,有耦合根底服务的嫌疑。然而换个方向去想:内存也是存储介质,数据库也是存储介质,二者原本没有质的区别。二者相比只是对于内存操作, 编程语言间接提供了 API,而数据库拜访须要依赖第三方库进行额定编码,假使能将数据库操作封装至跟内存操作一样天然,那么不是不能够让人承受。Id 关联法(感觉上不太合乎我的预期,有代码实现跟业务建模脱钩,耦合根底服务的嫌疑。容易进化为 MVC,此句属于自己主观臆断)大佬倡议:“对 ddd 了解,不能固化。聚合,本意是解决业务操作的一致性。大量文章,都表述为一次性加载,实操中是不事实的。解决“业务操作一致性”,走“id 关联 + 内聚到一个函数 + 事务管制”,就很好。”“没有必要强行 ddd,拆离开也没有什么问题,通过 id 关联就能够。”“业务建模时依照在同一聚合去建,落地时思考事实,拆分聚合,通过 id 关联。”思考:跟“持有仓储法”很像,区别可能是在于代码写在哪里,然而这种办法总感觉不是 OOP。聚合拆分法(我感觉 applicationService 中应该是跨畛域的流程编排,Order,OrderItem 有雷同的生命周期不能算跨畛域,只能算夸聚合。至于 domainService,做为畛域的一部分,实践上不应该波及根底服务,只是寄存业务相干然而不适宜放在聚合中,也非跨域的代码)大佬倡议:“如果 OrderItem 超过 10 万,20 万,这种状况个别大概率不须要一次性加载进去所有 OrderItem,而是分页加载 OrderItem,这和聚合的特点抵触,倡议设计成两个畛域对象独自治理”思路:建设 OrderAggregate 跟 OrderItemAggregate 两个聚合,通过畛域事件 实现最终统一。灵便场景法(聚合拆分法 Plus)(感觉还是反模型,代码好了解,业务专家不肯定认可,不像自然语言那样天然,为了性能做出斗争)如 demo 中咱们要批改 OrderItem 的数量,这样一个场景,场景主体是 OrderItem 而不是 Order,Order 被批改能够认为是副作用。明确场景的状况下(明确上下文) 能够建模为 OrderItemAggregate 和 OrderEntity 将 1:n 的关系转化为 1:1。// 订单明细聚合
public class OrderItemAggregate {
//id
private Long id;
// 商品(值对象)
private Product product;
// 数量(值对象)
private Count count;
// item 总价(值对象)
private ItemAmount itemAmt;
//Order 实体
private OrderEntity orderEntity;

public void modifyCount(Count count) {

this.count = count;
ItemAmt itemAmt = new ItemAmt(this.product.getPrice()*this.count.get());
//order 总价
Amt amt = this.orderEntity.getAmt();
// 总价 -item 总价 + 新 item 总价
amt = new Amt(amt.get() - this.ItemAmt.get() + itemAmt.get());
// 变更订单总价
this.orderEntity.modifyAmt(amt);
this.itemAmt = itemAmt;

}

}

// 订单实体
public class OrderEntity {
// 聚合惟一标识
private Long id;
// 订单号(值对象)
private OrderNo orderNo;
// 总价 (值对象)
private Amount amt;

public Amount modifyAmt(Amt amt) {

// 批改订单总价
this.amt = amt;

}

}

// 要订单明细中 名称叫电脑的商品数量批改 100
orderItemAggregate.modifyCount(count); 删除 / 增加订单明细同理。而,订单领取 (订单被领取) 的场景,业务主体是 Order(这个场景下 OrderItem 甚至不会呈现批改,当然也就没有必要加载 OrderItem)。总结感激各位大佬提供本人的思路为我解惑。对与聚合落地,因为最初一种灵便场景变动聚合的思路,齐全无对于根底服务,放弃了聚合内的一致性,合乎 DDD 畛域只关注业务的思维,而且勉强合乎 OOP,且落地成本低,从心里上我更偏向于最初一种形式。惟一的难点在于压服本人他是一个失常的业务模型。举荐浏览 1. 如何写出一篇好的技术计划?2. 阿里 10 年积淀|那些技术实战中的架构设计办法 3. 如何做好“防御性编码”?阿里云产品测评—开源 PolarDB-PG 体验阿里云自主研发的云原生关系型数据库产品,100% 兼容 PostgreSQL,高度兼容 Oracle 语法;采纳基于 Shared-Storage 的存储计算拆散架构,具备极致弹性、毫秒级提早、HTAP 的能力和高牢靠、高可用、弹性扩大等企业级数据库个性。公布评测,写下你的感触与评估即可取得多重福利。点击这里,查看详情。原文链接:https://click.aliyun.com/m/10… 本文为阿里云原创内容,未经容许不得转载。

正文完
 0