领域驱动设计思想解疑

hlevel 发布于 2015/12/21 11:29
阅读 1K+
收藏 2

@秋水逍遥 你好,想跟你请教个问题:


     这段时间研究我发现ddd的确很有优势,很能体现面向对象式抽象能力,非面向实现,带来可扩展,灵活是毋庸置疑的. 同时我也看你的 hrm-demo  ,我有一些疑问想请教一下,烦请解答一下

     在此之前我是研究那 Axon framework 框架的,他们通过事件驱动来带动程序流程. 很符合ddd推荐的思想。但是无奈框架太大,没办法驾驭,想用你开发的DDDLib 这个比较容易理解. 但是看了你的hrm-demo 例子,我直观感受觉得demo把domain领域层和基础模型数据层这些都混在一起了,不太符合推荐ddd标准思想.

    在我看来领域层只管理领域业务,有业务动作,领域里面更不应该有数据库查询语句出现,换句话来说即使不需要数据库领域层业务也是可以跑起来的,数据库仅仅做保存。而基础数据层仅仅只做数据保存才会有数据库操作,这样分的目也是兼容将来项目换数据库了,或者是改了数据库设计,我只需要把基础数据库模型改一下,而domain领域层运行的业务是不需要改动的,数据模型仅仅持久化数据的,应用层就是协调领域与领域之间交互,做到每层解耦,所以对于你这领域与数据层混合在一起不知道你是怎么思考这问题的?


我附上一个推荐ddd业务分层图: 文章在此






加载中
2
秋水逍遥
秋水逍遥

你好。基于你上面的叙述,我觉得你可能对“对象查询”有误解,以为等价于“数据库查询”了。对象查询属于业务逻辑,数据库查询才属于基础设施层。

领域实体的属性和方法都是领域模型的一部分,操纵、调用领域实体的属性和方法,以及根据属性查询领域实体,都属于业务操作,是业务逻辑的一部分。因此,select Employee e where e.educationLevel > :educationLevel这样的查询语句(注意这是对象查询语言而不是SQL)也是业务逻辑(用业务的语言表述,就是“查找学历高于指定级别的员工”),应保留在领域层。如果把它划分出去,放到基础设施层,业务逻辑层就不完整了。代码的用户想搞清楚Employee.findEducationLevelHigherThan()方法的业务逻辑,不得不去纯技术性的基础设施层去了解这些业务(而非技术)细节,这是非常不合理的——业务逻辑渗漏到基础设施层了,以至于业务问题要到技术层中去寻求答案。

    理想情况下,基础设施层应该是纯技术性的,与业务概念、业务逻辑毫不相关(不具有领域实体的任何知识),可以跨项目重用,不依赖于领域层;次理想的情况下,基础设施层知道业务概念,但不实现任何业务逻辑,这时候基础设施层依赖于领域层。在DDDLib中,我们对持久化的处理方式就是遵循上面的第一种规则来实现的:dddlib-persistence-hibernate里面没有任何领域实体概念,因而是跨项目通用的。

    补充说明:在DDDLib中,对象查询语言直接采用JPA规范的JPQL来实现了,毕竟JPQL属于一种对象查询语言,通用性较好;但另一方面,正因为它是一种通用性的语言,所以不能够非常贴合特定领域的需求。例如上面的例子,"select Employee e where e.educationLevel > :educationLevel", educationLevel参数只能是数值(或其他可比较的简单值),而不能是一个实体或值对象,因为JPQL无法通过>号比较实体/值对象。最理想的情况是实现一种领域特定语言DSL,例如可以这样表述:“select Employee where his educationLevel is higher than :educationLevel”,这样的语言就更靠近问题域而不是解决方案域了。

沃德天拉莫帅
沃德天拉莫帅
标准答案
1
宏哥
宏哥

domain的真正含义是:

行业

被一帮子什么都不懂的“狗假死”搞成领域模型了

0
翟志军
翟志军

你问题里的这个图里Domain 依赖于Infrastructure是不对的。真正的DDD应该是反过来。基础服务依赖于领域层。

翟志军
翟志军
回复 @秋水逍遥 : /OK
秋水逍遥
秋水逍遥
实际上第一张图表达的不是编译时“类”依赖关系,而是运行时“对象”调用关系,从这个角度来看,这张图还是正确的。编译时是基础设施层中的类依赖于领域层中的类,而运行时是领域层中的对象调用基础设施层中的对象,这两者的方向是相反的,能够做到这一点,依赖的是Spring等IoC框架,来实现这个控制反转/依赖倒置原则。
hlevel
hlevel
基础服务依赖领域层? 基础层 我理解为是 数据库和数据模型操作啊。能否提供一下文章参考一下?
0
沃德天拉莫帅
沃德天拉莫帅

很久没用ddd了,建立领域模型确实可以最大限度重用业务代码,他是跟技术没关系的,所以领域层应该是最底层的代码,基础服务依赖于领域层

hlevel
hlevel
回复 @成熟的毛毛虫 : 基础我也这么理解的。不知道你们还有没有按照这模式开发?假如开发了你们基于什么框架来写的?还是自己实现的?
沃德天拉莫帅
沃德天拉莫帅
回复 @hlevel : 基础层包含很多,ftp服务,数据库,但是理念跟代码又不同,反正我现在不纠结这种分层模式了
hlevel
hlevel
你跟一楼说得意思是一样的吗?基础层不应该是数据库操作工具吗?不晓得你理解的基础层是些什么?
0
清靜無虞
清靜無虞
应该是基础服务层服务于领域层吧,基础服务层包括像cache、数据存储、定时任务、邮件发送、http远程调用等等,这些提供给domain层使用,业务逻辑在domain层中,domain层与application层交互。我现在大致是这样分的:application->service->domain->infrastructure。至于严格意义上的ddd,包括像axon这种框架,我用着很别扭,我都是按自己的想法来规划的,认为这样更合理。
0
hlevel
hlevel

引用来自“秋水逍遥”的评论

你好。基于你上面的叙述,我觉得你可能对“对象查询”有误解,以为等价于“数据库查询”了。对象查询属于业务逻辑,数据库查询才属于基础设施层。

领域实体的属性和方法都是领域模型的一部分,操纵、调用领域实体的属性和方法,以及根据属性查询领域实体,都属于业务操作,是业务逻辑的一部分。因此,select Employee e where e.educationLevel > :educationLevel这样的查询语句(注意这是对象查询语言而不是SQL)也是业务逻辑(用业务的语言表述,就是“查找学历高于指定级别的员工”),应保留在领域层。如果把它划分出去,放到基础设施层,业务逻辑层就不完整了。代码的用户想搞清楚Employee.findEducationLevelHigherThan()方法的业务逻辑,不得不去纯技术性的基础设施层去了解这些业务(而非技术)细节,这是非常不合理的——业务逻辑渗漏到基础设施层了,以至于业务问题要到技术层中去寻求答案。

    理想情况下,基础设施层应该是纯技术性的,与业务概念、业务逻辑毫不相关(不具有领域实体的任何知识),可以跨项目重用,不依赖于领域层;次理想的情况下,基础设施层知道业务概念,但不实现任何业务逻辑,这时候基础设施层依赖于领域层。在DDDLib中,我们对持久化的处理方式就是遵循上面的第一种规则来实现的:dddlib-persistence-hibernate里面没有任何领域实体概念,因而是跨项目通用的。

    补充说明:在DDDLib中,对象查询语言直接采用JPA规范的JPQL来实现了,毕竟JPQL属于一种对象查询语言,通用性较好;但另一方面,正因为它是一种通用性的语言,所以不能够非常贴合特定领域的需求。例如上面的例子,"select Employee e where e.educationLevel > :educationLevel", educationLevel参数只能是数值(或其他可比较的简单值),而不能是一个实体或值对象,因为JPQL无法通过>号比较实体/值对象。最理想的情况是实现一种领域特定语言DSL,例如可以这样表述:“select Employee where his educationLevel is higher than :educationLevel”,这样的语言就更靠近问题域而不是解决方案域了。

     你好,非常感谢你回答,基础设施层在理想情况下应该是纯技术的,这点我赞同。但是在领域业务与数据库之间应该还有一层数据持久层,这层作用把领域运行数据转化成数据持久层所依赖的模型,比如业务租户是一个聚合根,领域层Tenant下有店铺、商户、会员实体等等. 但是实际转换成数据持久层模型的 Tenant 表字段有 id,创建时间, 店铺, 但是并没有 店铺id,商户id之类的依赖。从这个意义表达是领域层,与数据库设计无关。只对领域业务暴露一个定义好的接口.

     数据持久我认为比合适写HQL, sql 之类的,你说的用业务翻译语言解释HQL,我觉得解释sql 也一样说得通。只是一个写法不同而已.HQL 基于业务对象查询,SQL基于表对象查询。

     再就是我感觉你这套设计思路还是得先设计对象型数据库.或者是说你先建model 还是得考虑数据库存取方式,和优化方面做一些业务调整,没有达到解耦, 再就是假设你数据库部分模型重新设计了。那么你业务就得大改,牵动到领域层,传统三层ssh就这个问题。

     假设数据库部分表重新设计了(比如:原来表里有50字段,实际运行中发现其中有15字段是存大文本的,而且不经常用,但是其他字段经常用,那么我可以抽出这15字段放到其他表),这样我只改动我的数据持久层和数据库对应的model ,领域层不需要动什么.

    再来就是说的话领域模型为什么要和数据库模型分开?领域就是为用面向对象化思想来描述整个业务思路。但是在实际项目中。有些业务功能数据量比较大,查询面向对象方式去做得话加载数据,和查询都非常慢。我们不得不做牺牲来优化代码,优化查询方法,优化步骤,这样又破坏了用面向对象描述业务思想. 所以我这样才这样坚持认为.领域不应该有HQL,SQL还有和数据库模型完全对应的领域模型存在!优化之类功能可以交给数据持久层来完成工作,这样每层做自己最擅长得事情岂不是更好?


0
秋水逍遥
秋水逍遥

引用来自“秋水逍遥”的评论

你好。基于你上面的叙述,我觉得你可能对“对象查询”有误解,以为等价于“数据库查询”了。对象查询属于业务逻辑,数据库查询才属于基础设施层。

领域实体的属性和方法都是领域模型的一部分,操纵、调用领域实体的属性和方法,以及根据属性查询领域实体,都属于业务操作,是业务逻辑的一部分。因此,select Employee e where e.educationLevel > :educationLevel这样的查询语句(注意这是对象查询语言而不是SQL)也是业务逻辑(用业务的语言表述,就是“查找学历高于指定级别的员工”),应保留在领域层。如果把它划分出去,放到基础设施层,业务逻辑层就不完整了。代码的用户想搞清楚Employee.findEducationLevelHigherThan()方法的业务逻辑,不得不去纯技术性的基础设施层去了解这些业务(而非技术)细节,这是非常不合理的——业务逻辑渗漏到基础设施层了,以至于业务问题要到技术层中去寻求答案。

    理想情况下,基础设施层应该是纯技术性的,与业务概念、业务逻辑毫不相关(不具有领域实体的任何知识),可以跨项目重用,不依赖于领域层;次理想的情况下,基础设施层知道业务概念,但不实现任何业务逻辑,这时候基础设施层依赖于领域层。在DDDLib中,我们对持久化的处理方式就是遵循上面的第一种规则来实现的:dddlib-persistence-hibernate里面没有任何领域实体概念,因而是跨项目通用的。

    补充说明:在DDDLib中,对象查询语言直接采用JPA规范的JPQL来实现了,毕竟JPQL属于一种对象查询语言,通用性较好;但另一方面,正因为它是一种通用性的语言,所以不能够非常贴合特定领域的需求。例如上面的例子,"select Employee e where e.educationLevel > :educationLevel", educationLevel参数只能是数值(或其他可比较的简单值),而不能是一个实体或值对象,因为JPQL无法通过>号比较实体/值对象。最理想的情况是实现一种领域特定语言DSL,例如可以这样表述:“select Employee where his educationLevel is higher than :educationLevel”,这样的语言就更靠近问题域而不是解决方案域了。

引用来自“hlevel”的评论

     你好,非常感谢你回答,基础设施层在理想情况下应该是纯技术的,这点我赞同。但是在领域业务与数据库之间应该还有一层数据持久层,这层作用把领域运行数据转化成数据持久层所依赖的模型,比如业务租户是一个聚合根,领域层Tenant下有店铺、商户、会员实体等等. 但是实际转换成数据持久层模型的 Tenant 表字段有 id,创建时间, 店铺, 但是并没有 店铺id,商户id之类的依赖。从这个意义表达是领域层,与数据库设计无关。只对领域业务暴露一个定义好的接口.

     数据持久我认为比合适写HQL, sql 之类的,你说的用业务翻译语言解释HQL,我觉得解释sql 也一样说得通。只是一个写法不同而已.HQL 基于业务对象查询,SQL基于表对象查询。

     再就是我感觉你这套设计思路还是得先设计对象型数据库.或者是说你先建model 还是得考虑数据库存取方式,和优化方面做一些业务调整,没有达到解耦, 再就是假设你数据库部分模型重新设计了。那么你业务就得大改,牵动到领域层,传统三层ssh就这个问题。

     假设数据库部分表重新设计了(比如:原来表里有50字段,实际运行中发现其中有15字段是存大文本的,而且不经常用,但是其他字段经常用,那么我可以抽出这15字段放到其他表),这样我只改动我的数据持久层和数据库对应的model ,领域层不需要动什么.

    再来就是说的话领域模型为什么要和数据库模型分开?领域就是为用面向对象化思想来描述整个业务思路。但是在实际项目中。有些业务功能数据量比较大,查询面向对象方式去做得话加载数据,和查询都非常慢。我们不得不做牺牲来优化代码,优化查询方法,优化步骤,这样又破坏了用面向对象描述业务思想. 所以我这样才这样坚持认为.领域不应该有HQL,SQL还有和数据库模型完全对应的领域模型存在!优化之类功能可以交给数据持久层来完成工作,这样每层做自己最擅长得事情岂不是更好?


一般而言,建模的先后顺序是先进行领域建模,然后才考虑如何将它持久化。领域模型反映的是现实业务实体的属性和行为,和技术实现无关,因此应该是领域模型决定数据模型,而不是数据模型决定领域模型。数据模型在满足领域模型的前提下再考虑如何优化,因此不应该存在你说的“再就是假设你数据库部分模型重新设计了。那么你业务就得大改,牵动到领域层”问题。领域层只可能由于业务变动或对业务领域理解的深化才发生变动,一般不会因为持久化技术细节的原因而发生改动。

JPA提供了两类Annotation来标记领域实体:逻辑Annotation(如@Entity ,@OneToMany, @ElementCollection等)标识对象模型,物理Annotation(如@Table , @Column ,@JoinColumn等)标识关系模型。可见它也是支持将持久化元数据定义在领域层的。因此我认为,对象查询语言实现在领域层是正常不过的事情:与持久化元数据相比,对象查询语言更接近问题域,更远离技术领域。

由于采用了JPA的Annotation和JPQL,DDDLib的领域层不会绑定于特定的数据库(但确实绑定于数据库持久化而不能迁移到别的持久化技术)。

假设数据库部分表重新设计了(比如:原来表里有50字段,实际运行中发现其中有15字段是存大文本的,而且不经常用,但是其他字段经常用,那么我可以抽出这15字段放到其他表)”这种情形,用JPA的@SecondaryTable标注在一个地方就解决了,不需要修改别的东西,包括查询语言。

至于你说的可能存在性能问题,调整性能的主要手段应该是修改持久化元数据而不是查询语言。查询语言面向问题域,性能问题属于解决方案域范畴。如果你觉得因为技术原因需要修改领域层中的实体类的持久化元数据是个问题(我也觉得这种方法不完美),那你可以不采用Annotation方式定义持久化元数据,而采用XML方式,将持久化元数据存储于基础设施层。

最后,其实我也不完全反对领域特定仓储实现的方式(每个聚合一个仓储实现类的方式,仓储类知道聚合的细节。与之相比,DDDLib是领域无关的仓储实现方式,用一个仓储为所有领域的所有实体提供持久化服务),考虑到你所说的性能问题,以及支持非关系数据库存储的方式等。但我仍然认为,应该实现一种查询DSL,基于领域实体类型和属性用面向问题域的词汇表述查询意图,而这种DSL应该包括在领域层中,而由基础设施层实现DSL解析,转换为从持久化存储中获取数据。

在hrm-demo中的这种实现方式的一个附加好处是:大多数情况下,我们不在需要编写基础设施层,可以减少整整一层的代码。对于小型项目,这是非常方便的方式。只有对大型软件,才值得定义一套DSL,否则JPQL已经够用了。杀鸡别用牛刀。

hlevel
hlevel
谢谢了。感谢在问题上讨论,我大概知道了你的看法了,我还是会寻找我所说解决方案 :)
0
秋水逍遥
秋水逍遥

引用来自“翟志军”的评论

你问题里的这个图里Domain 依赖于Infrastructure是不对的。真正的DDD应该是反过来。基础服务依赖于领域层。

实际上第一张图表达的不是编译时“类”依赖关系,而是运行时“对象”调用关系,从这个角度来看,这张图还是正确的。编译时是基础设施层中的类依赖于领域层中的类,而运行时是领域层中的对象调用基础设施层中的对象,这两者的方向是相反的,能够做到这一点,依赖的是Spring等IoC框架,来实现这个控制反转/依赖倒置原则。
0
秋水逍遥
秋水逍遥

引用来自“清靜無虞”的评论

应该是基础服务层服务于领域层吧,基础服务层包括像cache、数据存储、定时任务、邮件发送、http远程调用等等,这些提供给domain层使用,业务逻辑在domain层中,domain层与application层交互。我现在大致是这样分的:application->service->domain->infrastructure。至于严格意义上的ddd,包括像axon这种框架,我用着很别扭,我都是按自己的想法来规划的,认为这样更合理。

你这里的各层关系如果代表调用顺序就大体正确,如果代表依赖关系就不对了。依赖关系应该是领域层最基础。

另外,你添加了一个service层是不合理的。如果它是一个领域服务,它应该属于领域层,放置那些不适合放在聚合中的业务逻辑(一项单独的功能用一个单独的service来实现);如果它是一个facade,那么它就与application层的职责重叠了——application层表示的就是系统的功能外观。所以我认为:不该有个单独的service层存在。大多数时候,所谓service层实际上是application层的别名。
返回顶部
顶部