Click here to Skip to main content
15,923,164 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.3K   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 
I'm having a hard time understanding the rather technical approach to what is explained as a business problem in the book written by Eric Evans.

The policy pattern enables you to turn a complex set of rules into a single object called a policy. The example Eric gives shows a long list of conditions that need to be met in order for cargo to be booked for a voyage. In business terms, this set of rules could be named the booking policy. In other words the way the business defines when the book cargo or when a voyage is filled to maximum capacity.

The way you explain it in your article turns the business term Policy into a rather complicated technically oriented piece of technology. Your business is not going to understand the high-level abstraction you introduced here. And neither do your teammates in about 6 weeks.

I would suggest keeping things very simple when working with domain driven design.

C#
public class OverbookingPolicy 
{
    public bool IsAllowed(Cargo cargo, Voyage) 
    {
        // Validate the rules
        return (cargo.Size + voyage.BookedCargoSize) <= (voyage.Capacity * 1.1);
    }
}


This policy explains to the business user how your application handles their cargo capacity policy.
You can easily raise an exception for the case that this check fails and tell the user what is going on.

It's also much easier for your teammates to understand in the next 6 weeks. Moreover, it's easier to test too. I can plug in different examples of voyages and pieces cargo in my tests to check if the policy validates. Then I can write a different test to make sure that if the cargo meets the criteria of the policy it is added to the booked cargo on the voyage. I can add another test that can easily check if my cargo isn't booked when the policy isn't met.
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.