我读过一些有关贫血领域模型和关注点分离的问题。在贫血域对象上执行/附加域逻辑的最佳技术是什么?在我的工作中,我们有一个相当贫乏的模型,并且我们目前正在使用“帮助器”类在域对象上执行数据库/业务逻辑。例如:

public class Customer
{
    public string Name {get;set;}
    public string Address {get;set;}
}

public class Product
{
    public string Name {get;set;}
    public decimal Price {get;set;}
}

public class StoreHelper
{
    public void PurchaseProduct(Customer c, Product p)
    {
         // Lookup Customer and Product in db
         // Create records for purchase
         // etc.
    }
}

当应用程序需要进行购买时,它将创建 StoreHelper,并调用域对象上的方法。对我来说,客户/产品知道如何将自身保存到存储库是有意义的,但您可能不希望域对象上有 Save() 方法。对于像 Customer.Purchase(Product) 这样的方法也有意义,但这是将域逻辑放在实体上。

以下是我遇到的一些技术,不确定哪些是好/坏:

  1. Customer 和 Product 继承自“Entity”类,该类以通用方式提供基本的 CRUD 操作(可能使用 ORM)。
    • 优点:每个数据对象都会自动获取 CRUD 操作,但随后会绑定到数据库/ORM
    • 缺点:这并不能解决对象上的业务操作问题,并且还将所有域对象绑定到可能不合适的基础实体
  2. 使用辅助类来处理 CRUD 操作和业务逻辑
    • 使用 DAO 来执行“纯数据库”操作,并使用单独的业务助手来执行更特定于业务的操作是否有意义?
    • 为此使用非静态或静态帮助器类更好吗?
    • 优点:域对象不依赖于任何数据库/业务逻辑(完全贫乏)
    • 缺点:不是很面向对象,在应用程序代码中使用助手不太自然(看起来像 C 代码)
  3. 使用双重调度技术,其中实体具有保存到任意存储库的方法
    • 优点:更好地分离关注点
    • 缺点:实体附加了一些额外的逻辑(尽管它是解耦的)
  4. 在 C# 3.0 中,您可以使用扩展方法将 CRUD/业务方法附加到域对象,而无需触及它
    • 这是一个有效的方法吗?什么是优点/缺点?
  5. 其他技术?

处理这个问题的最佳技术是什么?我对 DDD 还很陌生(我正在读 Evans 的书 - 所以也许这会让我大开眼界)

有帮助吗?

解决方案

Martin Fowler 写了很多关于领域模型的文章,包括 贫血域模型. 。他还对领域模型和数据库的许多设计模式进行了简要描述(和 UML 类图),这些设计模式可能会有所帮助: 《企业应用架构模式》目录.

我建议看看 活动记录数据映射器 模式。从您的问题描述来看,您的帮助程序类似乎同时包含域/业务规则 数据库实现细节。

Active Record 会将助手的域逻辑和数据库代码移动到其他域对象中(例如您的域对象) Entity 基类)。数据映射器会将帮助器的域逻辑移至域对象中,并将数据库代码移至单独的映射对象中。这两种方法都比过程式帮助程序类更加面向对象。

Eric Evans 的《领域驱动设计》一书非常棒。虽然有点干,但绝对值得。InfoQ 有一个 《快速领域驱动设计》迷你书 这是对埃文斯这本书的很好的介绍。另外,“快速领域驱动设计”以免费 PDF 形式提供。

其他提示

为了避免贫血模型,请重构您的辅助类:

逻辑如下:
"Customer.PurchaseProduct(Product 产品,Payment 支付)",
“Customer.KillCustomer(杀人者,武器武器)”
应该存在于“客户”域对象中。

逻辑如下:
“客户.IsCustomerAlive()”
“客户.IsCustomerHappy()”
应该去规范。

逻辑如下:
“客户.Create()”,
“客户.更新()”
显然应该去存储库。

逻辑如下:
“客户.SerializeInXml()”
“客户.GetSerializedCustomerSizeInBytes()”
应该去服务。

复杂的构造函数应该去工厂。

我就是这么看的。如果有人能评论我对 DDD 方法的理解,我会很高兴。


编辑:

有时 - 贫血域模型 不应该避免.

编辑了我的答案,添加 DDD 不是关于拾取和丢弃模式。
DDD 是关于我们的思维方式。

我一直认为贫血域模型是一种反模式。很明显,客户将购买产品,这种能力可以通过接口实现来泛化

Interface IPurchase
      Purchase(Product);

, ,因此您的任何域对象都可以根据需要实现它。通过这种方式,您可以向域对象引入功能 - 这正是它应该在的位置。

您没有提到的一种方法是使用 AOP 来处理数据访问。我最近使用这种方法的一个例子(尽管为了发布目的而大大简化)是我有一个 Account 域实体具有 debit 方法,封装从帐户成功扣款所需的业务逻辑。

注意:所有代码都是带有 AspectJ AOP 表示法的 Java...

public boolean debit(int amount) {
    if (balance - amount >= 0) {
        balance = balance - amount;
        return true;
    }
    return false;
}

将适当的存储库注入到我的方面中后,我使用切入点来拦截对此方法的调用......

pointcut debit(Account account,int amount) :
    execution(boolean Account.debit(int)) &&
    args(amount) &&
    target(account);

...并应用了一些建议:

after(Account account, int amount) returning (boolean result)  : debit(account,amount) {
    if (result) getAccountRepository().debit(account, amount);
}

在我看来,这提供了很好的关注点分离,并允许您的域实体完全专注于应用程序的业务逻辑。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top