Click here to Skip to main content
16,009,847 members
Articles / Programming Languages / C#
Tip/Trick

Domain Policy for Domain-Driven Design

Rate me:
Please Sign up or sign in to vote.
4.58/5 (6 votes)
13 Mar 2017CPOL 16.8K   76   10   4
How to capture complex business rules with the policy pattern

Introduction

To handle complex business rules, Eric Evans describes in his book Domain-Driven Design the Policy pattern (page 18), also known as Strategy pattern (Gamma). Based on his example, "Extracting a Hidden Concept" (page 17); this tip demonstrates a possible implementation.

Using the Code

First, we need the domain foundation classes for rules and policies:

C#
// ------------------------------------------------------------------------
public interface IRule
{
    bool IsValid { get; }
}

// ------------------------------------------------------------------------
public class Rule : IRule
{
    public Rule()
    {
    }
    public Rule(bool isValid)
    {
        IsValid = isValid;
    }

    public bool IsValid { get; private set; }
}

// ------------------------------------------------------------------------
public interface IPolicy
{
}

// ------------------------------------------------------------------------
public interface IPolicy<T1> : IPolicy
{
    IRule Validate(T1 arg1);
}

// ------------------------------------------------------------------------
public interface IPolicy<T1, T2> : IPolicy
{
    IRule Validate(T1 arg1, T2 arg2);
}

// ------------------------------------------------------------------------
public interface IPolicy<T1, T2, T3> : IPolicy
{
    IRule Validate(T1 arg1, T2 arg2, T3 arg3);
}

// ------------------------------------------------------------------------
public abstract class Policy : IPolicy
{
}

// ------------------------------------------------------------------------
public abstract class Policy<T1> : IPolicy<T1>
{
    public abstract IRule Validate(T1 arg1);
}

// ------------------------------------------------------------------------
public abstract class Policy<T1, T2> : IPolicy<T1, T2>
{
    public abstract IRule Validate(T1 arg1, T2 arg2);
}

// ------------------------------------------------------------------------
public abstract class Policy<T1, T2, T3> : IPolicy<T1, T2, T3>
{
    public abstract IRule Validate(T1 arg1, T2 arg2, T3 arg3);
}

// ------------------------------------------------------------------------
public abstract class DomainObject
{
    public TResult Validate<TResult, TPolicy, T1>(T1 arg1)
        where TResult : IRule
        where TPolicy : IPolicy<T1>
    {
        return (TResult)((IPolicy<T1>)_policies[typeof(TPolicy)]).Validate(arg1);
    }

    public TResult Validate<TResult, TPolicy, T1, T2>(T1 arg1, T2 arg2)
        where TResult : IRule
        where TPolicy : IPolicy<T1, T2>
    {
        return (TResult)((IPolicy<T1, T2>)_policies[typeof(TPolicy)]).Validate(arg1, arg2);
    }

    public TResult Validate<TResult, TPolicy, T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
        where TResult : IRule
        where TPolicy : IPolicy<T1, T2, T3>
    {
        return (TResult)((IPolicy<T1, T2, T3>)_policies[typeof(TPolicy)]).Validate(arg1, arg2, arg3);
    }
    protected void RegisterPolicy(IPolicy policy)
    {
        _policies.Add(policy.GetType(), policy);
    }

    private readonly Dictionary<Type, IPolicy> _policies = new Dictionary<Type, IPolicy>();
}

Based on the foundation, we have the following business classes:

C#
// ------------------------------------------------------------------------
public class Cargo
{
    public int Size { get; set; }
}

// ------------------------------------------------------------------------
public class Voyage
{
    public int Capacity { get; set; }
    public int BookedCargoSize { get { return cargos.Values.Sum(x => x.Size); } }

    public void AddCargo(Cargo cargo, int confirmation)
    {
        cargos.Add(confirmation, cargo);
    }

    private readonly Dictionary<int, Cargo> cargos = new Dictionary<int, Cargo>();
}

// ------------------------------------------------------------------------
public class OverbookingRule : Rule
{
    public OverbookingRule(int cargoSize, int bookedCargoSize, double maxCapacity, bool isValid) :
        base(isValid)
    {
        CargoSize = cargoSize;
        BookedCargoSize = bookedCargoSize;
        MaxCapacity = maxCapacity;
    }

    public int CargoSize { get; private set; }
    public int BookedCargoSize { get; private set; }
    public double MaxCapacity { get; private set; }
}

// ------------------------------------------------------------------------
public class OverbookingPolicy : Policy<Cargo, Voyage>
{
    public override IRule Validate(Cargo cargo, Voyage voyage)
    {
        double voyageMaxCapacity = voyage.Capacity * 1.1;
        return new OverbookingRule(
            cargo.Size, voyage.BookedCargoSize, voyageMaxCapacity,
            (cargo.Size + voyage.BookedCargoSize) <= voyageMaxCapacity);
    }
}

// ------------------------------------------------------------------------
public class VoyageController : DomainObject
{
    public VoyageController()
    {
        RegisterPolicy(new OverbookingPolicy());
    }

    public OverbookingRule MakeBooking(Cargo cargo, Voyage voyage)
    {
        var result = Validate<OverbookingRule, OverbookingPolicy, Cargo, Voyage>(cargo, voyage);
        if (result.IsValid)
        {
            int confirmation = GetNextOrderConfirmation();
            voyage.AddCargo(cargo, confirmation);
        }
        return result;
    }

    private int GetNextOrderConfirmation()
    {
        int confirmation = nextConfirmation;
        nextConfirmation++;
        return confirmation;
    }

    private int nextConfirmation = 1;
}

And finally, the sample application:

C#
// ------------------------------------------------------------------------
class Program
{
    static void Main(string[] args)
    {
        Voyage voyage = new Voyage { Capacity = 100 };
        VoyageController voyageController = new VoyageController();

        Console.WriteLine("Voyage, capacity={0}", voyage.Capacity);
        Console.WriteLine();
        for (int i = 0; i < 10; i++)
        {
            Cargo cargo = new Cargo { Size = 15 };
            var rule = voyageController.MakeBooking(cargo, voyage);
            if (rule.IsValid)
            {
                Console.WriteLine("added cargo with size={0}, voyage cargo size={1}",
                    cargo.Size, voyage.BookedCargoSize);
            }
            else
            {
                Console.WriteLine("out of capacity! cargo size={0}, booked size={1}, max capacity={2}",
                    rule.CargoSize, rule.BookedCargoSize, rule.MaxCapacity);
                break;
            }
        }
        Console.WriteLine();
        Console.Write("Press any key...");
        Console.ReadKey();
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Switzerland Switzerland
๐Ÿ‘จ Senior .NET Software Engineer

๐Ÿš€ My Open Source Projects
- Time Period Library ๐Ÿ‘‰ GitHub
- Payroll Engine ๐Ÿ‘‰ GitHub

Feedback and contributions are welcome.



Comments and Discussions

 
SuggestionUnclear how this adds any value Pin
Willem Meints2-Feb-19 7:26
Willem Meints2-Feb-19 7:26 
QuestionInterfaces and responses Pin
John Brett12-Mar-17 23:19
John Brett12-Mar-17 23:19 
AnswerRe: Interfaces and responses Pin
Jani Giannoudis13-Mar-17 9:38
mvaJani Giannoudis13-Mar-17 9:38 
Question... not enough description and explanation Pin
BillWoodruff12-Mar-17 20:35
professionalBillWoodruff12-Mar-17 20:35 
How does the proposed use of a high-level abstraction, the "Policy" pattern, result in better, clearer, more useful software ?

Business "rules" are most often complex collections of dependent conditions and results/actions: I don't see any actual "rule" being demonstrated here.
ยซWhen I consider my brief span of life, swallowed up in an eternity before and after, the little space I fill, and even can see, engulfed in the infinite immensity of spaces of which I am ignorant, and which know me not, I am frightened, and am astonished at being here rather than there; for there is no reason why here rather than there, now rather than then.ยป Blaise Pascal

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.