I've been using the broken rules scheme in my past few projects and I like the way it raises the visibility of rules by making them a first class citizen of the domain. Rules will no longer be buried with the other "stuff" of a business transaction. No sir, these rules have names!
Take for example, a couple of rules around issuing inventory for a title insurance company:
- Provide the ability for management to block requests for new inventory from a specific agency.
- Provide the ability to stop issuing inventory to agencies whose contract has been canceled.
- Provide the ability to stop issuing inventory to agencies that have failed to meet minimum reporting requirements .
- Provide the ability to stop agencies from being issued inventory they are not authorized for.
Speaking close to the language of the domain, these rules are grouped as a set. And for the sake of brevity, let us assume that these rules are independent of each other:
private List<ValidationRuleBase> GetRulesForIssuingInventory(Agent agent, PolicyType requestedPolicyType, List<PolicyType> policyTypesAuthorizedForTheAgent)
{
var rules = new List<ValidationRuleBase>();
rules.Add(new ShouldNot_Issue_To_Agents_That_Has_Been_Blocked_To_RequestInventory(agent));
rules.Add(new ShouldNot_Issue_To_Cancelled_Agents(agent));
rules.Add(new ShouldNot_Issue_To_Agents_That_Has_Not_Met_Minimum_Reporting_Requirements(agent));
rules.Add(new Agent_ShouldBe_Authorized_To_Order_The_Requested_PolicyTypes(agent,requestedPolicyType, policyTypesAuthorizedForTheAgent));
return rules;
}
In this particular scenario, this set of rules hangs off the domain object 'Order' and is checked when a request for inventory is received.
public void PlaceAnOrder(Order order)
{
List<ValidationRuleBase> rules;
List<ValidationRuleBase> brokenRules;
rules = order.GetRulesForIssuingInventory(order.Agent, order.PolicyType, order.Agent.AuthorizedPolicyTypes);
brokenRules = GetBrokenRules(rules);
if (brokenRules.Count>0)
{
throw new BusinessRuleException(brokenRules);
}
// continue with the rest of the placing an order transaction
}
An example of a rule implementation is:
public class Agent_ShouldBe_Authorized_To_Order_The_Requested_PolicyTypes : ValidationRuleBase
{
private Agent agent;
private PolicyType requestedPolicyType;
private List<PolicyType> policyTypesAuthorizedForTheAgent;
public Agent_ShouldBe_Authorized_To_Order_The_Requested_PolicyTypes(Agent agent, PolicyType requestedPolicyType, List<PolicyType> policyTypesAuthorizedForTheAgent)
{
this.agent = agent;
this.requestedPolicyType = requestedPolicyType;
this.policyTypesAuthorizedForTheAgent = policyTypesAuthorizedForTheAgent;
}
override public bool IsValid
{
get
{
return this.policyTypesAuthorizedForTheAgent.Contains(this.requestedPolicyType);
}
}
public override string Message
{
get
{
return String.Format("Policy Type {0} is not in the list of policy types Agent {1} can order.",this.requestedPolicyType.Name,this.agent.Name);
}
}
}
Note in the IsValid() override that the heart of the rule is for the most part just plain logic and is devoid of UI and persistence concerns. This leads to a more focused expression of the rule.