父文章 如何成为一名架构师,架构师成长之路_个人渣记录仅为自己搜索用的博客-CSDN博客_架构师成长之路

[落地版]领域驱动落地

[理论版]领域驱动设计DDD 代码框架 · 语雀

子文章 如何写可维护的代码 - 万物ddd ddd primitive . 封装,对象来实现可维护代码._个人渣记录仅为自己搜索用的博客-CSDN博客

ddd 在业务域的落地- 无领域术语下如何给领域实体的命名 hibernate区别 ,中台_个人渣记录仅为自己搜索用的博客-CSDN博客

一次重要的ddd重构 (可维护,重复读,可阅读)一次ddd重构记录_个人渣记录仅为自己搜索用的博客-CSDN博客

目录

父文章 如何成为一名架构师,架构师成长之路_个人渣记录仅为自己搜索用的博客-CSDN博客_架构师成长之路

ddd 面向对象好处

难点

取中 - 不完全面向对象.

对小白一样

持续的重构

架构变迁/重构变迁

原则

1. 面向对象,代码行不会任何变化. 类会变多,每个类的行数会变少. 接口会变多. 接口隔离.

2. 要有领域类,要封装

2.1 所有业务支付,订单,物流通用的部分:

同属异域- 粒度  分拆的逻辑.

2.2 新增字段, 新增类还是新增方法?

2.1 带来的好处

2.2 领域类的使用带来的变化?

2.3  DDD事务充血和内存面向对象的区别

2.4 充血多层的缺点.

3. 领域类哪些需要序列化,持久化, 和持久化类的区别?

3.4 领域类和持久化类的区别?

4. 持久化带来的问题

4.1 多个业务持久成一张表. 效率和领域成为矛盾.

4.2 封装和fastjson/jackson的序列化反序列化违背. 故不要用fastjson去直接序列化, 手动拉平后序列化.

4.3  ext中的value bean不能随意重构.

4.4 反序列化对性能的影响.

4.5 对存在ext中的bean的代码的继承,组合,封装重构 一定要考虑 序列化和反序列化的重写.

4.6 重构后序列化几种新老兼容的case.

4.5.6 序列化兼容性

要求

5. 洋葱模型和maven jar依赖

6. 充血意味着重构,重构意味着需要字段级别的测试. 需要录制和回放框架

DDD的核心是

过程式DDD和充血式DDD最大区别是

代码维护的重要点:

定义

概念合集

外文的ddd和cqrs CQRS, Event Sourcing, and Domain Driven Design FAQ

cqrs 和cqs区别

Command and Query Responsibility Segregation (CQRS) pattern

下面是四种伟大的程序架构:

2.DCI架构

3.DDD/CQRS

4.六边形架构

最近技术对ddd的影响

另外一篇文章可读 http://www.jdon.com/44491


学习成本高 , 但执行成本.

ddd 面向对象好处

1. 封装,职责单一. 两个方面. 1. 一个类里的代码内聚 2. 相同域业务要统一在类似的业务里,不要可以随意写在其他地方. 封装可以从编译层面确保这两点.  结合复杂度治理, 可以动态的拆分,上沉下浮. (非封装也能实现)

2. 可读,可维护 ,有封装后字段的调用可hiererarchy分层 . 例如 card payerCard ; trans 和 pay ,refund关系.

3. 培养能力思维, 而不是烟囱思维.

4. 写新的代码复用性, 知道去哪遍历方法,  可维护的另一个角度.

难点

1. 同属异域类的命名, 避免使用过程名词.  例如WhitelistUserEO PayUserEo 2. 使用了其他同属异域的EO

取中 - 不完全面向对象.

不同粒度的实体不再组合封装, 在service中组合.  层次过深. 例如: 舍弃 WhitelistUserEO 封装 whiteList

对小白一样

加字段, 加方法. 面向过程,不是新增类.

持续的重构

单测自动化是前提


架构变迁/重构变迁

1. 字段平铺 (内存态前后流程流转,持久化: 入参信息必存, 用户侧查询,前后流转, 大数据分析)

2.  封装. ( 基于流程分析, 领域概念离不开流程. )

2.1 同一个业务里同一个流程复用, 收付款方信息.

其他案例,详见ddd入门ddd 在业务域的落地- 无领域术语下如何给领域实体的命名 hibernate区别 ,中台_个人渣记录仅为自己搜索用的博客-CSDN博客

2.2 同一个业务不同流程复用. 提现和退款都需要操作 提现接口.

2.3 不同业务类似流程. (可能细节字段会不同)

例如

2.3.1 查询协议. 协议获取方式. 协议内容.

2.3.2 账户概念.

3. 流程三要素 . 状态机唯一性, 一个handler主目标状态必须唯一.( 状态拆成成两个字段除外.)

当前状态 - 事件 - handler. 必须要有个不同. 才能区别业务.  如果handler相同,那么久会在内部代码中出现if,else.

原则

1. 面向对象,代码行不会任何变化. 类会变多,每个类的行数会变少. 接口会变多. 接口隔离.

面向对象,好处是总是站在对象的角度上思考问题, 另外获取方法也是从对象上出发. 思考更直接.

接口有两种,一种是每个字段的get/set ,另外一种是EO字段的convert, getXxxEo. 域隔离.

还有一种是接口能力的封装,不同策略的实现. 要实现逻辑的分离, 不能在EO里实现策略类接口. 这样类里的逻辑会变大. 另外有些字段可能也只是暂存的. 例如ack阶段的pay字段保存的EO里.

2. 要有领域类,要封装

2.1 所有业务支付,订单,物流通用的部分:

面向多租户,多业务的配置框架. 有bizId 和 组件复用的场景码. 例如账户的配置可以在支付场景用中,也可以在转账场景中使用.

什么是领域类, 通过领域类可以实现本系统的能力,具体体现在对象关系和各个字段.  是对自己能力和下游能力的组合.  作用是承上启下. 承接上游各种业务, 封装下游各种能力.  这也是一个系统存在的意义. 领域类必须要封装,不提供get,set. 这样有助于,能力提炼,代码复用,应对不同业务时出现避免烟囱式代码. 使用软封装, 即属性包可见, 保持了封装,封装在包里,保持封装性的同时,也带来灵活性, 一方面逻辑代码可以脱离bean ,bean不至于太臃肿,同属异域(异域还是要通过maven模块进行隔离,不然就没有意义,互相都能编译看到,通过规范其实是限制不了的,同属异域增加各自的直接封装类进行很好的编译隔离),类分层,类爆炸可以少一点(不要担心类分层,超过3种互斥类型,就不要强制平铺了)(另外字段一样,但流程不一样,即状态不一样,也可以不平铺,分拆出来.上层维护多余的状态,底层状态机不变,但很难,会有参数会不同.),用方法来实现向对修改封闭,向扩展开放, 另外就是能支持get的call hierechay 分层,代码的可读性也加强了.

继承在设计,组合在代码实现。组合便于可hierarchy可评估,可维护,可阅读,避免覆载,等一系列继承可能的问题.

通过平铺可以确保在一层 ,

同属异域- 粒度  分拆的逻辑.

组合不要多层, 我最开始的实现是多层次的. EO不停的分化层不同的同属异域类.  哪怕是一个简单的. 同属异域的核心是你的方法粒度是什么粒度的.

同属异域要使用组合, get/set接口就必须要保留出来.包可见即可 或者 进行convert.

2.2 新增字段, 新增类还是新增方法?

如果不同的支付方式使用的是相同的字段. 新增方法.

如果新增字段,字段不同的业务都不同. 那么可以分化成新的对象. 本质上业务变大了,拆还是不拆.  上沉还下浮.

抽象父类entityHelper实现       spring里service,dao的注入.

DDD对于小白来说 ,就新增字段,新增方法即可. 根据代码复杂度框架来评价是否需要重构了.

领域实体有唯一id EO,值对象无唯一id VEO , 事件是短生命周期的实体.

《领域驱动设计》一书中并未给出领域事件的定义。因为该模型是在该书出版后才被提出。 当前对领域事件的定义:领域专家所关心的发生在领域中的一些事件。 将领域中所发生的活动建模成一系列的离散事件。每个事件都用领域对象来表 示……领域事件是领域模型的组成部分,表示领域中所发生的事情。[Evans, Ref, P-20] 一 from DDD领域驱动设计-领域事件(Domain Event)

有两种概念. 第一个是 外部facade进来, 调用模版框架, 创建的一个驱动事件, 包含了外部请求 . 第二部分 最终可以这个操作变成一个实体.

2.1 带来的好处

     2.1.1 . 提取系统能力,提高扩展性.

避免 我方request和 外部依赖的request直接在service处相连. 实现鲁棒性,可复用性,可扩展性

2.1.1 从边界角度看,同样如此, 无法应对变化.  新接业务,或者新来一个入口, 会导致 直接另起一个service 组装 新的request和外部依赖连接. 另外如果外部request变了. 大量代码也要变动,涉及到逻辑if,else处的代码.

2.1.2 从内部角度看, 自己能力(方法,if else)没有沉淀, 无法应对变化,  例如新的业务使用一个能力会导致另起service,直接烟囱式代码.

拥有自己的bean且无get,set后, 该bean不再是简单的持久化bean了, 而是领域类. 通过bean 承上启下 ,类和类之间是有层级的, 一旦封装后, 就会从能力的角度去看代码, 两个流程都要使用同一个类, 必然会抽象出能力, 而不是将外部类填充到里面去. 后续再有新的流程,就只需要bean之间的转换即可. 而不是烟囱式写代码. 例子 挂账退款/支付后退款. 内部逻辑是一样的. 而不在是基于suspendRefundOrder和 refundOrder去写代码.

    2.1.2 平铺的字段可以封装复用

, 例如 payer payee 的都平铺在bean里, payerInstId  payeeInstId 等等. 那么代 码写起来就很奇怪.

领域类本质是字段的封装 .

2.2 领域类的使用带来的变化?

第一次写的时候, 操作函数可能复用的是上游的request. 新的业务用的是新的对象. 会需要重构代码. 不然就要重写.

2.3  DDD事务充血和内存面向对象的区别

DDD事务充血和内存面向对象最大的区别是

1. 有事务执行逻辑. 而不是边判断边写入边组装. 判断先行,写入后行. 起事务之前必须先组装好对象.

2. EO 是要持久化的. 领域重构要考虑老数据如何反序列化. 或者保证序列化,保持不变.  原则,不能偷懒json. 必须逐个字段序列化和反序列化. 一旦json后,后面领域重构变成组合. 就需要手动转换成Map.设置进来,又变成了逐个字段.

因为自动化拉平序列化, 经过自动化拉平后. 可随意变动,变动后增加key的转换即可.  详见原则3的代码.

2.4 充血多层的缺点.

领域多层的设计导致字段移动,对json序列化的老数据反序列化也不友好.

如果没有自动化的拉平序列化能力,对持久化的数据反序列化应对领域重构变化非常不友好.

详见原则4的自动化拉平代码

3. 领域类哪些需要序列化,持久化, 和持久化类的区别?

需要持久化的数据可分为入参存储,回调流程需要的数据需要序列化, 线上业务读取,后台业务读取, 后台数据分析.

3.1 入参必须序列化. 有个干净的收单流程. 一切数据之源头,可重试的前提.

Question: 协议获取方式,协议领域类是否可以不用附属在订单类里,为了区分入参和其他参数的话?  这个领域类设计就有点奇怪.  因为协议获取方式即可以通过入参传入,也可以通过bizId从配置里获取. 都是领域的字段. 协议领域类如果在订单类里,这样订单类就会越来越臃肿.  本来的业务就那么多,臃肿没办法,把平铺字段拆分封装即可. DO简单即可.

3.2. 回调时需要的字段必须要序列化.

3.3 用户需要查看的, 线上业务读取,后台业务读取, 后台数据分析. 其他后台数据分析用的比较特殊,有些字段传给了下游,用一个下游的uniqueId即可获取. 理论上下游的业务都是我的业务. 通过自己需要能分析出来,如果不序列化,就要进行大量的join. 了解下游的设计.

实现

本质上每个领域字段都要和DO进行转换get,set. 即序列化和反序列化. 也有特殊的,可以图简单,使用json序列化,但 toDo, static fromDo 不是必须. 因为DO里的ext是String, 故toDo需要有个toDoHelp. 将每个bean的json设置到helpBean的ext里,然后统一序列化. EO可以保留ext, 用于保存一些不需要显示EO的字段. 偷懒,显示做好null判断即可,可以做到不用反序列化.

json的序列化,反序列化工具为啥必须使用Gson. 因为Gson对 充血模型友好, 是对属性的序列化,反序列化. 并且能支持 组合bean 默认是null 的逻辑. 遇到null ,会自动 new.

详见4里的推荐代码,支持接口,动态类. @DynamicField

3.4 领域类和持久化类的区别?

1. 比持久化类字段要多 其和普通持久化类的区别, 如果不持久化 后台统计分析类的数据的话 , 领 域类比持久化类要多很多.

2. 持久化类无逻辑. 领域类充满逻辑

4. 持久化带来的问题

4.1 多个业务持久成一张表. 效率和领域成为矛盾.

例如 operation表, 很多业务共用字段.  如果按照表来设计领域类.  就只有一个类,其他不一样的东西都放到extInfos 的key,value中.

解约成本的原因是 本来应该每个operation都要设计不同的表,但是偷懒变成了一张表, 虽然insert,update,query省去了工作量. 但是带来了日后的维护成本. 这个类巨大无比之后, 你去理解这个表的时候,就很苦难了. 这么多字段到底在哪用. 一个字段被n个业务场景使用 ,例如支付,退款. 你又会很懵逼.

所以坚持用不同的领域类来分化这个表.

baseExt里面,反序列化的时候, 不要把表ext的字段的所有值都放到baseEo的ext中. 要过滤掉.

反序列化的时候, 需要从map中删除已经反序列好的key.

不然 重复校验在update的toDo的时候,就会报错. 只能根据 isUpdate/ isInsert来实现. 会比较复杂.

public class ExtInfoHelpBean {/*** 整体作为key存在*/private String key;/*** 扩展属性*/private Map<String, String> extInfos = new HashMap<>();/*** Getter method for property <tt>extInfos</tt>.** @return property value of extInfos*/public String getExtInfoPropertiesString() {return MapUtil.map2String(extInfos);}/*** Getter method for property <tt>extInfos</tt>.** @return property value of extInfos*/public String getExtInfoJsonString() {return JsonConvertUtils.objectToStrNRN(extInfos);}/*** Setter method for property <tt>extInfos</tt>.** @param paramMap value to be assigned to property extInfos*/public void putAll(Map<String, String> paramMap) {for (String s : paramMap.keySet()) {//过滤掉null值.putToExtInfos(s, paramMap.get(s));}}/*** 过滤掉null值* @param key* @param value*/public void putToExtInfos(String key, String value) {if (value == null || key == null) {return;}if (extInfos.containsKey(key)){throw new InstTradeException(ErrorCode.SYSTEM_ERROR," key already exist key=" + key+",please new another ToDoHelpBean() and use toDoHelpBean.join(ToDoHelpBean)");}extInfos.put(key, String.valueOf(value));}/*** 过滤掉null值* @param key* @param value*/public void putObjectToExtInfos(String key, Object value) {if (value == null || key == null) {return;}if (extInfos.containsKey(key)){throw new InstTradeException(ErrorCode.SYSTEM_ERROR," key already exist key=" + key);}extInfos.put(key, String.valueOf(value));}}

4.2 封装和fastjson/jackson的序列化反序列化违背. 故不要用fastjson去直接序列化, 手动拉平后序列化.

对组合复杂Bean, 反序列化违反常理, 对新人不友好.

放弃fastjson/jackson 放弃原因: jackson不支持属性的序列化,反序列化,仅支持接口get/set的自动序列化反序列化, 必须手动设置注解.( 1.fastJson、JackJson以及Gson序列化对象与get、set以及对象属性之间的关系

2. 使用jackson对类的属性按要求序列化和反序列化@JsonDeserialize与@JsonSerialize

)

 问题1: nullpointException ,即反序列化跨层级set会抛空指针. 反序列化是延迟的. 只是保存,不关心反序列化. 后面流程也没有验证. 结果导致前台查询全挂了. 哪怕该流程,不需要获取该字段. 改bean内的字段.

  解决方案: 每个单测结束后都要调用get接口 order,trans.  Jackson反序列化的时候不会自动new . gson会自动new.

问题2: 数据丢失, 即反序列化的时候, 如果没有set接口, 反序列化失败. 然后再此保存的时候数据丢失. 问题到也不大. 后面要用到数据的时候,设置set即可.
        解决方案: 不需要. 对于ext value而言. 如果没有toDO 和fromDO 成对出现, 如果没有就不需要序列化. 不然最终还是会被覆盖掉. 除非 extItem 的对象应该是游离与trans的 实现懒惰加载,放置在Context中, 通过形参组合嵌套service, 但这违背了, .. 主EO里的所有字段都11对应.  fas是成对出现的.

问题3:  老数据无法反序列化. 即封装后,继承改组合后. 哪怕改了  Jackson/fastjson反序列化会抛异常, 容易出现老数据不兼容的坑,老数据有数据,新的没有,老数据反序列化不成功. 或者没有修改了一个流程,没有验证后续流程. 后续流程会反序列化,报错.

解决方案: 1. 手动拉平. ToDoHelp 里面有key,value 都是String. toDo  ,statis fromDo

2. 自动拉平

3. 不要去重构bean.

4.ext valueObject. 必须是简单value, 不能存在mayBeJson的字符串. 以{} [] 等结尾.

另外做好db ext 自动化验证, 有助于提醒老数据的兼容性.

Jackson放弃 ,原因:Jackson unwrapper 支持多层拉平( @JsonUnwrapped 以扁平的数据结构序列化/反序列化属性) 但jackson不支持属性的序列化,反序列化,仅支持接口get/set的自动序列化反序列化, 必须手动设置注解.( 对类的属性按要求序列化和反序列化@JsonDeserialize与@JsonSerialize)

4.3  ext中的value bean不能随意重构.

封装/组合/继承重构后序列化的结果就不一样了, 需要每次事务执行完进行DbCheck.

这就要求对领域类 封装/拆解的时候, 时刻要考虑老数据反序列化的兼容性.  对新人不友好.

解法1: 通过单测的ext字段进行校验.  但也有风险, 即多流程组合, 后面流程刚好把数据丢失了. db校验反而不管了. 出过线上事故. 原来json改成手动序列化 ext里的字段原来是json, json工具类序序列化列化是布尔, int, 但是手写序列化后就变成了String ,导致反序列化失败. 也因为原来数据没有反序列化,后续流程序列化的丢失( 嵌套EO且又是ext字段的, 有序列化, 但没有反序列化, 自动 ) , 单测无法验证出来.

1.1 每个流程执行完之后都需要验证DB字段. 这样就能避免.   - 全字段校验

1.2 然后平时采用soft模式, 这样每次review重点review update的文件什么, 兼容性是否有问题.  - 新老代码兼容性问题.  使用自动化工具 ,基于testng的自动化测试框架,自动化集成测试框架,自动本地集成测试框架,自动化单测框架_个人渣记录仅为自己搜索用的博客-CSDN博客_testng自动化测试框架

或者回归的时候, 新老代码各跑一遍,得到两个trace, 进行比对, 构造各种状态下的端到端. (更好, 不依赖人工review ,可以进行软校验.)

1.3 每次上线完,  soft模式跑通过后,执行录制模式. 进行覆盖. 合并到master代码上. 因为是纯单测模式,不需要上线发布.

解法2 (推荐 - 自动化 - 简单):  通过flatGson(见下面自研)进行序列化和反序列化. 你避免不了同学用普通的json序列化. 且一旦序列化后, 就无法兼容性修改回来. 必须要采用解法3来解决.
       解法3(兜底-工作量大-但实际上需要序列化的字段不多-因为很多字段只是为了统计序列化-业务上是下游使用):   1. DO中需要有对应valueBeanDO对应,每个字段都需要手动get,set. 这样确保编译层面能发现问题. 只要确保,序列化的时候extDo 需要整体null值判断即可. 这个到时可以正常单测验证出来. 2. 确保反序列化的时候extString备份到了领域类中即可.  只要一次即可. 如果没有备份,依然还是会丢失数据. ext字段只能构造传入. private put(key,value) , private  .

4.4 反序列化对性能的影响.

1. db数据读多少? 一般整个都读出来.

2. db ext中的复杂bean数据是否有必要反序列化?

对于持久化在ext中的bean就不能静态写死在类中. 需要动态化 set/get ,同时对 get- bean缓存拦截.   纯静态,没有懒惰序列化.就会导致性能下降很多. 类似fas.

4.5 对存在ext中的bean的代码的继承,组合,封装重构 一定要考虑 序列化和反序列化的重写.

这里除了手动set/get的字段,其他序列化成str变成ext里的value的字段就需要强制平铺拉平.  不然势必会导致序列化和反序列化复杂.   手动即可,因为真正需要持久化的数据不会特别多, 按需持久化. 写一个pom校验bean依赖要求, 不能依赖某个类. 把toJson封装成新的类toJson(Map<String,String>), 只能传入Map<String,String> ,通用的toJson(Object)不能使用, 启动期,单测期编译依赖检查.

hibernate的map序列化是比较简单的,还是需要手动将复杂的item手动变成map里的value,Hibernate映射json_dnc8371的博客

自研flatgson代码: 拉平的代码详见 最强拉平序列化( 反序列化,支持接口,动态类型. 性能也比较好. 前期就准备好 PathField .详见 FlatReflectionTypeAdapterFactoryTest

https://gitee.com/philcode/java-demo/settings#functionz

也备份在git_hub

, 灵感来源于 
GitHub - semplar2007/gson-flat: Object flattening support for Gson. Works through dirty reflection.

和 https://gitee.com/philcode/java-demo/tree/master/src/main/java/com/javedemo/gson/jsonAdapter​​​​​​

核心是解析后缓存的的List<Field> fieldPath path比较关键. 既能用于 read也能用于序列化.)

hibernate的map序列化就是笑话, 重构会导致无法兼容. https://www.baeldung.com/hibernate-persist-json-object

和 Hibernate JSON UserType: simple handling of JSON objects with Jackson – keep it httpsHibernate JSON UserType: simple handling of JSON objects with Jackson – keep it https

4.3.1 toDo, do类和toDoHelp类(内含map或者gson JsonElement ) 必须要是toDO的形参.

4.3.2 ignore 注解

4.3.3 static fromDo

4.6 重构后序列化几种新老兼容的case.

4.5.1 本来平铺改成bean封装 有些实在没法兼容的,就需要反序列化的时候手动设置. 例如本来平铺字段名是payerCardNo nameOfPayer.  现在变成了.

序列化的时候, 在形参中设置字段名转换规则,遇到 payer.CardNo 改成payerCardNo , payer.name改成nameOfPayer. 反序列化的时候,设置字段名转换规则反过来. 这样就能保证db内的数据保持一套了. 旧的odps分析也不用改动了. (因为不同名的封装后只能同名, 需要反序列化的时候, 设置字段名转换规则功能. 配置在反序列化的调用的形参中. 无法配置在类里,因为类已经不存在了.  )

4.5.2 新增字段同名冲突

1 外面有名称里,封装里又增加了同名. 报错. 不能同名,新的需改名

2 封装了已经有名称,外面又增加同名.报错,不能同名, 新的需改名

3 本来只有一个封装类,现在增加相同的一个封装类. 仅将新的类加前缀.

4.5.3 原来的bean被拆成两个bean. 或者字段被移到外部. 两个bean的融合序列化.

1. 如果在最上游,手动变成jsonElement后合并即可.

2 如果在内部,因为本来就是平铺了, 无所谓.

4.5.4 字段转换成另外的名字.

4.4.5 一拆为2,  增加handler 把两个字段进行判断融合成一个字段(确保老的大数据分析不用动). 另外保持拆分后的两个字段. 反序列化的时候,一个字段分拆成两个即可.

利用Gson

4.5.6 序列化兼容性

例如A机器序列化了新的对象. 但是B机器上获取到该数据进行反序列化(mq或者是定时任务 , 预发线上不隔离. 发起和回调. ). 导致不兼容修改. 本来新增字段应该是兼容的. 但是序列化,反序列化就是这个坑, 另外一方面也强调了懒惰序列化的好处. 本质上序列化的东西是丢失了信息的. 传统的新增字段能兼容是因为并没有丢失信息, 两边的其他字段的类都存在. 可以使用父子类, 也算是另外一种意义的上局部新增. 可以不序列化,返回null值 . 不使用就不抛空指针.

4.5.7  使用fastjson/Jackson 序列化后, 无法反序列化多层级的bean. 需要手动在构造函数里new处组合的对象.

另外如果缺少set接口, 反序列化后再保存,将丢失数据.

要求

1.  extString必须是 String key,String value. 减少重构带来的extString无法序列化的风险
                 2. 领域主类必须和DO字段11对应,可以封装. extItem中的valueItem 游离在外面, 放在Context中.

3. extItem 的toDo和fromDO 必须成对出现 . toDoHelpBean 必须也是 String key, String value . 对extItem 进行手动序列化和反序列化.

5. 洋葱模型和maven jar依赖

集成层依赖 领域层, 外部层. 同时领域层依赖外部层的requestBeanJar.  这样才可以将EO转换成外部的request. 不然就需要将EO里的字段get/set暴露出来. 但 领域层同时不能依赖外部的service和resultBeanJar . 矛盾的是,由于requestBeanJar和resultBeanJar一般都在jar

6. 充血意味着重构,重构意味着需要字段级别的测试. 需要录制和回放框架

基于testng的自动化测试框架,自动化集成测试框架,自动本地集成测试框架,自动化单测框架_个人渣记录仅为自己搜索用的博客-CSDN博客_基于testng框架

如何在编译层面控制序列化反序列化?

答:

1. 代码可以封装,组合 包括后续的重构. 但是序列化最好是拉平的flatMap.  而且一旦序列化后, 就要保持不变. 代码的重构不改变序列化的兼容性.

使用gson的toJsonTree 和ignore 将所有字段转换成一个map里或者DO里. JsonElement

关于兼容性的案例

一开始使用了继承,然后直接序列化. 后来重构成了组合. 那么toDo就要修改. fromDo也要修改.

2. 领域类的字段不能有默认值, 必须set/get. 特别关注 布尔型 ; 富领域类序列化到ext中, null值不设置到ext中. 这样就能保持ext的key不会被覆盖. 或者读取后反序列化失败. 多层级富对象需要有toDo的方法. 和 fromDo的static类. 如果默认对 富领域类初始化 new出来. 就会导致 第二个流程从数据库里读取出来,再序列化到DB中.会导致部分数据丢失.

3. 仅对指定字段序列化反序列化. 充血模型可以继续保持, 封装. 不暴露set/get

举例: 退款回调保存后, 收付卡信息丢失.

原因:

富对象直接json序列化到数据库中, 有些get需要ignore. 有些字段没有实现set接口.

有set接口,又很容易导致封装失败? 所以本质上是慎用json序列化反序列化. 而采用field序列化反序列化.  充血模型的序列化,可以使用 仅序列化制定注解字段的方案.

java rpc 反序列化 泛型 接口_个人渣记录仅为自己搜索用的博客-CSDN博客

不同json的黑名名单机制, 仅序列化指定字段,没有get的private字段_个人渣记录仅为自己搜索用的博客-CSDN博客

DDD的核心是

先创建好领域类,再操作. 再持久化. 而不是先操作,再去持久化.

流程思维在DDD中还是非常重要. 维护一个系统,先要了解整体的流程. 其次才是每个类的状态机. 关单这种流程不关心处于什么状态. 关单到领域类之间有很多逻辑. 所以不能直接从类状态机去强匹配系统.

流程的重要性状态机上, 任何程序的流转都是 当前状态 + 事件触发. 这里面分几种情况.

1.事件不同,状态不同,静态代码入口不同

2.事件不同,状态不同, 为了不写新的静态代码入口, 静态代码入口相同,内部就需要对事件进行if else. 典型的案例 状态机中间新增了其他挂起的中间状态,例如风控拒绝且待check. 对应的不再是入账,而是入内部户.

3.事件相同,状态不同,为了不写新的静态代码入口, 相同的静态代码入口. 内部就要根据不同的状态来进行if else. 特别是状态被拆分成两个字段的情况下, 就需要两个状态都继续流转. 例如上面说的挂起状态,继续退款和正常退款. 又或者上面说的挂起状态,继续入金和正常入金,还有出款方会不同的,继续入金是从内部户, 正常入金是从清算户.

过程式DDD和充血式DDD最大区别是

1. 方法是否写在类里.

代码维护的重要点:

1. 写的时候,找代码更简单 .通过封装不暴露下游的字段. 充血解决的是这个

2. 还可以组合来拆分/隔离让调用层次分层. 这个也很重要

但是不要用 组合+接口来替代继承. 这里的接口是返回组合的那个类 getBaseContext(). 暴露出来,仅给模版代码使用. (baseCase 传统的接口是父类的get都变成接口里的方法. 那和继承就没有区别了,callHierechay反而增加了工作量. )这个接口有白名单,只能哪几个模版类使用. 有专门的文件维护,依赖关系, 运行期编译检查.  分层是分层了但是第三层又复杂了. 如果是非充血的,还不如平铺和继承, 因为要复用势必是要接口化get/set.  就演变成如下图的糟糕情况. 表面是隔离/分层了.但实际上第二层又没有. 要么就是暴露 getBaseAccount()接口, 序列化的时候,会比较麻烦. 如何存储到ext中. 领域多层的设计导致字段移动,对json序列化的老数据反序列化也不友好.

对象的拆分/隔离基于几种: 1.流程 2.字段分组(2.1直接分 2.2抽象化分. 金额变成钱包. 钱包变抽象账户. 用于积分等加减. ). 3. 组合后具象化. 卡 -> 付款方卡,收款方卡. PayerCard{ Card card getCardNo(){return card.getCardNo()} }  PayeeCard{ Card card}..不同的卡用于不同的逻辑.

隔离有两种方法: 1.转换法, 每个流程各自在自己的包下面创建领域类, 字段仅包含自己有的,多层级的类,例如订单,付款方卡,收款方卡, 在类初始化阶段就生成. 2.组合法. 特别是多层级如何隔离比较难.   需要懒惰式生成. 每次get付款方卡的时候new 一个本包下的PayerCard类.

先总后分. 先根据系统的大流程去隔离领域类. 再每个流程内部自己看是否需要再次隔离. 还是使用组合法来操作.

看字段的使用,只需要抓最全的根类即可,这个类上有各个流程持久化的字段,最全. 可以从领域类的角度统揽全貌. 也可以从不同的流程类上去看set地方.

充血DDD包含的VO也可以借鉴这种思路.

领域类里的字段是持久化的,不持久化的就放置到上下文中. 哪些字段要持久化1.后续流程要用,例如付款方账号,例如用户id,用于差错到余额 2.可能是方便定位,方便统计分析,不想再join配置表,join上游系统获取传参,join下游系统获取参数.

定义

Eric Evans的“Domain-Driven Design领域驱动设计”简称DDD

什么是领域?  就是行业,细分行业.

领域驱动设计就是开发团队领域专家一起通过通用语言(Ubiquitous Language)去理解和消化领域知识,从领域知识中提取和划分为一个一个的子领域(核心子域,通用子域,支撑子域),并在子领域上建立模型,再重复以上步骤,这样周而复始,构建出一套符合当前领域的模型

   角色: 领域专家和开发团队

   沟通方式: 行业术语,领域语言,领域知识

   产出: 领域边界,领域模型和对象关系(不同于传统的数据库ER关系)

概念合集

图1: 概念合集

外文的ddd和cqrs CQRS, Event Sourcing, and Domain Driven Design FAQ

cqrs 和cqs区别

是 cqrs使用的模型是两套,cqs 读写分离,用的数据库表是同一套.

读写同库

cqs

读写分离

命令和查询分离

cqrs(

Command and Query Responsibility Segregation (CQRS) pattern

命令和查询责任分离

)

数据库分离是比较常见的技术

来自 【mysql 读写分离】10分钟了解读写分离的作用_东华果汁哥的博客-CSDN博客_mysql读写分离

command和query的表(模型)是不同的,为了应对业务复杂性,可能查询库的表直接和ui层一致,不需要领域层和ui层的转换.

图来自: DDD 中的那些模式 — CQRS - 知乎

这个故事也挺好:

CQRS对领域驱动设计有比较好的补充,使用得当可以解决领域模型被查询污染的问题。

下面是四种伟大的程序架构:

  1. Clean架构
    外圈的层次可以依赖内层,反之不可以,内圈核心的实体代表业务,不可以依赖其所处的技术环境。

2.DCI架构

本站中文DCI架构专题,DCI代表Data, Context, Interaction。 用上下文来驱动角色执行data. 角色是对应上下文下的类.  有点像 用户在不同的业务(券,支付,订单)下有不同的方法. 可以同名,用包路径区分.
   精读架构设计之 DCI - 知乎
   DCI:DCI学习总结 - 幸福框架 - 博客园

3.DDD/CQRS

领域驱动设计,本站DDD专题领域驱动设计对于成功交付和维护CQRS的系统非常重要。 DDD作为一项战略方针,允许将复杂的问题域划分为单独的块(称为有界上下文),虽然有很多方式如:不同的心智Mental模式,组织政治,域语言学等也是这样做,但是DDD建立了一个有界的心智mental模式,这样商务人士也可以理解,程序员也可以很容易地在代码中实现。 CQRS,作为一种战术办法,是实现DDD建模领域的最佳途径之一。事实上,它就是因为这个目标而诞生在这个世界上。相关资源:DDD – CQRS Leaven V20

4.六边形架构

允许应用程序都是由用户,程序,自动化测试或批处理脚本驱动的,在事件驱动和数据库环境下被开发和隔离测试。一个事件从外面世界到达一个端口,特定技术的适配器将其转换成可用的程序调用或消息,并将其传递给应用程序。该应用程序是可以无需了解输入设备的性质(调用者是哪个)。当应用程序有结果需要发出时,它会通过一个端口适配器发送它,这个适配器会创建接收技术(人类或自动)所需的相应信号。该应用程序与在它各方面的适配器形成语义良性互动,但是实际上不知道适配器的另一端的谁在处理任务。

百度安全验证
六边形架构_weixin_34358092的博客-CSDN博客  六边形架构 又被称之为ports&adpers 端口和适配器. 为什么它叫六边形架构呢?六边形的一个边就代表一个端口,但并不代表一个六边型架构就必须有6个端口

作者:葡萄喃喃呓语
链接:https://www.jianshu.com/p/c33de5e23fb4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最近技术对ddd的影响

原文:http://www.infoq.com/interviews/Technology-Influences-DDD#

另外一篇文章可读 http://www.jdon.com/44491

[理论]领域驱动设计 DDD 是啥,cqrs是啥相关推荐

  1. 领域驱动设计(DDD)实践之路(四):领域驱动在微服务设计中的应用

    这是"领域驱动设计实践之路"系列的第四篇文章,从单体架构的弊端引入微服务,结合领域驱动的概念介绍了如何做微服务划分.设计领域模型并展示了整体的微服务化的系统架构设计.结合分层架构. ...

  2. 领域驱动设计(DDD:Domain-Driven Design)

    领域驱动设计(DDD:Domain-Driven Design) Eric Evans的"Domain-Driven Design领域驱动设计"简称DDD,Evans DDD是一套 ...

  3. 领域驱动设计 (DDD)实例分析

    本文结合实例来分析下领域驱动设计 (DDD) 文章目录 啥是DDD 啥是驱动 DDD误解 啥时候用 啥是复杂 具体解决啥 为啥会耦合 咋解决耦合 咋做分治 咋做分界 模块 分层 咋落地 本文小结 啥是 ...

  4. 领域驱动设计DDD之读书笔记

    查看文章   领域驱动设计DDD之读书笔记  转载原地址:http://hi.baidu.com/lijiangzj 2007-08-17 16:53 一.当前Java软件开发中几种认识误区 Hibe ...

  5. 领域驱动设计(DDD)实践之路(三):如何设计聚合

    本文首发于 vivo互联网技术 微信公众号  链接:https://mp.weixin.qq.com/s/oAD25H0UKH4zujxFDRXu9Q 作者:wenbo zhang [领域驱动设计实践 ...

  6. 何时使用领域驱动设计(DDD)

    何时使用领域驱动设计?其实当你的应用程序架构设计是面向业务的时候,你已经开始使用领域驱动设计了.领域驱动设计既不是架构风格(Architecture Style),也不是架构模式(Architectu ...

  7. 领域驱动设计(DDD)-基础思想

    一.序言 领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法.在领域驱动设计理念上,各路大侠的观点也是各有不同,能力有限.欢迎留言讨论. 二.领域驱动设计 DDD是什么 wiki释 ...

  8. 领域驱动设计 DDD

    一.序言 领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法.在领域驱动设计理念上,各路大侠的观点也是各有不同,能力有限.欢迎留言讨论. 二.领域驱动设计 DDD是什么 wiki释 ...

  9. python 全栈开发,Day116(可迭代对象,type创建动态类,偏函数,面向对象的封装,获取外键数据,组合搜索,领域驱动设计(DDD))...

    昨日内容回顾 1. 三个类 ChangeList,封装列表页面需要的所有数据.StarkConfig,生成URL和视图对应关系 + 默认配置 AdminSite,用于保存 数据库类 和 处理该类的对象 ...

最新文章

  1. InputStreamReader/OutputStreamWriter乱码问题解决
  2. 转发离线安装 Android Studio 更新
  3. wget在linux中安装出现错误解决办法
  4. thinkphp mysql 中文 问号_thinkphp分页中文参数乱码解决
  5. 禁用ios7 手势滑动返回功能
  6. python正则表达式代码_python正则表达式的使用(实验代码)
  7. 软件架构之道的一次感悟
  8. pyqt5-步长调节器
  9. Kotlin 条件控制(六)
  10. Java中String,StringBuffer,StringBuilder的区别
  11. c语言木马源代码下载,木马编程 之超强服务... 附代码 原创.
  12. 【Hadoop】新旧Java MapReduce API的差异
  13. 利用webBrowser来实现自动登录网站
  14. 手机三十分钟熄屏如何一直亮_怎么让手机屏幕一直亮着
  15. php.ini gd_php安装gd扩展
  16. 电商后台管理系统项目 一
  17. 【python6】快递分拣小程序
  18. DirectX11教程5-贴图
  19. CC1310空中升级笔记04 WSN OAD Example
  20. 一款二维码签到app

热门文章

  1. C++中有符号与无符号
  2. Virtualized Firewall for Cloud Security: Working on Machines Identities, NOT on Their IP Addresses
  3. 一个相同的域名解析到多个ip,勉强实现负载均衡
  4. XP下使用aircrack注入式破解WEP(Commview支持网卡)
  5. 贵州最新建筑施工八大员之(安全员)考试题库及答案
  6. 用一个简单的例子说明SQL子查询
  7. 用笔记本通过网线给台式机共享网络
  8. DIY服务器相关资料
  9. linux开启多个matlab_Matlab并行编程方法
  10. linux--代码对比工具Meld Diff