在领域驱动的设计,贫乏的领域模型,代码生成,依赖项注入等方面……

埃里克·埃文斯(Eric Evans)已制定了什么是域驱动设计(DDD)。 Martin Fowler是DDD的大力支持者和拥护者。 这些都是非凡的名字,几乎可以肯定的是,他们正在支持一些有价值的东西。 我不是在这里对此争论。 也许我正在试图证明我编写软件的方式的合理性,或者也许我只是试图清除事物并具有建设性。 让我们来看看。

什么是领域驱动设计的核心- 域模型通用语言的抽象的概念。 我不会对此进行详细介绍–对于那些感兴趣的人,有维基百科(在页脚中有很多参考文献可供阅读)。 从理论上讲,这一切都是非常好的,并且域驱动的构建软件的方式应该对所有人都具有吸引力–毕竟,构建该软件是为了该领域的利益,而不是建筑师,开发人员或QA的利益。

但是现在谈到了实际部分–如何实施DDD? 我将在当代的背景下回答这个问题,即使用spring和hibernate之类的框架。 我会证明它们的用法合理。 Spring是一个非侵入性的依赖注入框架。 Fowler也强烈支持DI,并且DI被认为是实现DDD的好方法。 休眠是使用关系数据库时使用对象的一种方法。 另一种方法是使用JDBC并手动构造对象,但这很繁琐。 因此,休眠不会影响体系结构部分-它是一种实用程序(当然,功能非常强大)。

在本文中,我将使用“休眠”和“弹簧”作为“给定的”,尽管它们可以通过任何DI框架以及任何依赖对象的ORM或其他持久性机制进行更改。

使用spring和hibernate实现DDD的公认方法是:

  • 使用@Configurable使域对象适合进行依赖项注入(它们不会在spring之前实例化,因此它们需要这种特殊方法)
  • 在域对象中注入存储库对象,以允许域对象执行与持久性相关的操作
  • 使用薄的,无状态的事务服务层(外观)来协调域对象

这篇广泛的文章显示了这种方法的实现和描述。 另一个示例(没有Spring)是http://dddsample.sourceforge.net/ 。 稍后再讨论。

这种方法的替代方法是贫血域模型 。 它被认为是一种反模式,但同时非常普遍并且经常使用。 贫血数据模型的功能很简单–域对象内部没有业务逻辑–它们只是数据持有者。 而是将业务逻辑放在服务中。

之所以将其视为反模式,是因为,首先,这似乎是一种程序方法。 它破坏了封装,因为对象的内部状态完全不是内部状态。 其次,由于领域对象是设计的中心,因此,如果它的操作不属于它,而改为多个无状态服务类,则很难更改它。 域驱动的设计针对中型到大型应用程序,这些应用程序发生了很大变化,并且需要一种简便的方法来快速进行更改,而又不会破坏其他功能。 因此,在对象本身内具有对象的所有功能非常重要。 这也可以确保减少重复代码。

因此,代替让服务来计算价格: ProductServiceImpl.calculatePrice(complexProduct),我们应该简单地拥有ComplexProduct.calculatePrice() 。 因此,每当领域专家说价格计算机制发生变化时,更改它的地方就是一种,也是最直接的一种。

如果考虑简单的操作,这看起来很容易。 但是,当一个域对象需要另一个域对象来完成其工作时,它将变得更加复杂。 使用贫血数据模型,只需将另一个Service注入当前Service并调用其方法即可实现。 使用建议的DDD,可以通过将域对象作为参数来实现。

在我看来,域对象(它也是休眠实体)已经设置了其依赖项。 但不是在Spring之前,因为Spring无法确切知道要注入哪个领域对象。 它们由休眠“注入”,因为它确切知道应将哪个(由主键标识)域对象放置在另一个域对象中。 因此,有一个奇怪的例子–如果产品腐烂并且必须在仓库中分配气味,则必须调用例如Warehouse.increaseSmellLevel(getSmellCoeficient()) 。 并且它有精确的仓库,不受弹簧的干扰。

现在,我不同意这一点。 大多数来源(包括上面链接的两个来源)都指出应该将存储库/ DAO注入域对象中。 不,他们不应该。 只需调用“保存”或“更新”就不需要了解对象的内部状态。 Hibernate仍然知道一切。 因此,我们只是将整个对象传递到存储库。

让我们将其分为两个部分- 业务逻辑基础架构逻辑 。 域对象应该对基础结构一无所知。 那可能意味着它不应该知道它被保存在某个地方。 产品是否关心其存储方式? 不,这是“感兴趣”的存储机制。 这是实际的缺点:

  • 通过简单地将存储库调用包装在所有域对象中来实现CRUD –代码重复
  • 域对象可传递地依赖于持久性–即它不是纯域对象,并且如果存储库发生更改,则也必须对其进行更改。 从理论上讲,仅当域规则和属性更改时,才应更改
  • 人们很容易将事务,缓存和其他逻辑包含在域对象中

在上面的一篇文章中,我将在此处打开有关建议的解决方案的括号,以使代码重复和样板代码更易于处理。 建议生成代码。 而且我认为代码生成是一种罪过。 它将无法删除重复的或非常相似的代码并将其抽象化为工具。 最引人注目的示例是生成ProductDAO,CategoryDAO,WarehouseDAO等。生成的代码难以管理,无法扩展且严重依赖于外部元数据,这绝对不是面向对象的方法。

说到存储库,在建议的示例中,每个域对象都应该有一个存储库,该存储库又将调用持久性机制。 那我们得到什么:

用户在UI中按“保存”>保存在服务上的UI调用(以便获得事务支持)>保存在域对象上的服务调用>保存在资源库上的域对象调用>保存在持久性机制上的资源库调用>持久性机制保存对象。

是我自己,还是在这里调用域对象是多余的。 这是一种不增加任何内容的直通方法。 而且由于很多功能与CRUD有关(是的,即使在大型企业应用程序中也是如此),这对我来说似乎很糟糕。

最后,我发现@Configurable方法是一个hack。 它在后台做了一些魔术,这不是任何通用语言功能(也不是设计模式),并且为了了解它是如何发生的,您需要大量的经验。

所以,总结上面的大混乱

  • 域对象不应由Spring(IoC)进行管理,它们不应具有DAO或与基础结构有关的任何内容
  • 域对象具有由休眠(或持久性机制)设置的它们依赖的域对象
  • 域对象执行业务逻辑,就像DDD的核心思想一样,但这不包括数据库查询或CRUD –仅对对象内部状态进行的操作
  • 几乎不需要DTO-在大多数情况下,域对象本身就是DTO(这节省了一些样板代码)
  • 服务执行CRUD操作,发送电子邮件,协调域对象,基于多个域对象生成报告,执行查询等。
  • 服务(应用程序)层并不薄,但不包括域对象固有的业务规则
  • 应避免生成代码。 应该使用抽象,设计模式和DI来克服代码生成的需求,并最终–摆脱代码重复。

参考:有关 领域驱动设计,贫乏领域模型,代码生成,依赖项注入等的信息 ,请参见Bozho技术博客上的JCG合作伙伴 Bozho。

相关文章 :

  • Spring和AspectJ的领域驱动设计
  • 在域驱动设计中使用状态模式
  • ORM问题
  • 什么是依赖倒置? 是IoC吗?
  • 框架使开发人员愚蠢吗?
  • 每个程序员都应该知道的事情
  • JDK中的设计模式
  • Java最佳实践

翻译自: https://www.javacodegeeks.com/2011/09/on-domain-driven-design-anemic-domain.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374896.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Javascript模块化工具require.js教程

转自:http://www.w3cschool.cc/w3cnote/requirejs-tutorial-1.html, http://www.w3cschool.cc/w3cnote/requirejs-tutorial-2.html 随着网站功能逐渐丰富,网页中的js也变得越来越复杂和臃肿,原有通过script标签来导入一个个的js文件这种方式已…

数据值、列类型和数据字段属性

数据值:数值型、字符型、日期型和空值等。数据列类型 2.1 数值类的数据列类型2.2 字符串类数据列类型 2.3 日期和时间型数据数据列类型 另外,也可以使用整形列类型存储UNIX时间戳,代替日期和时间列类型,这是基于PHP的web项目中常…

全文搜索Apache Lucene简介

在本教程中,我想谈谈Apache Lucene 。 Lucene是一个开源项目,提供基于Java的索引和搜索技术。 使用其API,很容易实现全文搜索 。 我将处理Lucene Java版本 ,但请记住,还有一个名为Lucene.NET的.NET端口,以及…

函数scanf

本节介绍输入函数 scanf 的用法。scanf 和 printf 一样&#xff0c;非常重要&#xff0c;而且用得非常多&#xff0c;所以一定要掌握。 概述 scanf 的功能用一句话来概括就是“通过键盘给程序中的变量赋值”。该函数的原型为&#xff1a; # include <stdio.h> int scanf(…

C语言中定义变量位置

C标准的问题 C89规定&#xff0c;在任何执行语句之前&#xff0c;在块的开头声明所有局部变量。 即应该如下&#xff1a;定义变量只能在最开始&#xff0c;scanf等执行语句之前 int N 0;double sum 0;scanf("%d",&N);在C99以及C中则没有这个限制&#xff0c;即…

Java中的低GC:使用原语而不是包装器

总览 有两个很好的理由在可能的地方使用原语而不是包装器。 明晰。 通过使用原语&#xff0c;您可以清楚地知道null值是不合适的。 性能。 使用原语通常更快。 清晰度通常比性能更重要&#xff0c;并且是使用它们的最佳理由。 但是&#xff0c;本文讨论了使用包装程序对性能…

C# 连接Oracle数据库以及一些简单的操作

拖了很久今天终于在博客园写了自己第一篇随笔&#xff1a; 话不多说&#xff0c;我们直接进入正题&#xff1a; 1.连接数据库 using (OracleConnection conn new OracleConnection("data source192.168.97.60/orcl;User Idabc;Passwordabc;")) { …

markdownb编辑器

这是H1 这是H2 这是H3 这是一个标题。 这是第一行列表项。这是第二行列表项。给出一些例子代码&#xff1a; return shell_exec("echo $input | $markdown_script"); 转载于:https://www.cnblogs.com/xcl461330197/p/4605163.html

Java Secret:使用枚举构建状态机

总览 Java中的枚举比许多其他语言更强大&#xff0c;这可能导致令人惊讶的用途。 在本文中&#xff0c;我概述了Java 枚举的一些单独功能&#xff0c;并将它们组合在一起形成一个状态机。 单例和实用程序类的枚举 您可以非常简单地将枚举用作Singleton或Utility。 enum Si…

数组部分笔记

对于数组的初始化需要注意以下几点&#xff1a; 可以只给部分元素赋值。当{ }中值的个数少于元素个数时&#xff0c;只给前面部分元素赋值。例如&#xff1a; int a[10]{12, 19, 22 , 993, 344};表示只给 a[0]~a[4] 5个元素赋值&#xff0c;而后面 5 个元素自动初始化为 0。 …

指向函数的指针

指向函数的指针变量的一般形式为&#xff1a;数据类型 &#xff08;*指针变量名&#xff09;&#xff08;函数参数表列&#xff09;&#xff1b;如&#xff1a; int (*p)(int ,int );1、int (*p)(int ,int );表示定义一个指向函数的指针变量p&#xff0c;它不是固定只能指向…

核心Java面试答案不正确

总览 在Internet上&#xff0c;Java面试问题和答案从一个网站复制到另一个网站。 这可能意味着错误或过时的答案可能永远不会得到纠正。 这是一些不太正确或已经过时的问题和答案。 即是Java 5.0之前的版本。 每个提供的问题后都有两个部分。 斜体的第一部分指示答案不完整/错…

138.括号序列(区间型DP)

3657 括号序列 时间限制: 1 s空间限制: 256000 KB题目等级 : 黄金 Gold题解查看运行结果题目描述 Description我们用以下规则定义一个合法的括号序列&#xff1a; &#xff08;1&#xff09;空序列是合法的 &#xff08;2&#xff09;假如S是一个合法的序列&#xff0c;则 (S) …

C# 执行批处理文件(*.bat)的方法代码

代码如下:static void Main(string[] args){Process proc null;try{ string targetDir string.Format("D:\adapters\setup");//this is where mybatch.bat liesproc new Process();proc.StartInfo.WorkingDirectory targetDir;proc.StartInfo.Fil…

C语言空格怎么表示

1.直接敲空格就行&#xff0c;或者使用ASCII码值赋值为32。 空格没有转义字符。 printf("12%c45 58",32);输出 12 45 582.合法转义字符如下&#xff1a;\a 响铃(BEL) 、\b 退格(BS)、\f 换页(FF)、\n 换行(LF)、\r 回车(CR)、\t 水平制表(HT)、\v 垂直制表(VT) 0、…

Tomcat中的零停机部署(和回滚); 演练和清单

亲爱的大家&#xff0c; 如果您认为Tomcat不能再进步&#xff0c;那您就错了。 Tomcat 7引入了所谓的并行部署 。 这是由SpringSource / VMWare贡献的。 简而言之&#xff0c;并行部署是一种能够并行部署一个以上版本的Web应用程序的功能&#xff0c;使所有版本都可以在完全相…

javaweb 学习资源

http://jinnianshilongnian.iteye.com/category/231099转载于:https://www.cnblogs.com/sishahu/p/5368018.html

HDU 1863 畅通工程(最小生成树,prim)

题意&#xff1a; 给出图的边和点数&#xff0c;要求最小生成树的代价&#xff0c;注&#xff1a;有些点之间是不可达的&#xff0c;也就是可能有多个连通图。比如4个点&#xff0c;2条边:1-2&#xff0c;3-4。 思路&#xff1a; 如果不能连通所有的点&#xff0c;就输出‘?’…

2000年不算在21世纪

练习3-5 输出闰年 (15 分) 输出21世纪中截止某个年份以来的所有闰年年份。注意&#xff1a;闰年的判别条件是该年年份能被4整除但不能被100整除、或者能被400整除。 想当然地以为21世纪是2000~2099&#xff0c;当然没有通过 if(N > 2000&&N < 2099){for(int i …

使用迭代器时如何避免ConcurrentModificationException

Java Collection类是快速失败的&#xff0c;这意味着如果在使用迭代器遍历某个线程的同时更改了Collection&#xff0c;则iterator.next&#xff08;&#xff09;将抛出ConcurrentModificationException 。 在多线程以及单线程环境下都可能出现这种情况。 让我们通过以下示例探…