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

埃里克·埃文斯(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。

相关文章 :


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值