【问题标题】:How to avoid anemic domain model with business logic in the form of rules如何避免规则形式的业务逻辑贫乏的领域模型
【发布时间】:2013-03-26 19:53:09
【问题描述】:

我正在设计一个系统,该系统具有一个简单的实体框架支持的域对象,该对象具有我需要根据一系列规则更新的字段 - 我想逐步实施这些规则(以敏捷的方式)并且我正在使用 EF我对将每个规则放入域对象持怀疑态度。但是,我想避免编写“程序代码”和使用贫血的域模型。这一切都需要可测试。

例如,对象是:

class Employee { 
 private string Name; 
 private float Salary; 
 private float PensionPot;
 private bool _pension;
 private bool _eligibleForPension;

}

我需要建立规则,例如“如果 Salary 高于 100,000 并且 _eligibleForPension 为 false,则将 _eligibleForPension 设置为 true”和“如果 _pension 为 true,则将 _eligibleForPension 设置为 true”。

大约有 20 条这样的规则,我正在寻求建议,是否应该在 Employee 类或类似 EmployeeRules 类中实现它们?我的第一个想法是为从“Rule”继承的每个规则创建一个单独的类,然后将每个规则应用于 Employee 类,可能使用访问者模式,但我必须将所有字段暴露给规则才能做到这一点感觉不对。虽然在 Employee 类上设置每条规则也感觉不太正确。这将如何实施?

第二个问题是,实际的员工是支持到数据库的实体框架实体,所以我不乐意为这些“实体”添加逻辑——尤其是当我需要模拟对象以对每个规则进行单元测试时。如果他们有我在同一个对象上测试的规则,我怎么能嘲笑他们?

我一直在考虑在应用规则之前使用 AutoMapper 转换为更简单的域对象,但随后需要自己管理对字段的更新。对此也有什么建议吗?

【问题讨论】:

    标签: entity-framework domain-driven-design entity business-logic anemic-domain-model


    【解决方案1】:

    一种方法是使规则成为Employee 的内部类。这种方法的好处是字段可以保持私有。此外,规则的调用可以由 Employee 类本身强制执行,确保它们总是在需要时被调用:

    class Employee
    {
        string id;
        string name;
        float salary;
        float pensionPot;
        bool pension;
        bool eligibleForPension;
    
        public void ChangeSalary(float salary)
        {
            this.salary = salary;
            ApplyRules();
        }
    
        public void MakeEligibleForPension()
        {
            this.eligibleForPension = true;
            ApplyRules(); // may or may not be needed
        }
    
        void ApplyRules()
        {
            rules.ForEach(rule => rule.Apply(this));
        }
    
        readonly static List<IEmployeeRule> rules;
    
        static Employee()
        {
            rules = new List<IEmployeeRule>
            {
                new SalaryBasedPensionEligibilityRule()
            };
        }
    
        interface IEmployeeRule
        {
            void Apply(Employee employee);
        }
    
        class SalaryBasedPensionEligibilityRule : IEmployeeRule
        {
            public void Apply(Employee employee)
            {
                if (employee.salary > 100000 && !employee.eligibleForPension)
                {
                    employee.MakeEligibleForPension();
                }
            }
        }
    }
    

    这里的一个问题是 Employee 类必须包含所有的规则实现。这不是一个主要问题,因为这些规则体现了与员工养老金相关的业务逻辑,因此它们确实属于同一类。

    【讨论】:

    • 这看起来可以解决问题。虽然我给出了一个包含私有字段的示例,这是我想要的设计,但 EF 具有公共属性,因此如果我直接访问Employees 类,我就不必使用内部类。我将把这个问题留一点,希望有人能回答包括 EF 部分在内的问题。谢谢!
    • 我使用稍微修改的方法构建了一个系统的演示模型——我没有内部类的规则——它工作起来感觉很好。对于这个系统,我知道将字段公开并不是一种可怕的犯罪,但我可能会再次尝试使用内部类来看看它的感觉。感谢您的帮助。
    【解决方案2】:

    业务规则通常是一个有趣的话题。聚合/实体不变量和业务规则之间肯定存在差异。业务规则可能需要外部数据,我不同意更改聚合/实体的规则。

    您应该为规则考虑规范模式。该规则基本上应该只返回它是否被破坏,并可能带有某种描述。

    在您的示例中,eulerfx 使用的SalaryBasedPensionEligibilityRule 可能需要一些PensionThreshold。该规则确实看起来更像是一项任务,因为该规则实际上并未检查实体的任何有效性。

    所以我建议规则是一种决策机制,任务是为了改变状态。

    话虽如此,您可能想在这里向实体寻求建议,因为您可能不想公开状态:

    public class Employee
    {
        float salary;
        bool eligibleForPension;
    
        public bool QualifiesForPension(float pensionThreshold) 
        {
            return salary > pensionThreshold && !eligibleForPension;
        }
    
        public void MakeEligibleForPension()
        {
            eligibleForPension = true;
        }
    }
    

    这符合命令/查询分离的想法。

    如果您直接从您的 ORM 对象构建并且不想或不能包含所有行为,那没关系 --- 但它肯定会有所帮助:)

    【讨论】:

    • 一种有趣的方法,更适合“保持逻辑与对象”的口头禅。您最终会在 Employee 对象上实现每个业务逻辑,然后让另一个对象负责询问“您是否满足此规则的条件?然后触发此更新”?我可以看到这变得迅速复杂,但也保留了所有附加到 Employee 对象的逻辑。我想这取决于是否需要对 Employee 对象进行大量条件和更新。您能否举例说明其他编组更新的对象是什么样的?
    • “你能举个例子吗?” --- 我真的不明白这个:) --- 你能重述你的问题吗?
    • 对不起!必须有一个函数在某处执行“if employee.QualifiesForPension(100000) then employee.MakeEligibleForPension();” this 和 Employee 之间的关系如何工作 - 它会是一个单独的类吗?如果是,它会是什么样子?
    • 啊,好的 :) --- 那将是我在一个单独的类中关于(操作脚本)的“任务”位。得到这个链接:my.safaribooksonline.com/book/web-development/9780321669636/… 定义得很好:操作脚本 [POEAA] 包含指导领域模型 [POEAA] 中实体(即对象)活动的应用程序逻辑。每个脚本通常都满足应用程序域中的一个用例。操作脚本与事务脚本 [POEAA] 的不同之处在于它们将大部分工作委托给域对象。
    • 这听起来很有趣,对于一个更大的系统,我认为这里有价值,但我已经决定为这个系统,在这个规模上,eulerfx 的方法对我来说更实用。非常感谢,我学到了一个新技巧!
    猜你喜欢
    • 1970-01-01
    • 2011-12-19
    • 2018-11-10
    • 2010-12-02
    • 2014-02-24
    • 1970-01-01
    • 1970-01-01
    • 2012-02-04
    • 2011-02-20
    相关资源
    最近更新 更多