我决定是否应该使用富域模型而不是贫穷领域模型,并寻找两者的好例子.
我一直使用Anemic Domain Model构建Web应用程序,由Service - > Repository - > Storage层系统支持,使用FluentValidation进行BL验证,并将我的所有BL放在Service层中.
我读过Eric Evan的DDD书,他(以及Fowler和其他人)似乎认为Anemic Domain Models是一种反模式.
所以我真的很想了解这个问题.
此外,我真的在寻找一个富域模型的好(基本)示例,以及它提供的贫血域模型的好处.
Bozhidar Bozhanov似乎在这篇博客文章中支持贫血模型.
以下是他提出的摘要:
域对象不应该是spring(IoC)管理的,它们不应该有DAO或与其中注入的基础结构相关的任何内容
域对象具有它们依赖于hibernate设置的域对象(或持久性机制)
域对象执行业务逻辑,因为DDD的核心思想是,但这不包括数据库查询或CRUD - 仅对对象内部状态的操作
很少需要DTO - 在大多数情况下,域对象本身就是DTO(这节省了一些样板代码)
服务执行CRUD操作,发送电子邮件,协调域对象,基于多个域对象生成报告,执行查询等.
服务(应用程序)层不是那么薄,但不包括域对象固有的业务规则
应该避免代码生成.应该使用抽象,设计模式和DI来克服代码生成的需要,并最终 - 摆脱代码重复.
UPDATE
我最近阅读了这篇文章,其中作者提倡遵循一种混合方法 - 域对象可以仅根据其状态回答各种问题(在完全贫血模型的情况下可能会在服务层中完成)
不同之处在于贫血模型将逻辑与数据分开.该逻辑通常放在名为类**Service
,**Util
,**Manager
,**Helper
等.这些类实现数据解释逻辑,因此将数据模型作为参数.例如
public BigDecimal calculateTotal(Order order){ ... }
而富域域方法通过将数据解释逻辑放入富域模型来反转这一点.因此,它将逻辑和数据放在一起,富域模型看起来像这样:
order.getTotal();
这对对象一致性有很大影响.由于数据解释逻辑包装数据(数据只能通过对象方法访问),因此方法可以对其他数据的状态变化做出反应 - >这就是我们所说的行为.
在贫血模型中,数据模型不能保证它们处于合法状态,而在富域模型中它们可以.丰富的域模型应用OO原则,如封装,信息隐藏以及将数据和逻辑结合在一起,因此贫血模型是从OO角度出发的反模式.
如需更深入的了解,请查看我的博客https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/
我的观点是这样的:
贫血域模型=映射到对象的数据库表(仅字段值,没有实际行为)
富域模型=暴露行为的对象集合
如果你想创建一个简单的CRUD应用程序,也许一个带有经典MVC框架的贫血模型就足够了.但是如果你想实现某种逻辑,贫血模型意味着你不会做面向对象的编程.
*请注意,对象行为与持久性无关.不同的层(数据映射器,存储库等)负责持久化域对象.
首先,我复制粘贴了这篇文章的答案 http://msdn.microsoft.com/en-gb/magazine/dn385704.aspx
图1显示了一个Anemic Domain Model,它基本上是一个带有getter和setter的模式.
Figure 1 Typical Anemic Domain Model Classes Look Like Database Tables public class Customer : Person { public Customer() { Orders = new List(); } public ICollection Orders { get; set; } public string SalesPersonId { get; set; } public ShippingAddress ShippingAddress { get; set; } } public abstract class Person { public int Id { get; set; } public string Title { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string CompanyName { get; set; } public string EmailAddress { get; set; } public string Phone { get; set; } }
在这个更丰富的模型中,Customer的公共表面不是简单地公开要读取和写入的属性,而是由显式方法组成.
Figure 2 A Customer Type That’s a Rich Domain Model, Not Simply Properties public class Customer : Contact { public Customer(string firstName, string lastName, string email) { FullName = new FullName(firstName, lastName); EmailAddress = email; Status = CustomerStatus.Silver; } internal Customer() { } public void UseBillingAddressForShippingAddress() { ShippingAddress = new Address( BillingAddress.Street1, BillingAddress.Street2, BillingAddress.City, BillingAddress.Region, BillingAddress.Country, BillingAddress.PostalCode); } public void CreateNewShippingAddress(string street1, string street2, string city, string region, string country, string postalCode) { ShippingAddress = new Address( street1,street2, city,region, country,postalCode) } public void CreateBillingInformation(string street1,string street2, string city,string region,string country, string postalCode, string creditcardNumber, string bankName) { BillingAddress = new Address (street1,street2, city,region,country,postalCode ); CreditCard = new CustomerCreditCard (bankName, creditcardNumber ); } public void SetCustomerContactDetails (string email, string phone, string companyName) { EmailAddress = email; Phone = phone; CompanyName = companyName; } public string SalesPersonId { get; private set; } public CustomerStatus Status { get; private set; } public Address ShippingAddress { get; private set; } public Address BillingAddress { get; private set; } public CustomerCreditCard CreditCard { get; private set; } }
当我过去编写整体桌面应用程序时,我构建了丰富的域模型,并喜欢构建它们。
现在,我编写了微小的HTTP微服务,其中包含的代码很少,包括贫乏的DTO。
我认为DDD和这种贫乏的争论可以追溯到整体桌面或服务器应用时代。我记得那个时代,我同意贫血模型是奇怪的。我建立了一个大型的整体外汇交易应用程序,但没有模型,真的,这太可怕了。
对于微服务而言,具有丰富行为的小型服务可以说是域内可组合的模型和集合。因此,微服务实现本身可能不需要进一步的DDD。微服务应用程序可以是域。
订单微服务可能只有很少的功能,以RESTful资源或通过SOAP或其他方式表示。订单微服务代码可能非常简单。
更大,更单一的单一(微)服务,尤其是将其保持在RAM中的模型,可能会受益于DDD。
富域类的一个好处是,每次在任何层中引用对象时,都可以调用它们的行为(方法).此外,您倾向于编写协作一致的小型分布式方法.在贫血领域类中,您倾向于编写通常由用例驱动的胖程序方法(在服务层中).与富域类相比,它们通常难以维护.
具有行为的域类的示例:
class Order { String number Listitems ItemList bonus Delivery delivery void addItem(Item item) { // add bonus if necessary } ItemList needToDeliver() { // items + bonus } void deliver() { delivery = new Delivery() delivery.items = needToDeliver() } }
方法needToDeliver()
将返回需要交付的项目列表,包括奖金.它可以在类中,从另一个相关类或另一个层中调用.例如,如果您传递Order
给视图,那么您可以使用needToDeliver()
选择Order
来显示用户确认的项目列表,然后单击"保存"按钮以保留该项目Order
.
回应评论
这是我如何使用控制器中的域类:
def save = { Order order = new Order() order.addItem(new Item()) order.addItem(new Item()) repository.create(order) }
创建Order
和它LineItem
在一个事务中.如果LineItem
无法创建其中一个,则不会Order
创建.
我倾向于拥有代表单个事务的方法,例如:
def deliver = { Order order = repository.findOrderByNumber('ORDER-1') order.deliver() // save order if necessary }
内部的任何内容deliver()
都将作为一个单一的交易执行.如果我需要在单个事务中执行许多不相关的方法,我会创建一个服务类.
为了避免延迟加载异常,我使用JPA 2.1命名实体图.例如,在交付屏幕的控制器中,我可以创建加载delivery
属性和忽略的方法bonus
,例如repository.findOrderByNumberFetchDelivery()
.在奖金屏幕中,我调用另一个加载bonus
属性和忽略的方法delivery
,例如repository.findOrderByNumberFetchBonus()
.这需要dicipline,因为我仍然无法调用deliver()
内部奖金屏幕.