设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
17623 人已学习
课程目录
已更新 21 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (4讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?

王争 2019-11-29
上一节课,我们做了一些理论知识的铺垫性讲解,讲到了两种开发模式,基于贫血模型的传统开发模式,以及基于充血模型的 DDD 开发模式。今天,我们正式进入实战环节,看如何分别用这两种开发模式,设计实现一个钱包系统。
话不多说,让我们正式开始今天的学习吧!

钱包业务背景介绍

很多具有支付、购买功能的应用(比如淘宝、滴滴出行、极客时间等)都支持钱包的功能。应用为每个用户开设一个系统内的虚拟钱包账户,支持用户充值、提现、支付、冻结、透支、转赠、查询账户余额、查询交易流水等操作。下图是一张典型的钱包功能界面,你可以直观地感受一下。
一般来讲,每个虚拟钱包账户都会对应用户的一个真实的支付账户,有可能是银行卡账户,也有可能是三方支付账户(比如支付宝、微信钱包)。为了方便后续的讲解,我们限定钱包暂时只支持充值、提现、支付、查询余额、查询交易流水这五个核心的功能,其他比如冻结、透支、转赠等不常用的功能,我们暂不考虑。为了让你理解这五个核心功能是如何工作的,接下来,我们来一块儿看下它们的业务实现流程。

1. 充值

用户通过三方支付渠道,把自己银行卡账户内的钱,充值到虚拟钱包账号中。这整个过程,我们可以分解为三个主要的操作流程:第一个操作是从用户的银行卡账户转账到应用的公共银行卡账户;第二个操作是将用户的充值金额加到虚拟钱包余额上;第三个操作是记录刚刚这笔交易流水。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(109)

  • potato00fa
    我对DDD的看法就是,它可以把原来最重的service逻辑拆分并且转移一部分逻辑,可以使得代码可读性略微提高,另一个比较重要的点是使得模型充血以后,基于模型的业务抽象在不断的迭代之后会越来越明确,业务的细节会越来越精准,通过阅读模型的充血行为代码,能够极快的了解系统的业务,对于开发来说能说明显的提升开发效率。
    在维护性上来说,如果项目新进了开发人员,如果是贫血模型的service代码,无论代码如何清晰,注释如何完备,代码结构设计得如何优雅,都没有办法第一时间理解系统的核心业务逻辑,但是如果是充血模型,直接阅读充血模型的行为方法,起码能够很快理解70%左右的业务逻辑,因为充血模型可以说是业务的精准抽象,我想,这就是领域模型驱动能够达到"驱动"效果的由来吧
    2019-11-29
    4
    47
  • miracle
    建议将完整一些的代码放到 github 上 然后感兴趣的话可以自行去github 上研究或者提 pr

    作者回复: 好的,我把完整代码抽空整理好放到github上
    https://github.com/wangzheng0822

    2019-11-29
    4
    32
  • 辣么大
    理解OOP,我们就不难理解DDD:
    DDD第一原则:将数据和操作结合。(贫血模型将数据和操作分离,违反OOP的原则。)
    DDD第二原则:界限上下文。这是将“单一指责”应用于我们的领域模型。

    DDD is nothing more than OOP applied to business models. DDD其实就是把OOP应用于业务模型。

    实现:
    1、使用通用语言(Ubiquitous Language):类、方法、字段的命名,要符合业务。使用业务语言命名,以后在和客户或者其他团队交流时能够更顺畅。

    2、理解系统业务:例如做一个理财系统,要亲自去和银行卖理财产品的人聊聊或者买个理财产品之后,那些数据库中对你来说毫无意义的字段才变得有血有肉。

    介绍一篇博客吧:DDD101 https://medium.com/the-coding-matrix/ddd-101-the-5-minute-tour-7a3037cf53b8

    最后,是时候祭出大杀器了:《领域驱动设计》Eric Evans (反正我也没看)
    2019-11-29
    9
    22
  • 丿淡忘
    这两天一直在思考ddd,就等课程更新,这样一说就理解了,domain模型使用充血模型设计,使之具备独立性,而业务无关的vo,po就可以使用贫血模型进行设计,因为不涉及具体复杂业务,如果control层需要调用多个领域模型,则把相关的领域服务组合在一起,这里有个小问题,就是do转为dto这个过程,应该是在应用层完成还是领域层完成,如果在应用层完成,好像属于把领域模型暴露出去了,希望老师可以在指点一下
    2019-11-29
    9
    19
  • 风之射手座
    第2步支付处理流程感觉有点问题:
    从用户的虚拟钱包转90到商家虚拟钱包应该就完了,不应该再从应用公共银行卡再划钱到商家银行卡。如果要即时划转到商家银行卡,就要记得把商家的虚拟钱包减少90。
    好像是这样吧?
    2019-11-30
    8
    14
  • Cy23
    听完一遍,看来我需要在听一遍,php视乎要理解JAVA的有点差异啊
    2019-11-29
    12
    8
  • 落叶飞逝的恋
    还有一点,期待老师实现一个完整的案例的代码以供我们参考琢磨。

    作者回复: 完整案例代码可能就太多了

    2019-11-29
    3
    5
  • 随机的
    请教一下老师,以及各位同学,销售单、进货单、调拨单、入库单、出库单之类的单据类型是否适合使用ddd,单据的操作一般是新增编辑删除,还有状态的变化,以最复杂的调拨单为例,调拨单有新增、编辑、删除、查找操作,还有状态操作,比如从草稿到待审核,待审核到已审核,待审核到拒绝,审核通过之后还要在出库仓库生成待发货的出库单,入库仓库生成待收货的入库单,出库单入库单又有各自的状态操作,出库单确认发货,出库单变成已发货状态,同步修改调拨单和入库单的状态为已发货,入库仓库收到货,确认收货后,入库单变成已收货,同步修改调拨单和出库单的状态为已收货,这已经是简化的流程,我当前的实现中还涉及到仓库与总部的结算,仓库部分发货,部分收货等操作,结算还支持部分结算,就更复杂了,这种情况下,适合使用ddd吗?若适合,该怎么使用,仿照老师虚拟钱包的例子,实在不知从何下手,ddd不是更适合复杂操作吗,这里该如何应用ddd呢?求指教,感激不尽。
    2019-12-06
    4
  • 邹佳敏
    看了一圈评论,好像没有人和我有同样的疑惑?
    争哥说了很多交易流水表的设计,明明已经详细介绍了字段冗余的表1要明显优于表2,但为何在虚拟钱包的交易流水表的设计里,使用的又是字段紧凑的表2呢?
    那么,在底层虚拟钱包的交易流水表里,同样会存在数据不一致的情况呀?A转出被记录下来了,B转入失败。

    作者回复: 是有这个问题 我改下

    2019-12-02
    1
    4
  • join
    看到这里,感觉才真正理解充血模型的作用:

        真正的业务逻辑都放在充血的领域对象中,与具体使用什么框架(比如Spring,MyBatis),具体使用什么数据库无关。这样有利于保护领域对象中的数据,比如钱包中的余额,当有入账和出账操作时,余额在领域对象中自动执行加减操作,而不是将余额暴露在Service中直接操作(这样很容易出错可能导致帐不平衡,余额应该封装保护起来),当然“余额自动增减”这只是一个简单的业务逻辑例子,业务逻辑越复杂就越应该封装到领域对象中。

    1. Service层只是一个中间层,起到连接和组合作用。
    用于支持领域模型层和Repository层的交互(连接作用),利用各种领域对象执行业务逻辑(组合作用)。
    比如通过Repository查出数据,将数据转换为领域模型对象,利用领域模型对象执行业务逻辑(核心),然后调用Repository更新领域模型中的数据。

    2. Service类还负责一些非功能性及与三方系统交互的工作。
    比如幂等、事务、发邮件、发消息、记录日志、调用其他系统的 RPC 接口等。

        不允许Service中的逻辑过于复杂,如果Service中的组合的业务逻辑过于复杂,我们就要将这业务逻辑抽取出一个新的领域对象进行封装,通过调用这个领域对象来进行这些复杂的操作。

        由于controller和Repository层中本身没有什么业务逻辑,controller中的Vo对象实际上只是传输数据使用(数据从系统传输数据到外部调用方),Repository中的Entity本质上也只是传输数据(数据从数据库中传输数据到系统),所以用贫血模型不会带来副作用,是没有问题的。
    2019-11-30
    4
  • 墨雨
    看了老师的这篇文章让我对 entity,bo,vo有了一个更清晰的认识。我是这样理解的,entity是对数据库的映射,vo 是前端展示的映射,bo 在 DDD 充血模型中我看到了他的用处,看起来他是将 entity 的一些逻辑业务分离了出来做了一个解耦(在我看来貌似没有 bo 或者说 Domain 类似 加余额减余额的逻辑也可以写在 entity 中,只是这样做对于专注于数据库的 entity 来说逻辑更复杂了,维护起来会很困难 ),同时也解决了 entity 暴露过多 getter setter 方法的问题。不知道我这样理解有没有问题,欢迎老师指正。

    同时我有如下几个疑问:
    1.具体上 domain 和 entity 属性和结构上有哪些不同呢?(在我看来好像能写成一样的)
    2.在贫血模型下 bo 的作用好像没有那么明显了,多写一层 bo 能给我们带来什么好处呢?
    3. entity bo vo 类属性上好像有很多重合,貌似在实际编写的过程会出现很多重复代码,并且要为每一层编写转换代码,代码量好像又增加了,对于这种情况应该怎么优化和权衡呢?
    2019-11-29
    4
    4
  • 陈华应
    DDD真正的价值在于战略设计,对业务模型到领域模型的建模时需要重点关注的有哪些,比如确定核心服务(核心域、通用域、支撑域)、微服务边界(领域、子域)、领域边界(限界上下文),功能归类(聚合)等等。而战略设计最终的目的仍然是说过来说过去的那些,高内聚、低耦合、面向对象设计、职责单一、易扩展、易维护、易拆分、易演进。
    DDD战术设计是一种实施的方法论,但是因为他是看的见、摸得着(有真正所谓的代码结构可以参考)的,吸引了更多的关注点,如果没有背后的战略设计的思想,生搬硬套,甚至可能会适得其反。
    DDD最重要的还是设计思想,也就是战略设计,而不是他的模式或者分层方式,也就是战术设计!
    2019-11-30
    3
  • 睡觉💤
    在我看来,Repository与Domian都是service的底层。Repository复杂数据的存储,Domian负责业务逻辑,service将两者融合。
    2019-11-29
    3
  • 落叶飞逝的恋
    DDD 中VirtualWalletService convert哪里定义了。
    2019-11-29
    3
    3
  • Angus
    我理解的ddd分为四层,用户接口层,应用层,领域层,基础设施层。领域服务还是跟基础设施层打交道,领域服务主要是提供这个领域的业务行为,通过应用层聚合领域服务,而应用层正是和领域专家建立统一语言的一层,
    2019-11-29
    3
  • sprinty
    感谢老师的分享,收获很多,也产生了两个问题:

    问题1:Entity 转换成 Domain 的代码应该在哪一层实现?感觉在 Service 层不大合适,因为可能多个 Service 会使用到。
    问题2:如果涉及到表单的保存,入参是一个保存全量数据的对象(比如,创建一个新用户的所有用户数据,但部分属性还是要计算得到的,比如年龄等)。这个对象是属于 VO 吗?这时的 Domain 怎么设计呢?数据模型间的转换怎么处理呢?VO->BO->Entity 感觉就是在写各种赋值语句啊, 所以我以前在传统开发模式是合并 VO、BO、Entity的,一个大而全的东西也是很尴尬。

    期待老师解答。
    2019-11-29
    1
    2
  • 老姜
    更新流水出现异常会导钱包操作成功了,但是就是状态是错误的?是不是应该把生成流水放到一个事务里面,更新钱包和更新流水状态放到另外一个事务里面会避免这个问题?
    2019-11-29
    2
  • licstar
    有个疑问,如果VirtualWallet里的冻结之类的功能全都实现了,这样操作流水里又会多冻结、解冻的记录的类型。这样的话,似乎service的代码会和VirtualWallet一样复杂,因为VirtualWallet中的每个方法,在service里都要把数据同步到数据库中。这样合理吗,会让我觉得service层变成了另一个repository层,有点重复
    2019-12-05
    1
    1
  • Geek_Zjy
    总结(直接忽略案例):
    * 充血模型把业务逻辑放到 Domain 中处理,满足了 OOP 的规则,数据和行为封装为一体。
      类似于 CPU 主管计算,而对于数据转换之类的工作不应该交由 Domain 去做。
    * 把外围事务交给 Service 管理,比如接口传入的数据封装,底层数据库数据的读写,就好像 CPU 从来不关心 IO 的差异;比如把日志和消息幂等性等工作交给 Service 处理,CPU 也从来不负责监控和容错。
    自己的感受:
    这种业务相关性和业务无关性的分离,其实就是遵循了高内聚、低耦合,保证了业务和框架的独立性和重用性。

    问题:
    * 无论 vo 还是 Entity 在我看来都是 dto ,这么多dto会不会导致类爆炸?
      大概4、5年前,我用过两天 aws 的 sdk ,我记得所有的接口都会有对应的 dto ,所以这种类爆炸是必要的类爆炸,还是也是要自己权衡?
      类似关联查询返回的结果也算是 Entity 吧,Entity 未必和表一一对应吧(额,感觉不应该在这章提问)?

    其他:
    * 有人想要完整代码,我觉得没有太大必要,因为代码是示意代码,并非正式项目代码。
      而且课程目标不是要做出一个完整小项目,实战往往和理论不是一一匹配的,而且需要大量额外工作。
    * 如果老师真的打算代码上 github ,那我的建议是用接口和抽象类来完成即可,即便如此我把老师的例子完成也写了14个文件。
      老师还是应该把精力放到刀刃上,如果真的非要完成个小项目,我觉得也可以延迟到栏目结束,这种限时又定量的事,作为非全职讲师一定很难吧。
    * 关于问题的回复,我建议对于需要回复的问题,老师可以告知在后续章节会讲到,或者到答疑中统一提问,免得有人觉得回复不及时,而老师的一一回复既耗精力也很难普惠大家。
      当然我也希望评论区功加上评论、提问、点赞几个选项,老师以后可以只看提问类型的留言,或者只看没有人回复的提问。
    2019-12-03
    1
  • 努力的熊
    你们都这么厉害么,我看了一遍没看懂。看了你们的评论,太重要了,吓得我赶紧在研究研究。
    2019-12-02
    1
收起评论
99+
返回
顶部