65.9K
CodeProject is changing. Read more.
Home

Domain Policy for Domain-Driven Design

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (6 votes)

Mar 11, 2017

CPOL
viewsIcon

17224

downloadIcon

84

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:

// ------------------------------------------------------------------------
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:

// ------------------------------------------------------------------------
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:

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