Click here to Skip to main content
15,906,097 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.1K   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 

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.