第6周
2020年02月17日 - 2020年02月23日

编辑|子鱼

#问答与讨论

1.请教大家个问题,大家在代码落地的时候,实体里面会引用repository么,还是持久化相关的操作放到领域服务里?

[lincoln]

DDD少林派

➤ 问题解答
lincoln21:05这两种方式我看都有公司用
lory(侯保国)21:06是这样,如果小项目,我就直接放在entity里面
远远鱼21:09@DDD是啥 实体不会依赖资源库;资源库的客户尽可能是应用层。领域服务要承载领域知识,而不是过程。
忘却录音21:15不要放在实体里面
忘却录音21:16领域模型一般要保证模型的一致性,完整性。引入仓库会让领域模型不纯粹
yanhua21:29不要放在实体里面
lincoln22:02https://blog.csdn.net/alitech2017/article/details/81905075
lincoln22:03这有一篇盒马技术团队的文章,他们是实体引用repository的实践
lincoln22:04还是,盒马资深技术专家辉子,分享他眼中的领域驱动设计及实践经验。[捂脸]
远远鱼22:17**张群辉的这个方法并不是主流的方法

2.一个蛋疼又常见的行为,增加商品(spu)的同时增加库存(sku),在ui上是很常见。因为划分时商品spu和库存sku是两个不同的上下文,我目前的做法是将请求给到商品(spu)上下文,因为语义是增加商品,增加完后,再通过acl调用库存上下文,将所需参数传过去,增加库存。

[林杰]

DDD少林派

➤ 问题解答
泉乐22:57@林杰 你这个场景就是我们系统现在的场景
泉乐22:58消费者单的处理,既会使得库存受到影响,同时还会产生很多的库存账数据。并且库存的变化,又会引发两个业务主体间的补货。
泉乐22:59补货又涉及系统内和系统间。
林杰22:59也是同样的做法么
林杰22:59你的业务比我复杂点
泉乐23:00ACL是啥的缩写?
lory(侯保国)23:00能不能发布事件,如果在增加sku的时候,更新库存,发布一个StockUpdated
lory(侯保国)23:00event
lory(侯保国)23:00anticorrumptionlayer
泉乐23:01等闲一点,想把我这个项目总结一下,跟大伙一起来看看怎么样才是合理的ddd设计。
林杰23:02防腐层
林杰23:04「泉乐:等闲一点,想把我这个项目总结一下,跟大伙一起来看看怎么样才是合理的ddd设计。」———————-这个好,
远远鱼23:04“林杰:一个蛋疼又常见的行为,增加商品(spu)的同时增加库存(sku),在ui上是很常见。因为划分时商品spu和库存sku是两个不同的上下文,我目前的做法是将请求给到商品(spu)上下文,因为语义是增加商品,增加完后,再通过acl调用库存上下文,将所需参数传过去,增加库存。”———————-“增加多少库存”是在UI上配置的么?
lory(侯保国)23:04关系带来的是依赖,因而需要控制。EricEvans给出了3种方法:规定一个遍历方向添加一个限定符,以便有效地减少多重关系消除不必要的关系这3种方法都是为了让类之间的关联关系变得更加健康,尤其应避免双向的导航方向。我们必须一再强调从单一导航方向的视角对关系建模。之所以会出现双向导航,是因为类之间存在双向关联。双向关联一定存在主次。若存在双向关联的类属于同一个聚合,由于聚合类的实体之间可以采用对象引用的形式,就应保留“主类型”导航向“从类型”的方向。例如,聚合内的Order与OrderItem之间的关系,既可以描述为订单拥有多个订单项,也可以描述为订单项属于某个订单。订单为主,订单项为从,故而应该只保留从Order到OrderItem的单一导航方向。若存在双向关联的类分属两个不同的聚合,且为各自的聚合根实体,为了降低彼此的依赖强度,往往是保留“从类型”导航向“主类型”的方向。例如,Customer与Order是两个聚合的根实体,客户为主,订单为从,则应该保留从Order聚合根到Customer聚合根的单一导航方向。
lory(侯保国)23:04有很多时候,如User和Role的关系
lory(侯保国)23:05就是一个典型的双向关系,一般如何解决这个问题
林杰23:06「远远鱼:”林杰:一个蛋疼又常见的行为,增加商品(spu)的同时增加库存(sku),在ui上是很常见。因为划分时商品spu和库存sku是两个不同的上下文,我目前的做法是将请求给到商品(spu)上下文,因为语义是增加商品,增加完后,再通过acl调用库存上下文,将所需参数传过去,增加库存。”———————-“增加多少库存”是在UI上配置的么?」———————-对的,为了方便用户操作,一般都是有这个功能
远远鱼23:07那么在商品上下文里还要把这个库存数量传给库存上下文么?
林杰23:07对,如果不拆分请求的话,需要的。
远远鱼23:08感觉是让商品上下文强行承担了增加库存的职责呢
lory(侯保国)23:09发布事件呢?
远远鱼23:09是不是可以从UI上分离成两个请求,或者用BFF去组合这两个请求?
林杰23:10「远远鱼:是不是可以从UI上分离成两个请求,或者用BFF去组合这两个请求?」———————-有想过这样操作,
lory(侯保国)23:11bfe可能是一个好的选择
lory(侯保国)23:11ui拆请求,增加了客户端的编程
远远鱼23:12“lory(侯保国):发布事件呢?”———————-请问是谁,在什么时候发布事件呢?
林杰23:12对的,不过尽量保证增加的时候同时成功,否则对用户来说,就很奇怪,而且sku依赖spu,需要关联spu的id
lory(侯保国)23:12addProduct中,发布一个事件
lory(侯保国)23:13在domainService中
远远鱼23:13“远远鱼:是不是可以从UI上分离成两个请求,或者用BFF去组合这两个请求?”———————-这样可以让两个限界上下文没有集成(至少在这个点上是),运用“各行其道”模式,减少复杂度
远远鱼23:14“lory(侯保国):addProduct中,发布一个事件”———————-那应该发布一个“商品已创建”的事件看上去更合理。如果是发布“StockUpdated”,会不会有些奇怪?
lory(侯保国)23:16**嗯,你说的对

3.请问一个聚合根可以定义多种领域事件吗,不同的逻辑发送不同的领域事件

[刘杰]

DDD少林派

➤ 问题解答
滕云14:02可以的,并且这是种非常正常的情况
阿斌哥16:26实现进程内领域事件的发布和订阅有没有什么好用的框架?
刘杰16:29进程内还是用事务直接处理吧
阿斌哥16:30其他大佬也是这个观点吗?
远远鱼16:30@阿斌哥我用过Spring的事件处理,还挺方便的
刘杰16:31spring事件处理能保障事务一致性吗
远远鱼16:33https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2
远远鱼16:35@刘杰技术上spring默认事件监听器是在同一个事务里执行的,也可以配置成不同线程(非同一个事务)
刘杰16:35明白了,谢谢
阿斌哥16:35这个跟guava的eventbus相比不知道如何@远远鱼
远远鱼16:36@阿斌哥功能上差不多。具体有啥差别我就不清楚了
刘杰16:39你们发送的是领域事件,还是和张逸文章中的说的应用事件啊
远远鱼16:40都可以发
远远鱼16:42“刘杰:你们发送的是领域事件,还是和张逸文章中的说的应用事件啊”———————-你提到的应用事件在哪章里?我再去看一眼
阿斌哥16:42我也想去再看一眼[表情]
刘杰16:45https://gitbook.cn/gitchat/column/5cdab7fb34b6ed1398fd8de7/topic/5d7f548349b2b1063b520d23
@nichole16:45https://gitbook.cn/gitchat/column/5cdab7fb34b6ed1398fd8de7/topic/5d7f55c649b2b1063b520d29由于事件溯源模式运用在限界上下文的边界之内,它所操作的事件属于领域设计模型的一部分。若要准确说明,应称呼其为“领域事件”,以区分于发布者—订阅者模式操作的“应用事件”。
刘杰16:47https://enterprisecraftsmanship.com/posts/merging-domain-events-dispatching/但和这篇文章观点会有所不一样。我个人认为如果都是领域驱动的方式,可能只有领域事件的。
一位热心的市民19:11guava
一位热心的市民19:12**对外部依赖越少,配置越简单越好。

4.看张老师推荐的文章中,提到三层架构缺点里面的架构元素具体指什么?

[花雾]

DDD少林派

➤ 问题解答
花雾23:40**三层(或多层)架构仍然是目前最普遍的架构,但它也有缺点:架构被过分简化,如果解决方案中包含发送邮件通知,代码应该放置在哪些层里?它虽然提出了业务逻辑隔离,但没有明确的架构元素指导我们如何隔离

5.请教各位针对张老师这个系列。跳过战略,直接看战术,行不行额?

[lincoln]

DDD少林派

➤ 问题解答
花雾08:44
Logos13:47[Onlooker]为啥我觉得战略篇干货多些呢
甘泉13:51只看战略轮到自己写代码都不知道怎么去写。
阿斌哥14:13战术是招式,战略是心法。可以跳过战略看战术,但是看完战术一定要回头再看战略,否则战术将无所归依,就像行尸走肉一般,没了灵魂。[呲牙]
lucoo14:14总结的好[强]
阿斌哥14:16我个人的学习过程其实是从战术开始的,然后到战略,再回到战术。不是不想从战略开始,实在是刚入门的时候战略看不懂[表情]
张智高14:22感觉要多撸几遍啊,消化不良[表情]
lucoo15:44@阿斌哥大佬,你们现在都在实际项目中落地了吗
小曦阳哟15:47就是呀,大佬,你们项目中都已经用了领域驱动了没呀
阿斌哥15:56之前只是小规模的使用DDD,目前正在一个稍大的项目中落地,可能会遇到很多挑战
刘杰15:57我们一个非常大的项目准备落地,但只是在中台用
小曦阳哟15:58@阿斌哥你有没有写过相关的文章呀,实践中的踩坑记呢
阿斌哥16:01目前小打小闹没有遇到太多挑战,所以也就没有太多坑可以踩
小曦阳哟16:01我现在都不知道领域驱动的项目结构应该是什么样的
林杰16:02书里面有的

6.老师的github地址是多少来着?

[123abc]

DDD少林派

➤ 问题解答
林杰17:05**https://github.com/agiledon

7.同一个微服务的不同聚合之间的数据一致性,大家都用什么方案呢。妥协的办法是在聚合的领域服务调用其它聚合的领域服务。?

[深山小书童]

DDD少林派

➤ 问题解答
远远鱼16:39在应用服务里协调吧
远远鱼16:39你说的“聚合的领域服务”是指啥?每个聚合固定有一个对应的领域服务么?
深山小书童16:40每一个聚合都有领域服务
深山小书童16:40在应用层协调要用到分布式事务了
奋斗16:40saga
深山小书童16:41在同一个微服务内部用分布式事务,大家尝试过吗。。
远远鱼16:42“深山小书童:在应用层协调要用到分布式事务了”———————-在同一个服务里可以直接用事务,为什么一定要分布式事务呢?
Zpl16:42@深山小书童……不是在同一个微服务了嘛?为什么还会分布式事务
远远鱼16:42“深山小书童:每一个聚合都有领域服务”———————-为啥要这样设计?
深山小书童16:44事务开在聚合的领域层,保证聚合内实体的数据的强一致性,跨聚合用分布式事务保证。
深山小书童16:45每一个聚合都有领域服务是为了方便聚合的重组和移动。
远远鱼16:59“深山小书童:事务开在聚合的领域层,保证聚合内实体的数据的强一致性,跨聚合用分布式事务保证。”———————-事务是应用层的职责。领域层不应该关心事务
深山小书童17:02事务开在应用层的话,如果有远程调用就有可能出现长事务,所以我们现在的做法是应用层只负责编排服务,不管事务[捂脸]
远远鱼17:04应用层也不一定要整个包在事务里啊,可以把远程调用放到事务外面
深山小书童17:04到底事务是开在应用层还是领域层呢?大佬们怎么看
Zpl17:06@深山小书童事务,是在应用层的
深山小书童17:07通过协调调用顺序是可以一定程度避免长事务应用服务里面如多调用太多领域服务也会出现长事务~就看怎么取舍,有没有规范
Zpl17:07文章里面有写
Zpl17:07UnitofWork
Clsaa17:10事务/事件都应该做成透明的吧,事务在应用层开启,发出event的时候判断是一个远程event还是本地event,然后根据否有事务的标,确定是是否开启事务event.这样即使应用拆分,任何事务实现/通信方式都不会发生变化
深山小书童17:23嗯,谢谢大家,我也找到原文了
深山小书童17:23刘杰17:32求文章地址
深山小书童17:33这个是实现领域驱动那本书里面的
深山小书童17:33ImplementingDomain-DrivenDesign
Clsaa19:33书上说对AGGREGATE而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用。是不是意味着最好把某个聚合的实体放到一个包里,只有聚合根是public的,其他的实体应该是package的?
远远鱼19:45其实这样也可以
飞邪19:57顺便问下大家项目里聚合的概念是逻辑的还是物理的?用包来逻辑表示聚合?还是显示定义聚合类?
Clsaa19:58我们是包
Clsaa19:59Clsaa19:59这样的
飞邪20:00各有优劣。包这种方式看不出根实体,不直观
Clsaa20:02我们是这样的
Clsaa20:02Clsaa20:03要定义两遍比较烦,但如果只定义AggregateRoot很多东西又要搞成约定俗成的比较死板
飞邪20:17刚刚说ci检查点,好奇你们目前加了ddd哪些规范检查?
Clsaa20:28大概根据DDD的那三本书,整理了49条主要是BC/实体/聚合/值对象的
飞邪20:31你们项目看来是很有决心了,可以开源出来大家借鉴一下[呲牙]
远远鱼20:34“Clsaa:在CI流程中加了一个卡点,做DDD代码规范检查,这块不知道该怎么去约束”———————-可以用archunit去做架构守护测试https://www.archunit.org/
Clsaa20:35大概下个月就会开源出来一个DDD代码检查的IDEA插件,卡点那块环境比较复杂还开源不出来.主要是DDD现在有道无器啊,每个人都有每个人的实现方法,代码乱死了
Clsaa20:36「远远鱼:”Clsaa:在CI流程中加了一个卡点,做DDD代码规范检查,这块不知道该怎么去约束”———————-可以用archunit去做架构守护测试https://www.archunit.org/」———————-谢谢我去学习一下
远远鱼20:37“Clsaa:我们是包”———————-这个包下只有聚合相关的类么?聚合之外的领域服务放在哪里呢?
lockness20:40觉得包或命名空间用于BC合理聚合根就是BC下普通类
飞邪20:41「远远鱼:”Clsaa:我们是包”———————-这个包下只有聚合相关的类么?聚合之外的领域服务放在哪里呢?」———————-这也是一个不直观的地方。领域服务摆放位置不明显。老师的githubeas项目里领域服务就是放在同聚合一个包里
远远鱼20:42“lockness:觉得包或命名空间用于BC合理聚合根就是BC下普通类”———————-DDD里还有模块(module)的概念,是用于在一个限界上下文里组织类的。
Clsaa20:44「远远鱼:”Clsaa:我们是包”———————-这个包下只有聚合相关的类么?聚合之外的领域服务放在哪里呢?」———————-领域服务应该是属于某个聚合的
Clsaa20:44Clsaa20:45Clsaa20:48「lockness:觉得包或命名空间用于BC合理聚合根就是BC下普通类」———————-你的说法中间跳过了聚合这一层,应该是BC下有聚合聚合下有一个聚合根,聚合还包括一个边界,聚合相关的实体都被包含在这个边界里.我觉得BC和聚合都可以用包来表示.
飞邪20:49「Clsaa:「远远鱼:”Clsaa:我们是包”———————-这个包下只有聚合相关的类么?聚合之外的领域服务放在哪里呢?」———————-领域服务应该是属于某个聚合的」———————-我觉得不能这么说,领域服务也是跨聚合的
远远鱼20:50@Clsaa从你的代码结构来看,你说的domain.accout这个package,其实是module,并不是聚合。这个module的名字是account。领域服务属于module。
Clsaa21:03「飞邪:「Clsaa:「远远鱼:”Clsaa:我们是包”———————-这个包下只有聚合相关的类么?聚合之外的领域服务放在哪里呢?」———————-领域服务应该是属于某个聚合的」———————-我觉得不能这么说,领域服务也是跨聚合的」———————-你们是几个聚合外面加了层module,module里有个包叫service里面放着domainservice?
飞邪21:05差不多。我们的domain就是一个pom或者maven项目,这里也可以理解为module。然后下面就是各个模型元素的包
Clsaa22:07「远远鱼:@Clsaa从你的代码结构来看,你说的domain.accout这个package,其实是module,并不是聚合。这个module的名字是account。领域服务属于module。」———————-是的,我们现在没用module的概念,相当于Aggregate和Module是1v1的关系.我感觉大部分聚合之间没有内聚性,而且往往聚合是否内聚的判断比较靠近应用层,容易被当前的场景误导.所以我们就这么干的
Clsaa22:10**「飞邪:差不多。我们的domain就是一个pom或者maven项目,这里也可以理解为module。然后下面就是各个模型元素的包」———————-那你们的module不会很大吗?ApplicationService就更大了那你们单元测试对domainService还是applicationService的?

8.仓储的实现如果选择SQL数据库,每次加载1个聚合,你们的SQL会被DBA吐槽吗?

[Zpl]

DDD少林派

➤ 问题解答
陈瑞12:05聚合有多大?
Zpl12:15就根实体,10个属性
陈瑞12:18被聚合进去的类有多少个?
Zpl12:45@陈瑞聚合就一个根实体
阿斌哥12:46DBA吐槽的点在哪里?
Zpl12:50SELECT*
陈瑞13:02**这个没什么问题,只要不是join过多就行了

9.DomainPrimitive是DDD里面提出来的吗

[花雾]

DDD少林派

➤ 问题解答
远远鱼15:09我之前也没听过这个概念。刚才查了一下,是《SecurebyDesign》这本书里提出来的。参考:https://www.slideshare.net/OmegapointAcademy/domain-primitives-in-action-explore-ddd-2017https://livebook.manning.com/book/secure-by-design/chapter-1/
远远鱼15:11还有这个https://segmentfault.com/a/1190000020270851
花雾15:38我也是看到这个才知道的
冯锐祥11:58**关于DomainPrimitive概念的提出可以参考“…把常见的底层数据类型创建为ADT并使用这些ADT,而不再使用底层数据类型…”—《代码大全》第2版p130

10.请问如果一个表有id主键又有唯一code,关联表的时候用主键id还是code好呢?

[CoderCat]

DDD倚天剑

➤ 问题解答
砂锅米线10:36code
人生如戏10:38业务主键和逻辑主键,大家都怎么取舍的?所有的都用业务主键关联码?
ben10:41用主键性能更优
因即果,果求因。10:42id
丹青心10:42个人倾向主键经历过业务code发生变更的情况
砂锅米线10:43id也会变吧,比如数据导出导入
nc10:57id不用递增就不会变了吧
nc10:57比如用雪花算法
砂锅米线10:59
阿斌哥11:07我个人是倾向于用code,这里的code是指实体的唯一标识,业务上就必须保证是不能变的。不建议用数据库的id作为实体的唯一标识,业务逻辑中也不应该去使用那个自增的id。
阿斌哥11:08如果不对还请各位大佬指正
nc11:09这个code有没有业务含义?
王大宝11:10ID吧,他现在问的是关联表的时候,是技术层面的
nc11:14对呀,主键id就是技术字段[奸笑]
阿斌哥11:14这个code我理解的就是实体的唯一标识
阿斌哥11:16我主要是想说明不太建议用数据库的自增id作为实体的唯一标识,比如订单实体,唯一标识还是用订单号比较合适
nc11:17@阿斌哥 对的[奸笑]
东风如雨08:36**我比较倾向于用逻辑主键,code由于需要业务含义,一般都是varchar类型,主因是查询上不如数字类型块,次因是压测时做影子链路在偏移上也不如数字类型好处理,以及未来分库分表做路由时面临的问题可能(不一定)会少一些

11.把rpc跟appservice拆出来会不会比较好?

[三碗猪脚]

DDD倚天剑

➤ 问题解答
三碗猪脚17:09三碗猪脚17:11我的想法就是
三碗猪脚17:11这个做契约包把所有的dto都在这里包括枚举
三碗猪脚17:11三碗猪脚17:12还有事件
三碗猪脚17:14然后每一层的dto都分开但是有一个supper包来做复用继承这样可以保证后期项目迭代到后面dto膨胀也不会混乱
三碗猪脚17:14三碗猪脚17:14和光同尘17:16借大佬机会请教一问题
三碗猪脚17:23我计划就是api就只开放rpcapi
三碗猪脚17:23dto就全开
三碗猪脚17:23意味着domain的dto也开放出去了
三碗猪脚17:27能给到二方包的一般都是自己人
三碗猪脚17:27其他对外的走http网关
和光同尘17:29「三碗猪脚:能给到二方包的一般都是自己人」———————-确实如此,节省了客户端接口开发吧,有个问题哈,你如果引入其他部门的二方包是放在哪个模块引入
三碗猪脚17:32一般是appservice
三碗猪脚17:32业务上的话
三碗猪脚17:33如果是基础设施就放infrastructure例如中间件团队提供的图片中心什么的
三碗猪脚17:34appservice通过一层dto在传给domain
三碗猪脚17:35我现在还在实践包括单测那块看看有什么问题
三碗猪脚17:35整个开发看起来是繁琐了点
因即果,果求因。21:12因即果,果求因。21:13在看.net下一个框架abp,文档中领域服务可以被其他领域服务所调用,这和张老师提到的避免领域服务间的相互调用感觉冲突啦[捂脸]
三碗猪脚19:55我是觉得领域服务之间不能互调所有需要点参数都在appservice调用domainService的时候以传参的方式给他这样就算说以后服务变动我有个appservice拆出去了也不会影响到主要的逻辑
Antony20:09「因即果,果求因。:在看.net下一个框架abp,文档中领域服务可以被其他领域服务所调用,这和张老师提到的避免领域服务间的相互调用感觉冲突啦[捂脸]」———————-abp是伪ddd,这个作者开篇就表明了
Antony20:09借鉴了ddd
因即果,果求因。20:14**是的,所以在用时候我还是尽量遵从abp框架,不严格遵循ddd

12.我有一个聊天的实体,可以在这实体里面调用仓库去加载聊天消息吗?

[karl]

DDD屠龙刀

➤ 问题解答
反应慢12:15我觉得没问题。问题是你这个聊天实体怎么构建出来
无12:20可以吗[发呆]
karl[表情]12:51**聊天实体中的包含发送消息和接受消息,查看历史消息等方法,发送接受方法直接调用仓库进行保存吗,还是交给应用服务去调用仓库进行保存?

13.询问一下大家,你们在设计微服务的缓存域服务,会把各个域的缓存服务都放在里面吗?还是把缓存服务放在各自的领域里?

[蓝色的咖啡]

DDD屠龙刀

➤ 问题解答
反应慢11:05缓存是基础设施啊,和数据库一样看待
蓝色的咖啡12:59好的,我明白了
秦金卫(kimmking)13:04缓存可以做两层设计,基础的存取,针对业务和策略的部分
秦金卫(kimmking)13:05**前者可以替换不同的底层,比如redis或hz、memcached之类的,后者可以加有效期、更新策略等。

14.大家的系统日志怎么设计?就是记录操作数据、操作人和ip?

[JAMES🦁]

DDD屠龙刀

➤ 问题解答
反应慢13:10这不就是一个个事件吗?
任富飞13:18「程析智能-ElderJames:大家的系统日志怎么设计?就是记录操作数据、操作人和ip」———————-自动日志切面,加ThreadLocal设置tag,可读性比较好。
JAMES🦁13:44**没用CQRS,就没事件了

15.每个聚合中的事务由聚合内的事务自已控制?一个聚合中有order和orderitem,这时会存在两张操作,如果一个领域服务中会调用多个聚合,这时在领域服务中会开启事务,这时其中一个聚合本身就有了事务,这种算不是聚合没设计好?会出现事务嵌套,9聚合A存在单独被调,或被领域服务组合调用

[吴林峰]

DDD屠龙刀

➤ 问题解答
Deemo14:19@峰领域事件异步解耦聚合,聚合之间保证最终一致性
wingyiu14:27事务在应用服务开启
wingyiu14:28事务属于切面,所以放在应用服务
wingyiu14:29吴林峰14:31我的理解是:这种就不是事务的范围了,而是多个聚合调用下的一致性问题了
吴林峰14:33@wingyiu事务如果在应用服务中统一开启肯定是没问题了,但这种属于规避问题
吴林峰14:33wingyiu14:35事务嵌套怎么不好了?
wingyiu14:37你也可以用nosql啊一个聚合就是一个document啊[表情]
吴林峰14:39避免事务嵌套是常识
吴林峰14:39会造成等待死锁的可能发生
反应慢14:39事务传播策略
吴林峰14:42就是这种场景的最佳实践是什么?要保证每个聚合的独立性和完整性,就只能用最终一致性搞定?@反应慢你这种事务传播其实就是使用同一事务,和在应用层统一开启事画一个道理
反应慢14:43调用一个服务的时候,难道不应该原子的完成请求吗?
wingyiu14:44「反应慢:事务传播策略」———————-+1
反应慢14:44转账:a-,b+。购物:a-,这种作为一个聚合的a,自己咋玩事务?
反应慢14:46每个聚合都应该以一个参与者的身份加入到应用服务的处理流程中去,自己都不知道自己在不在一个事务中。
吴林峰14:47那应该是我把“聚合需要遵循事务的ACID原则”这句话理解错了
wingyiu14:47聚合保证内存态的不变量,事务是持久化的事了
反应慢14:48我比较推崇的方案是:a有状态,调用它的一个方法,每次在a都是这个状态的时候,调用结果永远是一样的。无副作用,维持不变性。
反应慢14:49a对象计算出来的结果,由应用服务去决定对不对外产生副作用。
吴林峰14:51那就是每个聚合的acid没法自已保证,由调用方来协调保证,
吴林峰14:51吴林峰14:51我就是对这句话产生的疑惑
吴林峰14:521.要么每个聚合各管各,各用各的工作单元事务,2.要么调用层统一事务;3.要么最终一致性
反应慢14:53吴林峰14:53各管各就是controll中注入两个unitofwork,每个仓储各用各的
反应慢14:53凑合看吧,我比较喜欢的这种方式
反应慢14:57这种聚合都是“不变”的,自然aci保证了,d由应用服务保证啦
阿斌哥16:32这个问题在DDD的三个群里,我印象中起码出现过三次了。我自己最开始也问过一次。我现在的理解就是,那个ACID是聚合的设计原则。但是有些时候是可以打破这些原则的(不是我说的,可能是某位大师说的)。有些时候,一个事务里面更改两个聚合也没什么不可以。但是什么时候打破这个原则,需要权衡
wingyiu16:35「反应慢:这种聚合都是“不变”的,自然aci保证了,d由应用服务保证啦」———————-+1
吴林峰16:50嗯,@阿斌哥所以问出来的都初步尝试中会遇到的
吴林峰16:50你看像这种address值对象,在数据库层面你们会单独建张t_Address表吗
吴林峰16:50吴林峰16:51这样一搞的话,如果一个对象有多个值对象,那不都要建一个通用的表
吴林峰16:51这样在性能上也倒退了
反应慢16:52内嵌进去啊
反应慢16:52领域对象和数据表不是一一对应的
X16:53是啊。。
吴林峰16:53如何存放?是单独一张t_address表还是把address的属性平铺到实体表上?
X16:53没要求一定要为值对象建表
阿斌哥16:53DDD设计的时候是完全不需要去考虑数据库中的表是怎么建的,定义好了repository就行。可以到最后面再来考虑怎么建表。那个时候所需要考虑的就是,怎么效率高怎么来了。纯粹的技术问题
吴林峰16:53嗯,
反应慢16:53你把数据库表看作是内存对象序列化和持久化的一种方式,序列化和持久化的时候怎么高效怎么来。
吴林峰16:54好的,这个提醒的对
随锋灬逐远方16:56@峰你这个设计用的那个工具
随锋灬逐远方16:56阿斌哥16:59基础设施层的实现问题最好放到最后面来考虑,甚至是把整个领域层都测试完了再来考虑技术层面的实现问题,一开始的时候完全不需要考虑数据是存在哪里的以及怎么存。这些都是技术问题,不是业务问题。DDD的目标之一就是为了隔离技术复杂度和业务复杂度的
吴林峰17:03我抓的张老师的图
吴林峰17:03我一般用
吴林峰17:03这个画
吴林峰17:03吴林峰17:04吴林峰17:06我是先进行的ddd代码模型实践,直接从技术层面先上手,然后这两天停了,这两天静下心来把战术这块看完。我是倒着来学的。
吴林峰17:07带着问题来学
wingyiu17:304-6课有你要的答案
wingyiu17:31借助Jpa规范之类的Orm框架很容易搞定持久化时领域模型和数据模型的映射
吴林峰17:46吴林峰17:46我才看到这儿
wingyiu17:49X18:07值对象如果是从别的服务拿这种场景orm就无能为力了
反应慢18:10值对象是聚合的属性
阿斌哥18:22阿斌哥18:22对于一个事务中只修改一个聚合这条原则有疑惑的,张老师文章中已经给出解答了。可以再把这一章再看一下。
阿斌哥18:24https://gitbook.cn/gitchat/column/5cdab7fb34b6ed1398fd8de7/topic/5dce1be6213b7e75ce581df2
吴林峰18:26但实际上真存在这种,一个聚合中有order和orderitem,你save它,肯定要开事务的
吴林峰18:26两张表
X18:29order和orderItem违背了一个聚合一个事务原则了吗?
吴林峰18:30没有
吴林峰18:39@阿斌哥 这是哪一节的,没找到
阿斌哥18:40领域驱动设计对持久化的影响,标题是这个
右军18:41其实吧,事务是一种技术手段,和聚合的关系比较辩证。如果使用消息异步,可以比较好工作单元内聚。如果强事务,不光跨聚合,跨上下文也有
吴林峰18:44嗯,照文章上说,放在应用服务上控制了
阿斌哥19:06“一个聚合对应一个资源库,并保证一个事务中只修改一个聚合实例”。前面半句没啥疑问,后面半句总感觉坑了好多人。我是在《实现领域驱动设计》这本书上看到类似描述的,不知道是不是有什么上下文我没理解到。
阿斌哥19:06现在看来,后面半句如果把“一个事务中只修改一个聚合实例”描述成“只在一个事务中修改一个聚合实例”是不是更好一点?目的是想强调不能把一个聚合的修改放到两个事务中,这样没法满足一致性。
反应慢19:08转账。。。事务
反应慢19:08两个账户聚合
反应慢19:09其实没必要纠结数量的问题
反应慢19:10一个服务,涉及到几个聚合实例,只要这几个实例修改后能保证acid就行
吴林峰19:10他这样描述也是对的,这样天然要花更多的动作解决一致性
吴林峰19:11只不过最简单是放应用层统一工作单元
反应慢19:17反应慢19:17吴林峰19:18就按这个原则弄
吴林峰19:19这样有利演进部署成微服务时才不会花太多精力在事务上
吴林峰19:20不应该怕麻烦而规避搞到一个事务中去
反应慢19:21后面截图的前提是在saga中
反应慢19:21中间有软状态
吴林峰19:22单体不用在saga中,有saga的思想就行
吴林峰19:22最终想成不同聚合是物理隔离的
吴林峰19:23单体时都隔离解决了事务,部成微服务时事务水到渠成
反应慢19:24突然觉得转账这个例子很有意思
吴林峰19:26这个书没有一句废话,全是干货,我搞忘了它上面有提到ddd的影子
反应慢19:26A给B转账,从Repo里把A账户加载出来,检查一下A的余额。发现够转。于此同时,A还在给C转,A->C这个事务先提交的。当A开始给B转的时候,A的余额已经不够用了。
吴林峰19:28看了这个系列,实现领域驱动这本书还有必要看不?
反应慢19:28有啊
阿斌哥19:30“今天,这个约束对微服务架构来说是完美的。它可以确保单个事务的范围不超越服务的边界。”这句话得有个前提吧,涉及到的多个聚合微服务化的时候是要划分到不同的微服务中。有这个前提,这句话才成立。但事实上,一个限界上下文作为一个微服务边界可能是合理的,而一个限界上下文内部同样有多个聚合,在这一个限界上下文内部,多个聚合的修改放到一个事务中并不会对微服务化产生什么影响。
反应慢19:31「反应慢:A给B转账,从Repo里把A账户加载出来,检查一下A的余额。发现够转。于此同时,A还在给C转,A->C这个事务先提交的。当A开始给B转的时候,A的余额已经不够用了。」———————-按道理,这个应该用selectforupdate从数据库里加载出来,但是repo其实不知道你要修改余额,所以加载的时候,不会用selectforudpate
阿斌哥19:31由此推出结论还是作者前面那句话:多年前第一次读到它时,我感觉这条规则毫无意义[呲牙]
吴林峰19:32他后面的服务是两个聚合
吴林峰19:33我觉得只要解决一致性就行了,设计上规避
阿斌哥19:35后面的服务是两个聚合。所以作者后面说了“在单个服务中维护多个聚合的一致性的另一种方法是打破聚合规则,在一个事务中更新多个聚合。”
吴林峰19:37有些时候别打破,只是暂时单体
反应慢19:38「反应慢:「反应慢:A给B转账,从Repo里把A账户加载出来,检查一下A的余额。发现够转。于此同时,A还在给C转,A->C这个事务先提交的。当A开始给B转的时候,A的余额已经不够用了。」———————-按道理,这个应该用selectforupdate从数据库里加载出来,但是repo其实不知道你要修改余额,所以加载的时候,不会用selectforudpate」———————-这个不是单个或者多个聚合的问题,而是由DDD所谓的不关心技术实现导致的技术问题。
orange19:59A的余额已经不够用了,这是个DDD关心的业务问题
反应慢20:00如何用业务手段去规避呢?
Simon20:00这个一般不用selectforupdate来锁记录,而是用冻结记录表来锁
Simon20:02例如,在余额表加一个字段叫冻结金额,转账前把转账金额写进去,用户可用余额=账面余额-冻结金额
orange20:02要有model,或几个model来解决这个问题。尤其是有并发性的。大体上是用最终一致性的业务手段来解决
Simon20:02这样用户就不会过度扣费
orange20:04可能是请求扣费->扣费成功(或扣费失败)这样的异步业务手段
Simon20:05updatefrozen_amount=frozen_amount+$transfer_amountwhereid=$id
Simon20:05不用selectforupdate,那个太重
反应慢20:09select,update,update,三步?
Simon20:101.增加冻结金额;2.转账,同一个事务里扣除冻结金额
Simon20:11查询可用余额的时候,永远返回账面余额减去冻结金额
阿斌哥20:11我理解的这其实是个技术问题,事务相关的技术问题。一旦到了考虑事务这种技术相关性时,repository的具体实现自然要加上forupdate。这跟DDD设计时不考虑具体技术并不冲突。
阿斌哥20:11当然,forupdate只是一种具体的技术方案,可能还有其他方案
orange20:12DDD持久化的SQL实现只用最朴素的那几种操作,最复杂的也就是聚合的级联SQL
orange20:12聚合的级联SQL也有orm帮你代劳
反应慢20:12updatetsetfrozen=$xwhereid=$idandfrozen=0;
反应慢20:12话说frozen之前,不应该检查一下吗
Simon20:13不要直接set,而是setx=x+…
Simon20:13可以检查,where条件一起写了
Simon20:14我只是举个例子,当然不能照搬
反应慢20:15有点懵。。。Asession查到的时候,余额为100,frozen是0,Bsession把Frozen更新成99之后,如果Frozen再加,就不够了啊
Simon20:16加的update语句做判断,如果不够就不加
Simon20:16update操作可以返回影响的数据行数
Simon20:17如果数据行=0说明操作不成功
Simon20:21要不就用事件溯源模式,开始转账插入一条扣款记录,如果需要回滚则插入一条冲正记录。查询可用余额需要把这些事件加起来计算
orange20:21A100块,A请求转10块->B,A90块,A请求转91块->C(拒绝,余额不足),A转B10块失败,A100块,A请求转91块->C,A9块
orange20:31推荐IDDD作者写的另一本书,和ActorModel相关的
反应慢20:32我比较喜欢actor模型,但是在常用的开发框架中,比较难实施。
反应慢20:34之前讨论过,使用Kafka等做消息路由,对相同id的操作总是会路由到相同线程上去,宏观上看是并行,单个来看是串行。
orange20:34java社区用actor,Rx之类的,js社区可以直接用node的原异步
反应慢20:35主要是集群模式下要好使
反应慢20:35rx之类的,单机还可以
反应慢20:36要做请求路由
orange20:36IDDD作者好像书里说过,其实所有的业务都是异步的。
反应慢20:37如果微观粒度下是串行的,压根就不会有并发问题。
反应慢20:38但是思想是好的,日常用的技术,没有与之对应的落地方案。
orange20:39你说的是量子物理的看法?
orange20:39咿?Actortm就是超量子物理的!
反应慢20:39对同一个actor的操作,由于有mailbox,所以actor内部其实是串行的
orange20:40是的
反应慢20:42所以我在想,如果Kafka有100个分区,只有100个线程去做并发,相同Key的消息总是会落到相同的分区上,由此,相同Key的数据都会被同一个线程处理。对于这个Key来说,它就是串行的,和Actor一样
orange20:44如果微服务是actor的话,kafka就是actor的消息通道
反应慢20:44但问题是这个并行度似乎有点低,还有就是Kafka重平衡的时候比较麻烦,整个集群就停下来了。
orange20:44所以我用rabbitmq
orange20:45kafka大部分功能不是为了消息通道
wingyiu20:49「反应慢:A给B转账,从Repo里把A账户加载出来,检查一下A的余额。发现够转。于此同时,A还在给C转,A->C这个事务先提交的。当A开始给B转的时候,A的余额已经不够用了。」———————-saga保证ACD,不保证隔离,也有补救办法,那书里有写
orange20:50其实还是社区技术成熟度的问题。如果量子技术成熟廉价了,谁还会用经典物理去解决工程问题呢?
orange20:51dataconsistency,systemavailability,andtolerancetonetworkpartition—onlytwocanbeachievedatanygiventime。
反应慢20:51**@wingyiu  其实是I引发的问题[呲牙]

秦金卫(kimmking)01:02冻结可以内存化
吴林峰13:00class和object之间不知如何选,两个虽然都可以画出关系图,但使用上有啥讲究没?
吴林峰13:00今天在尝试设计聚合,发现
吴林峰13:00朱永光13:12什么设计工具?
吴林峰13:13EnterpriseArchitect
吴林峰13:14每个工具都有类图和对象图
星沙明明13:15类是抽象的,对象是实例化的。类只有一个统一的名字,对象各有各的名字
吴林峰13:16主要是体现出来的图都是一样的,所以纠结这儿
吴林峰13:16都有名称、属性、操作
溪源More13:19class和object是不同的UML图里面的东西吧
谢炀13:20object像是时序图得
星沙明明13:23object的后面可以跟名字
星沙明明13:24可以多个对象画在一起相互协作
吴林峰13:25嗯,那还是画对象图更适合建模的第一步
谢炀13:26一般是用例图开始吧
谢炀13:27人工流程机器流程都画出来,在分析
吴林峰13:31从用例图开始+一些对象协作
吴林峰15:18有谁知道这种虚线如何搞出来?
张逸15:32我是用OmniGraffle画出来的
JAMES🦁15:32draw.io
吴林峰15:38好的,我研究下,EnterpriseArchitect只能画椭圆
吴林峰15:38不好控制
吴林峰15:38吴林峰18:38权限,菜单,角色,员工、帐号这几个你们怎么聚合
antz-H18:42现在发现,不要一拿到项目业务就是什么什么哪个是聚合根,哪个是实体
antz-H18:42否则都会很麻烦