Domain driven design 贫血领域模型的处理技术

Domain driven design 贫血领域模型的处理技术,domain-driven-design,business-logic,Domain Driven Design,Business Logic,我已经阅读了一些关于贫血领域模型和关注点分离的问题。在贫血的域对象上执行/附加域逻辑的最佳技术是什么?在我的工作中,我们有一个相当贫乏的模型,我们目前正在使用“helper”类在域对象上执行数据库/业务逻辑。例如: public class Customer { public string Name {get;set;} public string Address {get;set;} } public class Product { public string Nam

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

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)这样的方法也有意义,但这是将域逻辑放在实体上

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

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

  • 处理此问题的最佳技术是什么?我是DDD的新手(我正在读埃文斯的书——也许这会让我大开眼界)

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

    Interface IPurchase
          Purchase(Product);
    

    ,这样您的任何域对象都可以根据需要实现它。通过这种方式,您可以向域对象引入功能,而这正是它应该具备的功能。

    Martin Fowler已经写了很多关于域模型的文章,包括。他还简要描述了领域模型和数据库的许多设计模式(以及UML类图),这些设计模式可能会有所帮助:

    我建议看一下这些规则和模式。从问题的描述来看,助手类似乎同时包含域/业务规则和数据库实现细节

    活动记录将帮助者的域逻辑和数据库代码移动到其他域对象中(如
    实体
    基类)。数据映射器将助手的域逻辑移动到域对象中,将数据库代码移动到单独的映射对象中。这两种方法都比过程式助手类更面向对象


    Eric Evans的《领域驱动设计》一书非常优秀。虽然有点干,但绝对值得。InfoQ有一个很好的例子来介绍埃文斯的书。另外,“域驱动的快速设计”是一个免费的PDF格式。

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

    逻辑如下:
    “客户购买产品(产品、付款)”,
    “顾客。杀手顾客(人杀手、武器)”
    应直接存在于“客户”域对象中

    逻辑如下:
    “Customer.IsCustomerAlive()”
    “Customer.IsCustomerHappy()”
    应该符合规格

    逻辑如下:
    “Customer.Create()”,
    “Customer.Update()”
    显然,我们应该去存储库

    逻辑如下:
    “Customer.SerializeInXml()”
    “Customer.GetSerializedCustomerSizeInBytes()”
    应该去服务中心

    复杂的施工人员应该去工厂

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


    编辑:

    有时-贫血区域模型

    编辑了我的答案,添加了DDD与拾取和丢弃模式无关的内容。

    DDD是我们思考的方式。

    您没有提到的一种方法是使用AOP来处理数据访问。我最近使用这种方法的一个例子(尽管为了过账目的大大简化了)是我有一个
    账户
    域实体,它有一个
    借记
    方法,封装了成功从账户进行借记所需的业务逻辑

    注意:所有代码都是带有AspectJ AOP符号的Java

    将适当的存储库注入我的方面后,我使用切入点拦截对该方法的调用

    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);
    }
    

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

    似乎有很多不同的类只是为了处理客户。为什么不把它的大部分放在一个类中,用一个服务来处理任何复杂的事情呢?我的答案非常老套D@LuckyLindy主要是因为DDD是关于在领域专家和程序员之间建立桥梁的。域模型不应该包含技术内容,否则泛在语言将无法存在。为了转移技术性的东西,我们必须把它抽象出来。抽象某些东西总是会膨胀代码库
    after(Account account, int amount) returning (boolean result)  : debit(account,amount) {
        if (result) getAccountRepository().debit(account, amount);
    }