15 July, 2006 / Rules Framework Architecture. Unobtrusive Way.

2 comments

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:

  1. Instantiate RuleManager in Save method
  2. Pass this new user story and Add action type into RuleManager
  3. 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.

2 Comments:

At July 19, 2006 7:45 PM, Anonymous Anonymous said...

In what way do you store your busines rules. Is it XML (RuleML)? Do you have them in DB, or do you hard code them? And how does the "Execute()" method calls this rules?

Thanks a lot. I'm working on a rule engine for my company. They are simple rules but I have to come up with a solution implemented before the end of this week. I've seen NxBRE, but the rule definition language is a little "dark" for me :|.

Thanks again.

GJordan

 
At July 20, 2006 9:56 PM, Blogger TargetProcess said...

We hardcode specific rules in C#. For example, we have AssignOn rule that represented by RoleAssigner class. We think than it is the most natural way and it can be easily understood by any .NET developer.

Here is Execute method in RuleManager



public void Execute(Rule rule)
{
Type ruleActionType = Type.GetType(rule.RuleClass);

if (ReferenceEquals(ruleActionType, null))
throw new ApplicationException(string.Format("Type {0} could not be found", rule.RuleClass));

IRuleAction ruleAction = (IRuleAction) Activator.CreateInstance(ruleActionType);

ruleAction.Execute(m_Entity, rule);
}




So we use reflection to instantiate and execute the rule. And here is RoleAssigner rule



[ForEvents(ActionTypeEnum.ChangeState)]
public class RoleAssigner : IRuleAction
{
public void Execute(Entity entity, Rule rule)
{
IAssignable assignable = (IAssignable) entity;

// Assign user on entity
Team team = new Team();
User user = FindUserForRole(assignable.Project, rule.Role);

if (user != null)
{
team.User = user;
team.Actor = assignable.EntityState.Actor;
assignable.Teams.Add(team);
((Entity) assignable).Save();
}
}

public string Name
{
get { return "Assign On"; }
}

public string Description
{
get { return "Assign entity on user with specified role"; }
}

private User FindUserForRole(Project project, Role role)
{
UserCollection users = new UserCollection();

foreach (ProjectMember projectMember in project.ProjectMembers)
{
if (projectMember.Role == role)
users.Add(projectMember.User);
}

if (users.Count == 0)
return null;


return FindUserWithMinimumAssignedEffort(users);
}


private static User FindUserWithMinimumAssignedEffort(UserCollection users)
{
User minUser = users[0];
foreach (User user in users)
{
if (user.TotalAssignedEffort < minUser.TotalAssignedEffort)
minUser = user;
}

return minUser;
}
}

 

Post a Comment

Links to this post:

Create a Link

<< Home

 

We are developing TargetProcess agile project management software and blogging about our progress.

Subscribe to the RSS feed
Stay tuned by having the latest updates via RSS
Follow TargetProcess on Twitter
Get in touch with our team

Try TargetProcess
TargetProcess quick tour