this post is the second of short series which cover the task of creating simple Rule Engine using MEF technology (part 1).
the post code is available for download here
Prerequisite
this post assume basic understanding of the MEF technology (for MEF introduction read this post)
The Contracts
as any decoupled framework the Rule Engine rely on contract.
the role of the MEF technology is to composite the part that export (publish) the contract into the contract consumer (import).
it is a matching game where the consumer declare its needs and MEF try to satisfy those needs with the available exported parts.
How do we define the Contract?
traditionally we used to use interface and abstract class with contract.
MEF contract is more flexible and it include the following options:
- Interface (the traditional way)
- Type (it is not necessarily best practice but it legal)
- Delegate (the contract can be define at the method level)
- String (decoration)
The Rule Engine Contracts
The rule contract assume single input (generic type) and return Boolean indication for pass/fail.
The Rule Engine is using 2 contract strategies:
- Delegate level contract
- Interface level contract
Rule's Interface contract
public interface IRule<TInput>
{
string Title { get; }
string Description { get; }
Predicate<TInput> RulePredicate { get; }
}
looking at the contract we can see that this contract has 2 different responsibilities:
- Title and Description properties is basically rule decoration that may use by tools like Rule Simulation (which we discuss latter on this series)
- RulePredicate is the actually rule action (this is where the pass/fail logic goes)
(we may separate this contract into IRule<TInput> and IRuleDecoration<TInput>:IRule<TInput> for better separation of concern).
Rule's Delegate contract
the rule delegate is using the .NET Predicate<T>.
the predicate will get input and return pass/fail indication.
also this contract having the entire rule runtime functionality its lack the metadata for the tooling like Rule Simulation.
The needs of Metadata
modern software development is taking into account the entire software ecosystem including tools friendly aspects.
the ability to visual the simulation of specific input can be a life saver while using large and complex rules bank.
it will ease the debugging process, reduce production errors and users will have experience administrating the rules.
The motivation of using Delegate contract along with the Interface one
the down side of using Interface contract is encapsulation issue.
each of our interface contract must be declare on separate class (we can mitigate it by having grouping metadata).
by using Delegate contract we can encapsulate multiple rules that sharing the same logical context into single class.
How to attach Metadata to delegate?
MEF does support the notion of metadata, one way of decorating MEF parts with metadata is by using attribute technology (MEF doesn't force us into the attribute model but it is very convenient).
Step 1: is to define the metadata contract (we will use strongly typed metadata)
public interface IRuleMetadata
{
string Title { get; }
string Description { get; }
}
Step 2: define attribute decoration for our metadata definition
[MetadataAttribute] // Decorate this attribute as metadata
[AttributeUsage(AttributeTargets.Method)]
public class ExportRuleAttribute : ExportAttribute, IRuleMetadata
{
public ExportRuleAttribute (Type exportType): base (exportType) {}
public string Title { get; private set; }
public string Description { get; set; }
}
notice that the attribute inherit from ExportAttribute (which used to publish parts as exportable).
Step 3: exporting (publishing) method as a rule
[ExportRule (typeof(Predicate<DateTime>), Title="FirstMonthHalf",
Description="Valid for the first half of the month")]
public static bool FirstMonthHalf (DateTime input)
{
return input.Day < 16;
}
this snippet decorate rule for DataTime input
Step 4: consuming the Rules
the following class is having 2 private properties: (MEF can target private method)
- Importing the Interface contract
- Importing the Delegate contact
and one public property that expose the union of both technique (hiding the actual implementation from the class consumer).
public class RulesExecuter<T>
{
[ImportMany (AllowRecomposition=true,
RequiredCreationPolicy=CreationPolicy.Shared)]
private IEnumerable<Lazy<Predicate<T>, IRuleMetadata>>
RuleMethods { get; set; }
[ImportMany (AllowRecomposition=true,
RequiredCreationPolicy=CreationPolicy.Shared)]
private IEnumerable<Lazy<IRule<T>>>
RuleInstances { get; set; }
public IEnumerable<Rule<T>> Rules {...}
}
the following snippet is the implementation of the Rules property (Rule<T> is class that defined to present rule data)
var rulesMtd = from item in RuleMethods
select new Rule<T>
{
Invoke = item.Value,
Title = item.Metadata.Title,
Description = item.Metadata.Description
};
var rulesIns = from item in RuleInstances
select new Rule<T>
{
Invoke = item.Value.RulePredicate,
Title = item.Value.Title,
Description = item.Value.Description
};
var rules = rulesMtd.Union(rulesIns);
Summary
we saw 2 different techniques for publishing extensions:
- using traditional Interfaces
- using Delegate
we learn to use metadata in order of having better support for tooling.
the post code is available for download here
This post is the first of short series which will cover the task of creating simple Rule Engine using MEF technology.
from technical perspective the series will talk about the following techniques:
the post code is available for download here
Prerequisite
this post assume basic understanding of the MEF technology (for MEF introduction read this post)
Part 1
part 1 will focus on filtering MEF results using custom catalog
Filtering MEF results
MEF architecture is using catalogs (ComposablePartCatalog) to discover MEF parts.
the catalogs responsibility is to locate the parts using specific strategy.
The idea behind the filter Catalog
the filter catalog will act as wrapper for other catalogs (this way it is not restricted for specific discovery strategy).
the relation between the filter catalog to the concrete catalog resemble the relation between BufferedStream and the concrete stream,
the concrete catalog is responsible for the discovery and the filter catalog is responsible for filtering.
Analyzing the filter catalog code
The heart of the catalog filtering using Where clause (LINQ) on the inner catalog's Parts.
The filter catalog override 2 methods
- Parts: redirect to the inner catalog parts
- GetExports: filter the export definitions
public class FilteredComposablePartCatalog : ComposablePartCatalog {
// the inner catalog parts
private readonly IQueryable<ComposablePartDefinition> _parts;
// the current filter
private Predicate<ExportDefinition> _exportDefFilter;
public FilteredComposablePartCatalog(ComposablePartCatalog catalog,
Predicate<ExportDefinition> exportDefFilter) {
_parts = catalog.Parts;
_exportDefFilter = exportDefFilter;
}
public override IQueryable<ComposablePartDefinition> Parts {
get { return _parts; } // redirect to the inner catalog parts
}
// filter the export definitions
public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>>
GetExports(ImportDefinition definition) {
var exports = from expDefTuple in base.GetExports(definition)
let expDef = expDefTuple.Item2
where _exportDefFilter(expDef)
select expDefTuple;
return exports;
}
}
Using the filtered catalog
for usability purpose i added extension method called AssignFilter
var dirCatalog = new DirectoryCatalog (PLUG_INS_FOLDER);
var catalogFiltered = dirCatalog.AssignFilter(
expDef => (bool)expDef.Metadata["CustomMetadata"] == true);
so as a consumer the experience is quite similar to adding any other catalog.
the filter catalog hide the filtering implementation and all we have to do is to supply predicate.
Solution structure
The solution structure contain the following projects:
- MefRuleEngine.Common (contain contracts, attributes, enums and helpers)
- FilteredComposablePartCatalog (the code of the filter catalog)
- MefRuleEngine.Plugins.DebugEnvironment (rules implementation for debug environment)
- MefRuleEngine.Plugins.ProductionEnvironment (rules implementation for production environment)
- ComponentModel.Initialization (will be part of MEF in the future)
Summary
in this part of the series we discuss the filtering technique,
the following parts will focus on using metadata and adding export attribute on methods.
we will also speak about the current implementation of the rules engine and on how to visualize simulation for specific inputs.
The post code is available for download here