Yesterday we have finished Rules Framework implementation. Functional requirements are quite simple: It should be possible to execute set of rules on events in entity life-cycle (Add, Update, Delete, Change State). For example:
- When someone adds user story into TP, it may be assigned on less burden developer
- When someone changes state of bug (from Open to Fixed), assigned tester should be notified about that
Let's invent the wheel (I mean solution for Rule Framework).
We have entities hierarchy (some entities shown on the left of the picture below). Entity class is a base class for all entities. It contains common operations like Save/Delete/Retrieve, assertions and changes detection. From Rules Framework point of view we need to execute several rules when entity saved into database, so all we need is invoke rules from Save() method. OK so far, now we need to think about events domain model.
Some knowledge that we have about rules:
- Event and Rule nouns. We often used them in discussion, so most likely they are entities.
- Each entity may have own set of rules
- All entities have Add, Update and Delete events and several entities have Change State event.
- For each state we may apply different set of rules, this complicates things
- We have different rules that will be executed uniformly. This leads to interface with Execute method. Let's call it IRuleAction. Each rule with business logic will be an implementation of IRuleAction.
So let’s coin some terms. Event may be applied for different entity, for different state and each event has one of four types (Add, Update, Delete, Change State). Rule is something that relates to event and may be executed. Naturally, we came up with two classes: TpEvent and Rule (Tp prefix required to avoid conflicts with C# event keyword). TpEvent has a collection of rules (look at the right corner of diagram). TpEvent and Rule are persisted classes.
Rule class in quite general and does not have any specific behavior. But how we will have different rules in this case? In other words, how we will bind IRuleAction implementation to Rule class? Well, Rule has RuleClass property, which gives rule some uniqueness. Nothing special, RuleClass stores name of the custom rule class that contains specific behavior. As you rightly guessed, we will use reflection for rules execution.
That’s (almost) it! Now we understand that we need something that will initiate rules execution. We will call this class RuleManager. It will be instantiated in appropriate place (for example, Save() method) and invoke all events for current ActionType and entity.
For example, we adding new user story:
- Instantiate RuleManager in Save method
- Pass this new user story and Add action type into RuleManager
- Rule manager will load TpEvent from database with all Rules, instantiate IRuleAction using Rule.RuleClass property and reflection magic and Execute this instantiated IRuleAction implementation.
1. RuleManager usage:
ActionTypeEnum actionType = entity.IsNew ? ActionTypeEnum.Add : ActionTypeEnum.Update; RuleManager ruleManager = new RuleManager(entity); ruleManager.RaiseEvent(actionType);
2. Load event from database and execute each rule
TpEvent tpEvent = TpEvent.FindEvent(entity.GetType(), actionType); foreach (Rule rule in tpEvent.Rules) Execute(rule);
3. Inside Execute method. Create instance and really execute the rule
Type ruleActionType = Type.GetType(rule.RuleClass); IRuleAction ruleAction = (IRuleAction) Activator.CreateInstance(ruleActionType); ruleAction.Execute(entity, rule);
Very nice class diagram:
So what are the advantages of such implementation? I can’t find them… Just kidding :) First, we almost leave domain model untouched. Few lines of code have been added into Entity.Save() and Entity.Delete() methods. So we have low coupling. Second, we can add new rule very easily: all we need is implement IRuleAction interface with single Execute(Entity entity, Rule rule) method. Third, rules are written on C# which is definitely good.
Another interesting part is UI for Entity->Event->Rules setting, but this is a different story.