Frage

I am designing a system that has a simple Entity Framework backed domain object that has fields I need to update based on a series of rules - I want to implement these rules progressively (in an agile style) and as I am using EF I am sceptical about putting each rule into the domain object. However, I want to avoid writing "procedural code" and using anemic domain models. This all needs to be testable as well.

As an example, the object is:

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

}

I need to build rules such as "if Salary is higher than 100,000 and _eligibleForPension is false then set _eligibleForPension as true" and "if _pension is true then set _eligibleForPension as true".

There are approximately 20 such rules and I am looking for advice whether they should be implemented in the Employee class or in something like an EmployeeRules class? My first thought was to create a separate class for each rule inheriting from "Rule" and then apply each rule to the Employee class, maybe using the Visitor pattern but I'd have to expose all the fields to the rules to do this so it feels wrong. Having each rule on the Employee class though doesn't feel quite right either. How would this be implemented?

The second concern is that the actual Employees are Entity Framework entities backed to the DB so I don't feel happy adding logic to these "Entities" - especially when I need to mock the objects for unit testing each rule. How could I mock them if they have the rules I'm testing on the same object?

I have been thinking of using AutoMapper to convert to a simpler domain object before applying rules but then need to manage the updates to the fields myself. Any advice on this too?

War es hilfreich?

Lösung

One approach is to make the rules inner classes of Employee. The benefit of this approach is that the fields can remain private. Also, the invocation of the rules can be enforced by the Employee class itself, ensuring that they are always invoked when needed:

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

One problem here is that the Employee class has to contain all rule implementations. This isn't a major problem since the rules embody business logic associated with employee pensions and so they do belong together.

Andere Tipps

Business rules are usually an interesting topic. There may certainly be a difference between an aggregate / entity invariant and a business rule. Business rules may need external data and I wouldn't agree with a rule changing an aggregate / entity.

You should think specification pattern for rules. The rule should basically just return whether it was broken or not with possibly a description of sorts.

In your example SalaryBasedPensionEligibilityRule, as used by eulerfx, may need some PensionThreshold. This rule really does look more like a task since the rule really isn't checking any validity of the entity.

So I would suggest that rules are a decision mechanism and tasks are for changing the state.

That being said you probably want to ask the entity for advice here since you may not want to expose the state:

public class Employee
{
    float salary;
    bool eligibleForPension;

    public bool QualifiesForPension(float pensionThreshold) 
    {
        return salary > pensionThreshold && !eligibleForPension;
    }

    public void MakeEligibleForPension()
    {
        eligibleForPension = true;
    }
}

This sticks with the command/query separation idea.

If you are building directly from your ORM objects and do not want to, or cannot, include all the behaviour then that is OK --- but it certainly would help :)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top