Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Domain Policy for Domain-Driven Design

4.58/5 (6 votes)
13 Mar 2017CPOL 17K   76  
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)