什么是领域驱动设计?
你可能使用领域驱动设计(DDD)开发了一些项目。你可能很满意, 使用领域模型来开发领域业务。并且得意地展示给你的同事看,他们会说“666”。
但有的时候你使用领域模型
你总觉得哪儿有点不对劲。你会嘀咕你可能遗漏了什么。 emmmm...但真的是这样吗?你发觉自己在问类似下面的这些问题:
- 什么是领域模型?
- 领域模型是怎么更新到数据库的呢?
- 领域模型是否要引入一种架构模式?例如
CQRS
当我刚开始使用领域模式
的时候,我也同样被上面这些问题所困扰。 甚至当我写文档前几天,我也并没有扎实地掌握某些细节。 我经历了一些“啊哈”的开窍时刻,我想把这些分享给你。 这篇文章会深入讲解帮你明白上面问题的答案。
在看答案之前,我们需要先往后退一步。 这篇文章的目的不是给你一个要点清单,而是想帮你真正地领会领域模型。 其实我们并没有太多需要学习的,事实上,我们会花很多时间试图忘记某些已经习得的概念(unlearning)。
“忘记你已经学到的。” — Yoda
这篇文章真的很长。它更像一本mini书,这也是我更喜欢的形式。
摘要
如果你不想阅读整篇文章,可以快速浏览这份摘要。如果你打算阅读整篇文章,你完全可以跳过这部分。
Question:什么是领域模型?
为解决场景下的问题而形成的一套模型,然后使用这套模型来解决业务问题。 根据重复劳动经验我们会形成一套模式。领域模型也一样会形成一套模式,他包括:实体、值对象、模块、领域服务。
Question:领域模型是怎么更新到数据库的呢?
使用资源库(repository
)将领域模型更新到数据库。 在一个一对多
的实体,例如:用户组(Group
)和用户(User
),用户组内有N个用户, 如果用户非常多,一次加载Group
肯定会造成性能损失,这种问题怎么设计呢? 如果不使用领域模型依靠经验你会想到拆分,现在你使用了领域模型想的方案也应该是拆分。 我看到有些人把repository
注入到领域模型内,这种做法是错误的。
Question:领域模型是否要引入一种架构模式?例如CQRS
首先我想说的是领域模型不需要引入架构模式。 领域模式是解决领域内的业务问题的,他不是解决架构问题,所以领域模型本身不需要引入架构。 当你使用领域模型的时候,领域层之上你可以使用架构,比如CQRS
、MVC
等等。
要不,我们开始正文。我想从传统的项目开始讲解一步步的过渡到领域模型。 我将讲解三种组织代码的结构,分别是:传统模式、IDDD
模式、我思考的模式。
说一下我们用到的技术框架:
- ORM:Spring Data JPA
- Database:h2-database
我们先假设一个需求用例,现在我们要开发一个身份认证模块(identity)其中包括的功能有 获得用户信息、修改密码、用户认证。
使用传统项目来开发整个需求
首先来看下我们的项目结构:
这个项目结构大家已经非常熟悉了,就是我们传统的分层方式。 我们通常会把业务逻辑写在service里面,如下:
整个功能我们已经开发完了,是不是看着非常简单。 我们来看changePassword
方法,我们通过repository
根据用户名获得到用户, 然后调用authenticate
方法进行认证,认证通过后修改密码(user.setPassword()
)。 是不是看上去没有任何毛病,而且程序还运行的非常成功。嗯,确实是。 但是在这种编程模式里我们更多的是从数据库的模式来开发的,因为User
只是封装了静态数据。 然后在service
中来完成修改密码这个逻辑,这种操作更像是过程化编程。
我们应该从面向对象的角度来思考问题。 一提到对象你首先想到的是什么?继承?封装?多态?还有吗? 我再添一条类是一组相关的属性和行为的集合
。
既然我提到了属性和行为,那我们重新思考下修改密码这个用例: 首先password
是User
类上的一个属性,对于修改密码我们应该是调用User
对象上的changePassword
方法, 然后由changePassword
来完成修改password
属性,这就是封装。
接下来我们使用领域模型再来完成上面用例的业务。
使用IDDD来开发整个需求
首先我们也是来看一下IDDD的项目结构:
当你看到这个项目结构的时候,你可能心生疑惑:“怎么没有service, repository
这样的包呢?”
我来告诉你,IDDD推荐我们使用应用层、领域层和基础设施层来做分层。 对于这种分包方式是《实现领域驱动设计》这本书比较推荐的。所以这个项目我们以这种方式来组织我们的代码。
接下来我们来看看应用层服务和领域模型:
再来看UserService
上的changePassword
方法,我们还是通过authenticate
获得用户, 然后又调用了用户的changePassword
来修改密码。
但是你会发现我们并没有把密码加密这个交由User
来完成,这是为什么呢?
因为加密不是User
的职责。就像authenticate
一样,你自己不可能认证自己, 就像你不借助镜子永远不会看清你的脸蛋。就像你会问别人我今天漂不漂亮,而不会自欺欺人的告诉自己我很漂亮。
通过将changePassword
交由User
,你会发现你的业务很清晰了。 因为你对号入座将原本由用户该做的事情又还给了用户。
接下来我想说一个我认为的分层架构,以及如何组织代码。这部分知识涉及到了UML、面向对象。
我直接上图,然后在细细讨论:
当你看到这个结构,你会发现怎么没有层了呢?怎么都放在了一个包里。 聊到这我就应该给你说一说UML中的模块和构造型。 我们都已经很熟悉模块了,因为我们平常都是在用包来组织模块。 这个我就不多讲了,我想说的什么是构造型。构造型就是用来区分不同种类的类。 玩了那么多年的Spring,还记得那几个常用的组件注解(@Component
、@Controller
、@Service
、@Repository
)吗? 他们被放置在org.springframework.stereotype
这个包下,就是为了区分你的不同种类的类。
所以我们现在为我们的模块(identity
)画一个UML的类图:
当你看到UML的类图再和刚才的Java项目结构图进行对比是不是有些清晰了呢? 这个时候你可以再配合一个用例和时序图,来表达你的业务。
整个示例源码
what-is-a-domain-model-example
结束
也大体说完了整个演变过程,这也是我一年了的技术总结吧。
为了搞清楚心中的一个疑惑:“为什么我们项目的代码组织结构和国外开源框架的组织结构有那么大的不同呢。” 我花了一年的时间看完了:《领域驱动设计》、《实现领域驱动设计》、 《企业应用架构模式》、《软件建模与设计》这几本书,其实还有正在看没有看完的。
当你想搞懂一个东西的时候,你应该把他搞到通透,然后再说你怎么看待这个问题。 有的时候我在看完DDD以后,我还是会问自己,这是我想要的吗?出现这种疑惑还是需要攀登, 你应该继续选择研究这本书引用其他知识,知识的传播可能是书籍也可能是论文。 当把这些迭代的思想看完以后,你还得回过头来在看这个问题,再去揣摩。可能会有新的突破。 只有这样一次次的突破,才能解决你心中的疑惑。
还有很多细节我可能没有一一表达出来。 上述演变过程仅代表我个人思考,禁止转载,如有误人子弟之处,请您指出。
如有问题请加QQ群讨论: