第7周
2020年02月24日 - 2020年03月01日

编辑|子鱼

#问答与讨论

1.在看.net下一个框架abp,文档中领域服务可以被其他领域服务所调用,这和张老师提到的避免领域服务间的相互调用感觉冲突啦[捂脸]

[因即果,果求因。]

DDD倚天剑

➤ 问题解答
三碗猪脚19:55我是觉得领域服务之间不能互调所有需要点参数都在appservice调用domainService的时候以传参的方式给他这样就算说以后服务变动我有个appservice拆出去了也不会影响到主要的逻辑
Antony20:09「因即果,果求因。:在看.net下一个框架abp,文档中领域服务可以被其他领域服务所调用,这和张老师提到的避免领域服务间的相互调用感觉冲突啦[捂脸]」———————-abp是伪ddd,这个作者开篇就表明了
Antony20:09借鉴了ddd
因即果,果求因。20:14是的,所以在用时候我还是尽量遵从abp框架,不严格遵循ddd
moonlight23:04「因即果,果求因。:是的,所以在用时候我还是尽量遵从abp框架,不严格遵循ddd」———————-应该反过来,abp允许你这样做,但你可以不这样做
moonlight23:05原则上同级的东西不应该相互调用,如应用服务调用应用服务,领域服务调用领域服务,聚合调用聚合。
haxe09:32「杜小非:原则上同级的东西不应该相互调用,如应用服务调用应用服务,领域服务调用领域服务,聚合调用聚合。」———————-同级调用有什么坏处?
moonlight15:42举个例子,领域服务调用领域服务,出现这种情况通常暗示了业务逻辑复用在领域服务层而不是聚合内,换种说法就是业务逻辑暴露到了领域服务层。
moonlight15:43业务逻辑的复用,应该是基于聚合本身,而不是领域服务。如果是在领域服务则通常代表了存在面向过程的代码。
haxe15:50有道理,但是应用服务和应用服务的相互调用为何禁止呢
haxe15:52一个单体应用内,订单服务如何调库存服务或其他的,如果禁止应用服务互相调用的话
DDZ15:57事件解耦
haxe15:58经典ddd实现
haxe15:59先不上事件
LenFon16:05干嘛要这么局限
haxe16:06追根溯源呀
LenFon16:07ddd后续增加了领域事件
moonlight16:08「haxe:有道理,但是应用服务和应用服务的相互调用为何禁止呢」———————-应用服务内是一个BC,BC间应该通过集成事件来相互通知而不是调用
haxe16:11那么下单查库存的操作呢
寒雨冰16:16两个微服务之间不互相调用吗?
moonlight16:18在这里分两种:1、事件溯源:先发一个命令给库存的聚合让其预扣,预扣成功则产生领域事件,eventhandler订阅到这个事件后更新订单状态为下单成功;2、传统DDD:(1)订单BC内存在库存的冗余数据,在领域服务里先判断后处理;(2)这是我们当前的做法但不是很好:订单BC的应用服务层请求库存BC的契约判断是否能够下单。但这种方式代表了业务逻辑甚至暴露在应用服务层。
moonlight16:19我们也曾经考虑过将这个逻辑放到领域服务层来做,但感觉上有点奇怪
moonlight16:19这相当于A服务的领域层,依赖了B服务应用层的契约
haxe16:21库存bc的契约是指的哪个层呢
moonlight16:21其实是一个技术实现,将应用服务层的方法暴露为webapi而已
haxe16:22其实只要使用接口完成隔离,理论上解耦就到这为止了
moonlight16:22应该说是解耦的开始
haxe16:24简单表述一下[表情]把较为复杂的逻辑模块封好在领域层以内,到了应用层有所耦合,问题应该不大吧

2.我现在有点疑惑,领域事件的发布是在领域层发布还是在应用层发布,有没有什么讲究?

[阿斌哥]

DDD少林派

➤ 问题解答
刘杰13:09我们是事件随着聚合根的更新存在本地事件表保持事务一致性,通过基础设施层发送。
阿斌哥13:16大体的思路看懂了,但是还有几个具体的疑问:问题一:领域事件存本地事件表是在哪一层做的?更新聚合的方法返回领域事件?然后在应用服务或者领域服务中调用事件的repository进行持久化?
阿斌哥13:17问题二:通过基础设施层发送,是专门有组件去轮询事件表并发送出去?具体是用的是哪些组件呢?
阿斌哥13:17@刘杰还望指点一下
刘杰13:461.我们是在领域服务里做的(事务在command里)。2.不是轮训,而是通过监听数据库的binlog+轮训对账,保证可靠消息。
阿斌哥13:49“事务在command里”怎么理解?
刘杰13:51我们用了cqrs
阿斌哥13:55轮询对账单目的是为了防止监听到的binlog消息处理时失败的场景是吗?
阿斌哥13:56准确的说应该不是“防止”,而是“补偿”
刘杰14:00是的
lincoln14:46请教下:六边形架构的端口+适配器模式。落地到代码上是这样理解么?适配器:对应适配器类端口:对应具体类里面的方法
远远鱼15:42端口是和某个具体的适配器没有关系,位于应用核心里的东西,一般是接口
远远鱼15:42lincoln15:50那就是:端口是内六边形对外提供的一个接口,用来对接适配器。已达到内、外两个六边形交互信息。
远远鱼15:57
远远鱼15:59IDDD里认为“HTTP是端口”,这让我困惑了很久。我看了很多资料,认为IDDD里的说法是有问题的。http://www.dossier-andreas.net/software_architecture/ports_and_adapters.htmlhttps://web.archive.org/web/20180822100852/alistair.cockburn.us/Hexagonal+architecture
远远鱼16:06https://www.jianshu.com/p/f39f4537857e**

3.请教一下,如果订单域,存在订单聚合,商品快照实体,商品快照明细,在订单聚合根中应该持有快照实体中的ID还是Entity?

[孙君]

DDD少林派

➤ 问题解答
孙君11:01我看书中规则是,持有ID如果这种情况,对快照实体中行为的调用,通过订单聚合根怎么执行?
炙炙炙炙炙炙云深11:07我们实际落了一份商品快照的值对象,以json格式存入订单表的一个字段中
炙炙炙炙炙炙云深11:08主要是为了订单详情,可以更快的展示商品数据。
炙炙炙炙炙炙云深11:09代码层面上,订单聚合分为主订单实体和子订单实体列表,子订单实体存在一个值对象是商品信息,然后落库和读取做转换
孙君11:10主订单聚合持有的事子订单实体的ID还是对象?
lincoln11:14那这个商品快照的json字段不小吧?
阿斌哥11:17同一个聚合内的持有对象,不同聚合之间持有聚合根ID
甘泉11:20**存json字段应该是为了使用关系型数据库,但懒得做模式化。

一位热心的市民11:29
炙炙炙炙炙炙云深11:31只存了展示信息,暂时所有数据不超过512,字段实际是1024

孙君11:41加载订单聚合,是要把订单,商品快照一起加载内存?
孙君11:42我们的商品快照比较大,一般都100条数据左右,好多人接受不了这个
刘杰11:42请问你们在更新一个字段的场景,也会对整个聚合根进行更新吗
阿斌哥11:55商品快照里面都有些啥啊?作为订单聚合,需要用到的商品信息应该没几个字段吧,最重要的就是商品编码。所以就算有100条数据,也没占多少内存。其他的商品信息是不是可以考虑不要放到这个所谓的快照里面,你放到快照里面,目的应该只是为了展示吧,如果只是为了展示的话,就不需要经过领域层加载数据了,直接查出来展示一下就行。
孙君12:00一些基础sku与服务sku执行订单下商品变更的逻辑
孙君12:01sku:描述,梳数量,名称,类别等内容
孙君12:01算了一下,大概100多K
yanhua13:13抛开查询和报表的需求,订单聚合可能都不需要商品快照
林杰13:24可以考虑,将快照单独一个上下文
炙炙炙炙炙炙云深13:31@孙君 我们对商品信息,实际只存下了订单详情和订单列表中需要显示的数据。其余的并不存储[捂脸]
炙炙炙炙炙炙云深13:32快照没必要太过量存储吧。真正详细信息还是需要商品域去提供
Zpl13:35事实上需要快照
Zpl13:35商品
Zpl13:35这是凭证的一部分
Zpl13:36跟ddd没关系,即使普通的设计,也会快照部分信息
孙君13:36对的
Zpl13:42基础属性、销售属性、其他属性,销售属性无遗是要快照的,基础属性看内部怎么划分吧,也不是所有都要,其他属性这些不用快照,个人看法
孙君13:46嗯比如往订单中增配商品的场景:1、获取订单聚合2、判断增配的sku是否已经存在,数量变化3、持久化第一步加载订单聚合,包含其中的商品快照?100K左右
孙君13:46主要是这种场景,ddd的处理逻辑
Zpl13:53@孙君 这种场景还把orderitem当作order聚合内实体集合的,不是很合适
Zpl13:57或者是增配的是新订单
孙君14:00嗯因为之前的设计问题,履约的对象不是合同而是订单,导致增配的逻辑目前都是在订单上
Zpl14:08增加子订单?
孙君14:19嗯增加订单项
yanhua14:19OrderItem上只有最必要的信息(SKUID,单价等)。商品快照可以是另外一个聚合根。查询、报表的时候可以聚合。如果从订单往下生成生产拣货单、包裹配送单等,这些逻辑和订单无关,是另外的聚合根或领域服务。就订单自己的业务逻辑来看,基本不会需要商品快照上的属性。
忘却录音14:33你们说的商品快照是咋实现的
BHY_Y15:42BHY_Y15:42这样不会有性能上的问题吗
远远鱼15:51可以有个近期快照,再遍历快照之后的事件

4.请教大家个问题,对于有多个复杂的查询场景,是不是用CQRS单独建一个读模型和读接口比较好。比如XXQueryService,这个service放到应用层。里面直接调用缓存和mybatismapper来获取然后组装数据?跳过领域层。或者大家有好的DDD+CQRS的实战文章也可以给推荐下哈。

[lincoln]

DDD少林派

➤ 问题解答
远远鱼11:32**关于CQRS,这篇应该很全面了:后端开发实践系列之四——简单可用的CQRS编码实践https://insights.thoughtworks.cn/https-insights-thoughtworks-cn-backend-development-cqrs/

**

5.还没来的及看实战篇,直接去github看了代码:有两个疑问,各位怎么看:

[lincoln]

DDD少林派

➤ 问题解答
image.png
lincoln17:29阿斌哥17:54关于第一个疑问我的观点:如果是用Mybatis来实现的话,只需要再搞个Mapper.xml文件就算是实现了,你再搞个RepositoryImpl,其实还是个interface,没必要吧。如果你是想通过注解的方式来实现那些SQL,再搞个Impl应该也是可以的,毕竟SQL的注解都加在Repository上面,确实难看。如果只是在Repository这个接口上加了个@Mapper注解,SQL写在Mapper.xml里面的话,就没必要再搞个Impl了,接口上只是多了一个注解,可以接受。
炙炙炙炙炙炙云深17:57我的看法是Repository中,数据来源不仅仅是数据库,缓存,其他服务接口都可以作为数据源。
阿斌哥17:57关于第二个疑问我的观点:如果实体类跟数据库中的字段非常类似,直接用也挺好,如果差别比较大,我个人是建议再加一层。免得自己搞一堆typeHandler,以及处理各种复杂的对象关系映射。
炙炙炙炙炙炙云深17:57而且mybitis中返货的是数据库po对象,Repository返回的是实体对象,这里还有一个转换问题。。
炙炙炙炙炙炙云深17:59po和entity是否共用,是看你怎么进行开发吧。如果建模设计和数据库设计由不同的人同步进行,必然无法保证两者相同。。再举个例子,比如我只能在订单待支付的时候,才能通过超时关单实现订单状态变更为已支付,一模一样的po和entity很难实现这种功能。
二两豆腐18:03我们在做的时候通常的情况下会做一层转换,entity转为domian
炙炙炙炙炙炙云深18:10说到转换。。强烈推荐mapstruct。
花雾21:53**Mapstrut推荐加一

6.这个问题大家怎么看,如下图所示:

image.png
[lincoln]

DDD少林派

➤ 问题解答
lincoln18:04lincoln18:04疑问就是:图中红颜色的字
lincoln18:05第一个图全,能看到包名
阿斌哥18:13这个NominationRequest是应用层定义的,appService并没有去依赖北向网关的什么,反而是北向网关要去依赖这个NominationRequest
阿斌哥18:15你北向网关定义的参数可以不是NominationRequest,但是转成NominationRequest还是北向网关的职责。话说回来,参数就用NominationRequest,转换的工作交给SpringBoot框架来做不也挺好?
lincoln18:20嗯,大家写代码的时候。比如dubbo接口的入参,会渗透到appService么?
阿斌哥18:22写代码的时候是先写appService,再去写北向网关,所以谁依赖谁,很清楚。不存在渗透的问题
阿斌哥18:38Bob大叔的《架构整洁之道》推荐开发人员都读一读,里面的很多观点让我受益匪浅。
@nichole18:52@阿斌哥我也很喜欢这本书,整洁架构的最内核就是领域模型,不过在领域层、服务层确认需求分析的正确性,好像有点难。
阿斌哥19:08“在领域层、服务层确认需求分析的正确性,好像有点难”,我不是很明白你这句话的意思,一旦从业务那里获取了正确的需求,领域层和应用层就能确认这个正确性吧,毕竟业务逻辑都在这里面。还是说你的意思是从业务那里获取正确的需求这件事本身就很难?@@nichole
飞邪19:25两个分层细节问题想问问大家的实践经验:1.应用服务除了能访问领域服务之外,有没有约束不能访问根实体也就是聚合、仓库?我看到有人说应用服务最好只调用领域服务,为了把领域逻辑都锁在领域层,因为调仓库需要组装校验参数等这些也是业务逻辑2.领域事件发送位置,推荐在根实体里发?还是领域服务里?应用服务里?有人推荐说应用服务里发,理由是因为由应用层启动事务,这样保证发送代码一定会跑在事务里
阿斌哥18:12大家有没有发现,虽然都是网关,一个北向网关一个南向网关,有南向网关的抽象,但是却没听张老师说过北向网关的抽象,这个怎么理解?[呲牙]
小浩子18:21北向网关是开放主机服务,面向调用方的
lincoln18:22北向网关是OHS(比如controller),不会被别的层依赖。所以不需要抽象。
冯炜18:26我觉得就算抽象也是对ui偏向抽象了
飞邪18:27不需要被依赖就不需要抽象,这个角度讲的通。或者说像rpc,抽象出来的接口暴露在订阅一方了。
lincoln16:01这里的gateway和其它层,算不算双向依赖?
lincoln16:02远远鱼16:37domain应该不依赖于其他层
阿斌哥16:38**不要把包名当作层,java语言里面没有层这个概念。interfaces里面是南向网关的抽象,也可以不把interface这个包放gateway包里面,不管放哪都掩盖不了它是南向网关的抽象层这一事实。应用层不应该直接依赖南向网关的实现,应用层和南向网关的实现都应该依赖于抽象。

7.大家有没有DDD涉及增删改查的代码示例,对比学习下,多谢

[H2O]

DDD屠龙刀

➤ 问题解
朱永光13:10DDD没有crud吧……
Paul13:11DDD没有crud那它怎么操作数据库呢
吴林峰13:15吴林峰13:15用ddd思想划出上下文限界后,不同限界文用不同的代码模型,微软的开源项目已给出了实践,该crud的就crud
吴林峰13:16别把简单的上下文也搞成ddd
霍尔顿13:16https://github.com/ardalis/CleanArchitecture
JAMES🦁13:21是聚合状态的更新与持久化问题
吴林峰18:25实现仓储中的xxDbContext能理解成和限界上下文一一对应的吗

8.有个问题想请教下群里的各位大神,在一个大型的软件研发部门,部门所负责的各类项目toBtoC,内部系统,管理系统等等大大小小的大概有40个左右,那么想要建立一个共通性组织,负责这些项目中产品,技术,测试,运维,基础资源中的设计与标准化,并整体把控软件研发的品质。那么这样的一个组织架构该如何组建呢?

[sunny]

DDD屠龙刀

➤ 问题解
阿斌哥12:25这题超纲了[呲牙]
sunny12:27有没有公司内部关于这样的职能团队的组织架构,可以给启发一下的。
.12:27这题超纲了[呲牙]
sunny12:29我有一些自己的设想,需要帮忙印证下。
Liiang12:36你的描述像是要建立中台组织架构[疑问][疑问]
Bill12:38sunny13:05是的
sunny13:06其实就是需要按照中台组织架构的思想来设计
任富飞13:13老板要是愿意出钱,早就升级自己的平台了。
溪源More13:19这是PMO么
李鸿翼13:29@sunny  技术委员会
sunny13:30PMO偏管理层面的
sunny13:30这个职能是偏落实实施,可以认为是技术委员会。
JAMES🦁13:33共享平台事业部
华谋学府咨询-殷志华13:39感觉跟Cmmi里面的推进办组织有点接近
sunny13:43有参考的文档或样例吗
薄军13:51itilv4落地呗
kevin13:53itil偏运维吧
溪源More13:54这偏管理层面,有点像PMO。中台更像是干事的
华谋学府咨询-殷志华13:56@sunny  大纲,供参考

9.如果一个值对象Address在多个聚合里面都要用,是在每个聚合中重复创建一个,还是放到哪儿共享?

[吴林峰]

DDD屠龙刀

➤ 问题解
反应慢18:29你的值对象会重写equals和hashcode方法吗?
反应慢18:30会将里面的省市县乡字段设置成final的吗
X18:41final?final
X18:42很少把它们弄成final[尴尬]
熊节JeffXiong18:42所有字段都应该是final的
熊节JeffXiong18:42所有对象都应该是immutable的
X18:43实体也是吗?
阿斌哥18:43实体肯定不是啊[尴尬]
熊节JeffXiong18:45实体是mutable对象也只是因为框架的局限性而已
反应慢18:45其实理想情况下,修改字段应该是consta={x:’y’,…b}
反应慢18:47不得不说,用过解构赋值之后,再写一堆set,简直要吐
熊节JeffXiong18:47exactly
X18:48我也一直想去掉getset。。就是领域对与po互转有点累直接很多工具拷贝依赖getset。。
熊节JeffXiong18:49对,很多时候都是工具的局限
吴林峰20:13最佳实践是什么[强]
吴林峰20:16一大堆值对象重复写
吴林峰20:16尊重聚合,聚合第一,重用第二?
茫茫大士09:16「X:我也一直想去掉getset。。就是领域对与po互转有点累直接很多工具拷贝依赖getset。。」———————-有插件,allGenerateSetter,好像叫这个,选中对象一键生成所有set,往里面放值就好了,
X09:19嗯嗯。。不是讨厌生成。。主要是无奈于他们破坏封装性又不得不加。
antz-H09:23可以尝试把构造器的入参数写全
X09:32这样只解决了构建问题,还需要拆解开获取属性转po呢
antz-H09:32你的po还是要getset呀
X09:33不一样。。po也不做逻辑处理
antz-H09:33主要领域模型不要set,用更有业务含义的词表示
反应慢09:33意思就是先取出来,然后都作为构造参数传进去
X09:34再深入想一下呢。。没那么简单吧。。不然也不会大家都采用折中的方式了
antz-H09:34trytry
antz-H09:35会有suprise
X09:35不用set没问题啊
X09:36有构造函数就行。。说的是get问题
antz-H09:36get,直接属性名
antz-H09:36替换
X09:38行吧。。不是形式的问题,是封装性为何要破坏的问题。。是业务逻辑需要吗?还是技术框架限制?
卢伟标11:20「X:行吧。。不是形式的问题,是封装性为何要破坏的问题。。是业务逻辑需要吗?还是技术框架限制?」———————-以前想过entity.toJson()/.toXml()/.toMysqlTableModel(),但是又引入了依赖。一个对象如果一直呆在内存中,不用写出到某个输出地,就没有这种转换xxO的烦恼了。对象封装性与序列化二者之间不可协调的?
X11:29@卢伟标-爱车小屋-东莞嗯嗯。。理解。。试过一种方式让entity和vo继承domainObject也是增加了依赖不过是反向依赖
X11:30**不过domainObj要提供protected权限的属性获取方法

10.架构师如果偏技术的话,那对应的架构管理是什么岗位或者角色?

[kevin]

DDD屠龙刀

➤ 问题解
finikes23:23架构=技术;架构管理=架构+管理=技术+管理=技术管理[偷笑]
Hank23:29「维益-kevin:架构师如果偏技术的话,那对应的架构管理是什么岗位或者角色?」———————-架构本身就是技术活吧,架构管理,这个是不是应该根据企业的组织来看,如果有架构组就可以叫架构组或部门,就是架构经理,如果团队较小架构师还要承担管理职业可能就叫技术总监,或技术负责人了。
WalesKuo23:35业务架构师为公司制定业务蓝图,业务规划。技术架构师为公司制定技术体系,建设技术体系。应用架构师在业务蓝图中的某一个领域中进行技术实施。
朱永光23:50togaf了解一下
Hank23:54「朱永光:togaf了解一下」———————-赞成,这个框架从各个阐述的比较详细
Hank00:00所以建议从问题的根本出发,是想解决什么问题,然后再针对性的设计对应的方案[表情]
JAMES🦁00:01系统架构师
GG.Leung[表情]00:15**@维益-kevin 请问一下哪里有togaf的资料可以指引下吗

11.请问有没有推荐的开源java版的事件驱动框架可以参考的?

[sunny]

DDD屠龙刀

➤ 问题解
反应慢22:25vertx?
反应慢22:30Spring也有事件驱动
秦金卫(kimmking)22:30webflux
秦金卫(kimmking)22:30底层都是reacticex
反应慢22:31webflux属于响应式吧
反应慢22:31rxjava之类的[捂脸]
sunny22:38看了一些资料,有个不太成熟的想法,是否可以搞一个通用的支持事件分发处理统一封装,给使用者提供统一的方式让事件的分发处理可以在jvm内(选择同步或异步),jvm间(同步,异步,消息队列),而不需关系内部事件的传递机制。
反应慢22:39camel
sunny22:39反应慢22:39eventbus
sunny22:40eventbus没有看到有好用的开源实现啊
sunny22:41对,就是eventbus的基本思想。
反应慢22:42我最近在看企业集成模式
秦金卫(kimmking)22:42guava里有个轻量级的
反应慢22:43看你画的图,可以找下这一方向的实现
秦金卫(kimmking)22:43eip的话
反应慢22:43比如camel
秦金卫(kimmking)22:43camelspring-integration
反应慢22:43
秦金卫(kimmking)22:43muleservicemix里也有一套自己的
sunny22:44谢谢
秦金卫(kimmking)22:44你可以先看一下seda
秦金卫(kimmking)22:44再看eip
sunny22:45刚刚发这些有些看过,大部分都没有把底层的事件分发机制是jvm还是jvm间,支持的消息队列这些给屏蔽,并统一成一致的使用
秦金卫(kimmking)22:46vertx支持jvm间eventbus
任富飞22:46hazelcast,网格计算?
秦金卫(kimmking)22:47vertx的api也都是两套,同步和异步的
sunny22:47支持jvm间替换成消息队列吗?
秦金卫(kimmking)22:47走消息队列太慢了
sunny22:47可配置的,可指定。
反应慢22:48vertx是构建在netty之上的吧?
秦金卫(kimmking)22:48
秦金卫(kimmking)22:48eventbus的吞吐量非常大
秦金卫(kimmking)22:49业务数据可以用mq,因为量级小
秦金卫(kimmking)22:49异步框架的event量级至少比业务处理量级高1-2个
sunny22:50要是有个统一高层模型或接口。使用者可以自行实现或者采用现场的可以作为时间通道的媒介就好了。
秦金卫(kimmking)22:50你说的这个很简单
秦金卫(kimmking)22:50封装一层就成
sunny22:52还是有很多问题要考虑的
反应慢22:52tdd起来[吃瓜]
反应慢22:53边做边考虑
秦金卫(kimmking)22:53对了,akka也可以夸jvm的eventbus
sunny22:53封装出来的东西要项目上别人好用,不然就没啥意义了。
秦金卫(kimmking)22:53从erlang来的,erlang本来就有
反应慢22:53一次性考虑不出来就荒废了
sunny22:54是啊,先搞个设计出来。
秦金卫(kimmking)22:55加油
sunny22:56把大伙刚刚发的这些有这种思想的框架,先研究一下。
wingyiu01:37**「sunny:看了一些资料,有个不太成熟的想法,是否可以搞一个通用的支持事件分发处理统一封装,给使用者提供统一的方式让事件的分发处理可以在jvm内(选择同步或异步),jvm间(同步,异步,消息队列),而不需关系内部事件的传递机制。」———————-zeromq?