|
|
February 8th, 2010
As of today (February 2010), the story of Java/WCF interoperability is fair. That wasn’t always the case. In the past, I’ve struggled to get Java and .NET to play nice. Today, I was able to make a .NET WCF client talk to a Java CXF web service with just a little coaxing. Here’s how I did it.
Contract first The first step to successful interoperability is to define the contract. Somehow you need to generate the WSDL, and you need to tightly control what it looks like. Use tools to help you, but keep a close eye on what those tools do.
I started with a WCF service contract. This is a .NET interface that uses the [ServiceContract] and [OperationContract] attributes. Put this interface and all of the data types it uses into a class library project. Here’s an example:
[ServiceContract(Namespace = "http://correspondence.updatecontrols.com")]
public interface ISynchronizationService
{
[OperationContract]
FactTree Get(FactTree pivotTree, long pivotId, long timestamp);
[OperationContract]
void Post(FactTree messageBody);
}
The FactTree data type used by this interface is decorated with the [DataContract] and [DataMember] attributes.
[DataContract(Namespace = "http://correspondence.updatecontrols.com")]
public class FactTree
{
[DataMember]
public long DatabaseId { get; set; }
[DataMember]
public List<Fact> Facts { get; set; }
[DataMember]
public List<FactRole> Roles { get; set; }
[DataMember]
public List<FactType> Types { get; set; }
}
Create a WCF service
Even though we want to end up with a Java web service, an intermediate step is to implement this service in WCF.
- Create a new project in Visual Studio using the template Visual C#: Web: WCF Service Application.
- Add a reference to the class library that defines the interface.
- Change the name of the generated service from Service1 to something meaningful.
- Delete the generated IService1 interface.
- Use your own interface instead.
- Add a [ServiceBehavior] attribute to set the Namespace.
At this point, the service looks something like this:
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in Web.config and in the associated .svc file.
[ServiceBehavior(Namespace = "http://correspondence.updatecontrols.com")]
public class SynchronizationService : ISynchronizationService
{
public FactTree Get(FactTree pivotTree, long pivotId, long timestamp)
{
throw new NotImplementedException();
}
public void Post(FactTree messageBody)
{
throw new NotImplementedException();
}
}
Like the comment says, we need to edit the svc file and the web.config. Right-click the svc file in the project tree and select “View Markup”. Change the Service attribute to the fully qualified name of the service class.
<%@ ServiceHost Language="C#" Debug="true"
Service="UpdateControls.Correspondence.WebService.SynchronizationService"
CodeBehind="SynchronizationService.svc.cs" %>
The web.config change is slightly more complicated. There’s a lot of junk in web.config that you don’t need to worry about. The section you want is all the way at the bottom. Look for the <service> tag. It has two attributes: name and behaviorConfiguration. Also look for the <endpoint> tag right below it. It has three attributes: address, binding, and contract.
- Change the service name to the fully qualified name of your service class.
- Change the endpoint binding from wsHttpBinding to basicHttpBinding.
- Change the endpoint contract from IService1 to the fully qualified name of your interface.
Here’s a trick to getting the fully qualified names. Delete the text between the quotes of the attributes. Open the Class View by hitting Ctrl+Shift+C in Visual Studio. Expand the tree to find your service class and interface. Drag them onto the web.config file between the quotes.
You can also change the name of the service behavior, but that’s not necessary for this intermediate step.
Examine the WSDL
These steps ensure that we have nice clean WSDL to work from. Take a look at it by running your WCF service application. A directory listing will open in the browser. Click on the svc file. If you get a yellow screen, please double-check your steps.
Click on the link to see the WSDL you’ve created. Different browsers react differently to raw XML. IE and Firefox will show it to you, but Chrome will give you a blank screen. You’ll have to view source to see the WSDL in Chrome.
On this first page, you’ll see all of the input and output messages, and the operations, and the service itself. Double-check that the service uses binding="i0:BasicHttpBinding_...".
Hack the url to look at more detailed information. Change the query string to “?wsdl=wsdl0” to see the declaration for the binding. It uses “http://schemas.xmlsoap.org/soap/http” with the “document” style.
Hack the url again with “?xsd=xsd0” to see the data types. You should recognize these data types as the ones you wrote in C#. Notice that it turns all of your List<T>s into ArrayOfTs. When we import these into Java, they will become classes containing List<T>.
Create the Java contract project
Create a Java project in your favorite IDE (mine is Eclipse). Open a command prompt and go to the source directory of that project (probably ends in “src”). Download Apache CXF and unzip it to your hard drive (mine is in “c:\apache-cxf-2.2.6”).
Go back to the first WSDL page, the one with the “?wsdl” query string. This is the URL that we are going to generate Java files from. Copy this URL and use it at the command line:
\apache-cxf-2.2.6\bin\wsdl2java.bat http://localhost:3642/SynchronizationService.svc?wsdl
CXF will generate a bunch of class files. Most will be in a package derived from your namespace. One will be in “com.microsoft.schemas._2003._10.serialization”. If you find one in org.tempuri package, you forgot a Namespace setting in one of your attributes. These class files are decorated with enough annotations to make them compatible with the WCF service.
Create the Java service project
Although you could put your service implementation and contract in the same project, I prefer to keep them separate. You can use the contract project to write a different service implementation, or even to write a client.
Create a new Dynamic Web Project. Add to the new project a reference to the contact project. You will also need to add this reference to the Java EE Module Dependencies in the project properties. Otherwise it won’t copy the contract jar file to the service lib directory, resulting in a NoClassDefFoundError at runtime. Then add a class that implements the service contract. Copy the @WebService annotation from the interface to the class. The service looks something like this:
@WebService(targetNamespace = "http://correspondence.updatecontrols.com", name = "ISynchronizationService")
public class SynchronizationService implements ISynchronizationService {
@Override
public FactTree get(FactTree pivotTree, Long pivotId, Long timestamp) {
// TODO Auto-generated method stub
return null;
}
@Override
public void post(FactTree messageBody) {
// TODO Auto-generated method stub
}
}
The service project needs the CXF jar files. Copy them from the CXF install folder (C:\apache-cxf-2.2.6\lib) into the project’s library folder (WebContent\WEB-INF\lib). This is the minimal set that you will need:
- jaxb-api-2.1.jar
- jaxb-impl-2.1.12.jar
- wsdl4j-1.6.2.jar
- XmlSchema-1.4.5.jar
- cxf-2.2.6.jar
Now we need to publish this web service as a servlet. The quickest way to do that is to derive a class from CXFNonSpringServlet. Right-click the project and select "New: Servlet”. Change the servlet base class to “org.apache.cxf.transport.servlet.CXFNonSpringServlet”. Uncheck the boxes to implement doGet and doPost. The base class handles those for you. Once the class is created, override the loadBus method.
package com.updatecontrols.correspondence.service;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.xml.ws.Endpoint;
import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
public class SynchronizationServlet extends CXFNonSpringServlet {
@Override
public void loadBus(ServletConfig servletConfig) throws ServletException {
super.loadBus(servletConfig);
Endpoint.publish("/SynchronizationService", new SynchronizationService());
}
}
Open the web.xml file. You will notice that a servlet mapping was created for you. This mapping is set up to handle URLs that directly address the servlet, but the CXF servlet adds the service name to the URL. Add a “/*” to the end of the URL pattern to direct all such addresses to the servlet.
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>correspondence_sync_service</display-name>
<servlet>
<description>
</description>
<display-name>SynchronizationServlet</display-name>
<servlet-name>SynchronizationServlet</servlet-name>
<servlet-class>
com.updatecontrols.correspondence.service.SynchronizationServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SynchronizationServlet</servlet-name>
<url-pattern>/SynchronizationServlet/*</url-pattern>
</servlet-mapping>
</web-app>
Run the project in Tomcat to make sure the servlet is published correctly. Point a browser at the servlet (in my case http://localhost:8080/correspondence_sync_service/SynchronizationServlet) and you should see a listing of available SOAP services. Append the service name to the URL (http://localhost:8080/correspondence_sync_service/SynchronizationServlet/SynchronizationService) and you will get a 500 error. If you get a 404, you haven’t modified the web.xml file correctly.
Create a WCF client
The last step is the easiest. Since we started by creating a WCF service contract, we can ask WCF to create a client proxy. I documented this technique in .NET to .NET web services without WSDL. It turns out that this trick works equally well for .NET to Java web services.
Add an endpoint to the app.config of your client. The URL should be the servlet name followed by the service name. For example:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="SynchronizationServiceSoapBinding" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8080/correspondence_sync_service/SynchronizationServlet/SynchronizationService"
binding="basicHttpBinding" bindingConfiguration="SynchronizationServiceSoapBinding"
contract="UpdateControls.Correspondence.WebService.Contract.ISynchronizationService" name="SynchronizationService" />
</client>
</system.serviceModel>
</configuration>
And with that you have a .NET WCF client communicating with a Java CXF server. I expect that similar strategies could be used to go the other direction, although I haven’t tried yet.
Posted in C#, Java | No Comments »
January 27th, 2010
Correspondence is a library for creating collaborative smart client applications in .NET. When you express a model in Correspondence, it provides three things:
- Storage
- UI updates
- Synchronization
This open source library includes a demo application to illustrate its collaborative capabilities. Correspondence Reversi is a WPF rendition of a popular two player game. Download the client to play against a friend, or to randomly join in a game with a stranger. Download the source code to learn how Correspondence makes collaboration easy.
Storage
Applications typically store their data in a relational database. But they act upon that data by loading objects into memory. To bride the gap, application designers can choose from several object-relational mappers (ORMs).
The problem with the ORM approach is that it requires the application designer to express their model three times:
- Database schema
- Objects
- Mapping configuration
Keeping these three in synch becomes a maintenance task each time the model changes. And deploying a new version requires that the data be migrated to the new schema.
Correspondence is not an ORM. The application model is not reflected in a relational database schema. When the model changes, only the objects are changed. The schema remains consistent. This allows for new versions to be deployed without changing the database or migrating data. And it eliminates the need for mapping configuration, as the library stores all models the same way.
UI updates
Correspondence is built on top of Update Controls, a library for keeping UI controls up-to-date. While most UI update libraries require you to manage your own dependencies, Update Controls discovers them for you and manages them on your behalf. The only thing that Update Controls requires is that your model alert it when a property is accessed or modified.
Correspondence takes on the responsibility of notifying Update Controls. A Correspondence model can be bound to a Winforms or WPF user interface – even through an intermediate View Model – to provide automatic dependency discovery and change notification. A Correspondence application developer will never see INotifyPropertyChanged or ObservableCollection.
Synchronization
By far the most compelling feature of Correspondence is that it automatically synchronizes a data model among clients. Two or more people collaborating on the same data on different machines will automatically see each other’s changes. The automatic UI updates provided by Update Controls ensure that changes made on one machine automatically appear on the screen of another.
Most smart client applications switch from off-line mode to on-line mode based on the availability of the network. While on-line, smart clients communicate changes that the user makes with a server. It runs queries on the server to bring back information that the user wants to see. While off-line, they switch into a mode where data storage and queries are performed locally. Typically, smart client synchronization occurs during the switch between modes.
Correspondence works differently. It offers a consistent programming model whether the network is available or not. Objects created in Correspondence are stored locally, regardless of network availability. A background thread constantly synchronizes the local storage with a server when available, and silently waits when it is not. By eliminating the switch between modes, Correspondence simplifies the task of smart-client development, and improves the end-user experience.
Correspondence Reversi synchronizes with a cloud service running in Windows Azure. This service collects data from each client, and redistributes that data to other clients who need it. Two people playing a game together will see each other’s moves. But they will not see any of the traffic from other games. This is not a special feature of the Reversi game model. This is a feature of Correspondence. A different model will be synchronized just as intelligently, and will work with the same synchronization service. There is nothing application-specific about the cloud service.
Please download the client and play against your friends. Then explore the source code and see what you can do with Correspondence.
Posted in Synchronization, Update Controls | No Comments »
January 18th, 2010
The sum of an empty set is zero. This is a well-known mathematical truth that the .NET Framework understands.
int sum = Enumerable.Empty<int>().Sum();
Assert.AreEqual(0, sum);
Entity Framework 1.0, however, doesn’t work like that. If you try to sum an empty set, it will throw an exception.
OrderEntities entities = new OrderEntities();
int totalQuantity = entities.OrderLine
.Where(orderLine => false)
.Sum(orderLine => orderLine.Quantity);
Assert.AreEqual(0, totalQuantity);
System.InvalidOperationException: The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
Here’s my solution
Let Sum return a nullable integer. Then, if it is null, coerce it back to zero.
OrderEntities entities = new OrderEntities();
int totalQuantity = entities.OrderLine
.Where(orderLine => false)
.Sum(orderLine => (int?)orderLine.Quantity) ?? 0;
Assert.AreEqual(0, totalQuantity);
You know that Quantity cannot be null. I know that Quantity cannot be null. But we have to trick Entity Framework into thinking that it could be null. By casting the integer to int?, you select the Sum overload that allows for a null. Since Entity Framework doesn’t do the right thing with that null, then we’ll do it ourselves.
Posted in Entity Framework | 1 Comment »
January 16th, 2010
The “We Are Microsoft” charity coding weekend is an opportunity for developers to donate their time to local charities. It is organized primarily by Toi Wright. Matt Lagrotte of Verio has graciously provided web hosting for all of the charities. Without their support, and the support of Microsoft and other sponsors, 20 charities would go without the IT support that they need.
Our charity is Second Wind Dallas. They find sponsors for local families in need. Schools identify those families, and a committee determines which sponsor will adopt which family. Several volunteers coordinate the communications among schools, families, and sponsors. Right now, the system is run completely by phone, email, and Excel. They need help.
We are building an online database to coordinate this information. Volunteer assignments, family referrals, and sponsor adoptions all change year after year. As a result, we are developing this as a historic model.
The primary function of the site is data entry. An administrator will set up the volunteer, school, family, and sponsor records. They will manage the assignments of volunteers to schools, the school referrals of families, and the adoption of families by sponsors.
The secondary function of the site is notification. A volunteer will be reminded to contact sponsors for donations. They will pick up those donations at the school and deliver them to the families. They will be reminded to send a thank you to each sponsor for those donations.
We are building this system using ASP .NET Web Forms, SQL Server 2005, and Entity Framework. We will have it done within the next 48 hours. And when we are done, Second Wind will have a much more manageable process.
Posted in Charity | No Comments »
December 29th, 2009
Download the source code and follow along.
This post is part of a series:
- The customer is not you.
- If-then is backwards.
- Condition and outcome templates.
- Condition composition.
- The rule model.
We’ve defined interfaces for the various types of rules in our point-of-sale system.
public interface IFreeItemsRule
{
IEnumerable<Item> GetFreeItems(Check check);
}
public interface IDiscountRule
{
IEnumerable<Discount> GetDiscounts(Check check);
}
public interface ICouponRule
{
IEnumerable<Coupon> GetCoupons(Check check);
}
These interfaces assume that we can execute rules based entirely on the check. While this is true for the majority of our example rules, this is not true for the frequent diner discount.
- m dollars for every n prior visits of minimum dollars or more that have not already been rewarded.
This rule needs access to the history of visits. So instead of providing a simple Check, we’ll provide a richer object. Inspired by the Model-View-ViewModel pattern, I’ve decided to call this rich object a Rule Model.
The RuleModel class
The Rule Model has access not only to the Check that the user is currently editing, but also to a repository. It can query the repository for any historical information that a rule may require.
public class RuleModel
{
private Check _check;
private IRepository _repository;
public RuleModel(Check check, IRepository repository)
{
_check = check;
_repository = repository;
}
public Check Check
{
get { return _check; }
}
}
For example, our frequent diner rule needs access to the history of visits.
public int GetPriorVisitCount(
FrequentDiner frequentDiner,
decimal minimumAmountPerVisit)
{
int totalPriorVisitCount = _repository
.GetChecks(check => check.FrequentDiner == frequentDiner &&
check.TotalBeforeDiscounts > minimumAmountPerVisit)
.Count();
int discountedPriorVisitCount = _repository
.GetChecks(check => check.FrequentDiner == frequentDiner)
.SelectMany(check => check.Discounts)
.OfType<PriorVisitDiscount>()
.Sum(discount => discount.VisitsCounted);
return totalPriorVisitCount - discountedPriorVisitCount;
}
The RuleModel can satisfy the demands of the frequent diner discount rule. It can count the number of prior visits by this frequent diner, and it can count the number of prior visits that have already been rewarded. The difference is needed to determine whether the frequent diner has earned a new discount.
Let history decide
When the frequent diner earns a discount, we record that reward on the new check. We use a derivative of the Discount class that captures the number of visits counted.
public class PriorVisitDiscount : Discount
{
private int _visitsCounted;
public PriorVisitDiscount(
string description,
decimal amount,
int visitsCounted)
: base(description, amount)
{
_visitsCounted = visitsCounted;
}
public int VisitsCounted
{
get { return _visitsCounted; }
}
}
By capturing this information in the discount itself, we ensure that our action is atomic. The very act of awarding the discount deducts the visits required to earn it. Refer back to the GetPriorVisitCount method to see how this historical information is used.
By capturing data historically rather than keeping a separate tally, we can greatly reduce the likelihood of a defect. And if there ever is a question about discounts awarded (or not awarded), the entire history is there to be audited.
Rules are independent
Download the source code and examine the tests. You’ll find that each of the example rules that we originally promised the customer are working as expected. You will also find that they are working independently of one another. As we discussed at the beginning of this series, independence is important for understanding the behavior of the rule engine. If the rules depend upon one another, it becomes difficult to explain why they behave the way that they do.
An example of independence can be found if we combine a free item rule with a coupon rule.
- Buy 5 burgers, get 2 ice creams free.
- Buy an ice cream and get a coupon for half off your next visit.
Take a look at the IndependentRulesTest to see how we express these rules:
[TestInitialize]
public void Initialize()
{
InitializeService(
new List<IDiscountRule>(),
new List<IFreeItemsRule>() {
new BuyOneGetOneRule(5, BurgerId, 2, IceCreamId) },
new List<ICouponRule>() {
new TriggerItemCouponRule(1, IceCreamId, "Half off") }
);
}
You might worry that the customer would be awarded free coupons for the two free ice creams. But fear not:
[TestMethod]
public void DontAwardACouponForFreeIceCream()
{
// Buy five burgers.
Check check = new Check();
check.AddItem(BurgerId).Quantity = 5;
Check outputCheck = _service.ExecuteRules(check);
// No coupons awarded.
Pred.Assert(outputCheck.Coupons, Is.Empty<Coupon>());
}
As you can see, no coupons are awarded when the customer buys 5 burgers. This is because the rules are independent of one another. Each rule is evaluated against the original check. Rules are never permitted to modify the check. No rule depends upon the results of another.
Conclusion
The next time your customer asks for customizable business logic, consider what we’ve gone over here. You could use a general purpose off the shelf rules engine, but that is probably asking too much of your customer. They would be forced to think like a programmer. Instead, you can provide specific goal-directed rules that they can parameterize. Use set unions to compose rules, and be sure that each rule runs independently of the others. The result will be a simple, powerful, and extensible custom rules engine that will empower, rather than confuse, your customer.
Posted in Rules engine | 2 Comments »
December 19th, 2009
Download the source code and follow along.
This post is part of a series:
- The customer is not you.
- If-then is backwards.
- Condition and outcome templates.
- Condition composition.
- The rule model.
So far, we have defined rules by using concrete examples. We turned them around to express them in terms of the goal. And we coded one interface per outcome, one class per condition.
With this code, we could easily write a system that applies one rule.
public class PointOfSaleService
{
private IDiscountRule _discountRule;
public PointOfSaleService(IDiscountRule discountRule)
{
_discountRule = discountRule;
}
public CheckWithDiscounts AddDiscounts(Check check)
{
return CheckWithDiscounts.Create(check, _discountRule.GetDiscounts(check));
}
}
Notice that the rule does not modify the original check. If it did, we could not safely execute the rule again. Instead, it returns a new object: a check with discounts. The check is independent. The check with discounts is dependent – upon the check and upon the rules.
Sorry, make that rule (singular). We inject the rule into the service. There is probably some code elsewhere that loads the rule from the database, so that the user can modify it.
But the user probably wants to write several different rules. They don’t want just one combo; they want a whole menu of them. Furthermore, they also want discounts for prior visits. We need to apply all of these discounts.
Union of collections
The IDiscountRule is coded to return a collection of Discount objects. We did this because one rule could apply multiple discounts. We can take advantage of that fact to compose these rules. All we need to do is take the union of their outcomes.
public class PointOfSaleService
{
private List<IDiscountRule> _discountRules;
public PointOfSaleService(List<IDiscountRule> discountRules)
{
_discountRules = discountRules;
}
public CheckWithDiscounts AddDiscounts(Check check)
{
return CheckWithDiscounts.Create(
check,
_discountRules.SelectMany(
rule => rule.GetDiscounts(check)));
}
}
The SelectMany extension method takes the union of several different sets. Each set is generated by one element of the source collection. You can think of it as flattening a tree. In this case, the parent of the tree is the rule, and the child is the list of discounts that the rule generates.
Isolation
We’ve taken care to remove cycles from our rules before we started. Remember the Big Spender rule? Give a 5% discount on checks totaling more than $500. To eliminate cycles, we need to consider the total of the original check, not the total after discounts have been applied.
Consider another way that we could have written the method:
public class PointOfSaleService
{
private List<IDiscountRule> _discountRules;
public PointOfSaleService(List<IDiscountRule> discountRules)
{
_discountRules = discountRules;
}
public void AddDiscounts(Check check)
{
// WARNING!!
// This is the wrong way to apply multiple rules.
foreach (IDiscountRule rule in _discountRules)
check.AddDiscounts(rule.GetDiscounts(check));
}
}
In this version, we are modifying the original check after each rule. The next rule sees the modifications. So what would happen if our Big Spender purchased a combo? If the combo brought the total of the check down below $500, he would no longer get his 5% discount. That is, unless we moved the Big Spender discount to the front of the list.
This is the mistake that so many rules engines have made. Each rule has the opportunity to change the state of the system. Each rule can see the effects of the rules that came before. Now, all of a sudden, order matters. The user has to prioritize rules. The rules engine has to run complex solutions like the Rete algorithm in order to iterate to a solution. We can no longer develop rules in isolation without running the risk of affecting the other rules.
Preserve original state
A point-of-sale system is interactive. The user can add items to a check and see what discounts are applied. They can then add or subtract other items and see how the discount is affected.
Imagine how we would accomplish that interactivity if we modified the check to apply discounts. Add a combo, and the rules engine adds a discount. Then remove one of the items. The rules engine would have to recognize what just happened and remove the discount in response. Can you imagine the code it would take to make this work? Can you imagine all of the corner cases? Can you imagine how much testing you would need to do to be sure it was right?
It is much easier to instead preserve the original state of the system. Our correct rules engine (second listing) does just that. Our incorrect rules engine (third listing) destroys the original check. It is nearly impossible to get back to the original.
In the correct rules engine, any changes that the user makes are applied to the original Check. The object that appears on the user’s screen, however, is the resulting CheckWithDiscounts. It is a very simple matter of re-running the rules after every change to update the discounts. If the user removes a trigger item, the rule will no longer fire, and the discount will be “removed”.
The only way to safely apply multiple rules is to allow each rule to see only the original data. Rules should not modify data. Instead, the engine should gather their outcomes and build a new state from that union. Even then, that new state does not replace the original state. When the original state changes, you can simply re-run the rules to evaluate their new effect.
To finish it all off, we provide all of the context that the rules need in the Rule Model.
Posted in Rules engine | No Comments »
December 10th, 2009
Download the source code and follow along.
This post is part of a series:
- The customer is not you.
- If-then is backwards.
- Condition and outcome templates.
- Condition composition.
- The rule model.
We have rewritten our rules in terms of goals rather than conditions:
- Free items = 1 sandwich for every 4 on the original check.
- Discount =
- 75 cents for every combination of sandwich, chips, and drink on the original check, plus
- 5 dollars for every 10 prior visits of 5 dollars or more that have not already been rewarded.
- Coupons = 1 half-off coupon for every ice cream.
Now we need to give the customer a way to express rules in those terms. One way to do this is to identify all of the properties mentioned in the rules and provide them to the user:
- Check
- Item
- Quantity
- Total
- Visit
- Coupon
- etc.
This approach works for programmers, but it does not work for business users. The customer is not you. programmer can build a rule out of tiny parts. But the business user does not think this way. If you force them to think this way, they will get it wrong.
For example, while working on an eCommerce system, I asked a business user what properties they want in their rules engine. I explained that we could use an operator to compare a property against a number. One of the properties they asked for was “Any Item”. “How would you use that?”, I asked. “Like this: if Any Item > $50.00, then print a coupon.” This makes perfect sense to a business user. But this property is nonsense to a programmer.
Templates
Rather than attacking the problem from a programmer’s perspective, attack it from the business user’s perspective. Business users can’t build a rule out of small pieces. So we have to build rules for them. We can do so by creating templates.
This is why we want specific examples of rules. For each example of a rule, start by turning every constant into a parameter:
- Free items = m promotionalItemId for every n triggerItemId on the original check.
- Discount =
- m dollars for every combination of {itemIds} on the original check, plus
- m dollars for every n prior visits of minimum dollars or more that have not already been rewarded.
- Coupons = 1 couponType coupon for every n triggerItemId.
There may be some trivial parameters that you need to add. The example rule only dealt with one trigger item for a coupon, but you can imagine that they might want to specify the number. A coefficient of 1 tends to get forgotten.
We now have a set of outcome templates and condition templates. The outcomes are goal-oriented, and the conditions satisfy the outcomes. Conditions in this context are not just true or false; they govern the behavior of the outcome. That is why conditions must be expressed within the scope of an outcome, not the other way around.
Interface per outcome template
We can finally start putting rules into code. The first thing we will want to do is express each of our outcome templates as an interface. In a condition-oriented rules engine, an outcome would be an action: add a free item, print a coupon, etc. But in a goal-oriented rules engine, an outcome is a query. Given a check, what are my free items. What are my coupons. These are the interfaces that represent our three outcome templates:
public interface IFreeItemsRule
{
IEnumerable<Item> GetFreeItems(Check check);
}
public interface IDiscountRule
{
IEnumerable<Discount> GetDiscounts(Check check);
}
public interface ICouponRule
{
IEnumerable<Coupon> GetCoupons(Check check);
}
Each interface is implemented by a rule. We don’t know at this point what kinds of rules might implement these interfaces, but we do know precisely what the outcome of those rules can be. A rule can yield free items, or it can yield coupons. But it can’t do both. The rules are goal-oriented.
Class per condition template
Once we have outcome templates expressed as interfaces, we can write our condition templates as classes that implement those interfaces. Remember that these conditions are not simply true or false. They influence the outcome.
public class CombinationDiscountRule : IDiscountRule
{
private string _description;
private decimal _amount;
private List<ItemId> _triggerItems;
public CombinationDiscountRule(string description, decimal amount, List<ItemId> triggerItems)
{
_description = description;
_amount = amount;
_triggerItems = triggerItems;
}
public IEnumerable<Discount> GetDiscounts(Check check)
{
// Get the minimum quantity of all trigger items.
int comboCount = _triggerItems
.Select(triggerItemId => check
.Items
.Where(item => item.ItemId == triggerItemId)
.Sum(item => item.Quantity))
.Min();
for (int i = 0; i < comboCount; i++)
yield return new Discount(_description, _amount);
}
}
To define a rule, all the business user has to do is fill in the parameters.
To our programmer sensibilities, it seems that we are putting too much behavior into one class. Not only is it looking for combinations, but it is also creating discounts. But to a business user, it’s one rule. They don’t build things from pieces. They need a template.
Still, there is some refactoring that we can do. We’ll tackle that next in Condition composition.
Posted in Rules engine | No Comments »
December 4th, 2009
Download the source code and follow along.
This post is part of a series:
- The customer is not you.
- If-then is backwards.
- Condition and outcome templates.
- Condition composition.
- The rule model.
It seems that every new rules technology is just another way to write an "if" statement. We are used to expressing conditional logic in procedural code with flow control. Most of the rules engines that you could buy apply that same paradigm to rules. This is a mistake. It is better to turn that around.
Take, for example, the Windows Workflow Foundation Rules Engine. Every rule in the rule set is made up of three components:
- Condition
- Then actions
- Else actions
The condition can read any property of the workflow. It determines which of the two actions to execute. Both of the actions can modify any property of the workflow. Reading and writing from the same set of properties causes problems.
Conflicts
One problem occurs when two rules have actions that write to the same property. For example:
- If A = 1 then C = 3
- If B = 2 then C = 4
Given this rule set, when A=1 and B=2, what is the correct value of C?
Workflow and other rule engines solve this problem by prioritizing rules. Rules with higher priority take precedence over rules with lower priority. The unfortunate consequence of this is that you have to understand the priorities of existing rules in order to correctly prioritize a new rule. You cannot insert the new rule with confidence that it won't break anything.
Cycles
Another problem occurs when a cycle arises among conditions and actions:
- If D > 4 then E = 7
- If E > 5 then D = 3
If the second rule fires, should we re-run the first? How do we know when to stop?
Many rules engines use a sophisticated pattern-matching strategy called the Rete Algorithm to solve this problem. This algorithm runs subsets of the rules on each iteration based on the effects of the prior iteration. It walks the deltas toward a satisfactory solution.
Unfortunately, this algorithm is complex and can produce unexpected results. A business user defining a rule set cannot easily reason through cycles to determine exactly what the system will do. It is easier to reason about an acyclic system.
Goal directed rules
What works better is to express the rules in terms of the goal, not in terms of the condition. Rather than saying "If the amount is less than $500, the loan is approved", turn it around. "The loan is approved when the amount is less than $500." While this seems like a semantic distinction, it solves the problems of conflicts and cycles quite neatly.
When I say "The loan is approved when the amount is less than $500", I am also saying that there are no other conditions. To add another condition, I would have to modify the statement. For example: "The loan is approved when the amount is less than $500 or the amount is between $500 and $1000 and the credit score is over 700."
Think about the way that Excel spreadsheets work. I don't write a rule that says "Add one to B2 and store the result in C3". I put a formula in C3 that says "=B2+1". I cannot simply add another rule to the system that also affects C3. I would have to modify the formula in C3. The result is unambiguous.
Think also about the way that Linq works. I don't write a loop that modifies my target collection. I write an expression that calculates it. The behavior of the loop is harder to predict; the behavior of the expression is explicit.
A goal directed rule states the value of one property based on the values of other properties. The rule is explicit. Properties are not allowed to change, and rules are complete, so one rule cannot conflict with another.
Find and eliminate cycles
When rules are goal directed, cycles are easy to find. The algorithm is simple. Start with one property that you intend to solve for. Mark it as being evaluated, and then evaluate it. If that rule refers to another property, check to see if it's marked. If it is not, mark it and evaluate it. If it is, you have found a cycle.
Suppose we wanted to add this rule to our point-of-sale system:
- People who spend more than $500 get a 5% big spender discount.
More formally:
- Discount = if Big Spender, 5%, otherwise 0.
- Big Spender = Order Total > $500.
- Order Total = sum of Items minus Discount.
We mark Discount and evaluate it. We see that it requires Big Spender, so we mark and evaluate Big Spender. This leads us to Order Total, and then back to Discount. Since Discount is marked, our rules form a cycle.
To break this cycle, we make our rule more explicit:
- People who spend more than $500 before discounts are applied get a 5% big spender discount.
Now our formal definition looks like this:
- Discount = if Big Spender, 5%, otherwise 0.
- Big Spender = Order Total > $500.
- Order Total = sum of Items
minus Discount.
The cycle has been eliminated, and it is easier to reason about the system. No complex algorithm is required.
Point-of-sale as goal directed rules
Let's rewrite the example rules of the point-of-sale system. We turn the if-then around to make it goal directed. We combine rules that influence the same goal. And we eliminate cycles by being explicit. Here's the result:
- Free items = 1 sandwich for every 4 on the original check.
- Discount =
- 75 cents for every combination of sandwich, chips, and drink on the original check, plus
- 5 dollars for every 10 prior visits of 5 dollars or more that have not already been rewarded.
- Coupons = 1 half-off coupon for every ice cream.
These are some rules that we can easily reason about. Coming up in the series, we'll start to put these rules into code with Condition and outcome templates.
Posted in Rules engine | No Comments »
December 1st, 2009
Download the source code and follow along.
This post is part of a series:
- The customer is not you.
- If-then is backwards.
- Condition and outcome templates.
- Condition composition.
- The rule model.
Some problems call for customizable rules. Scientific and statistical analysis of data must be modified as new facts are learned and new questions are asked. Loan approvals are adjusted according to the latest actuarial tables. Sales and marketing systems are tweaked to incorporate new promotions. If a programmer has to change the code to customize these rules, the system has failed.
One solution to this problem is to build in support for a business rules engine. In .NET, you might use Windows Workflow Foundation or BizTalk BRE. In Java, you might try Drools. These tools allow customization of business logic at run time. They are general-purpose, out-of-the-box, inexpensive solutions.
Another solution is to build in a scripting language. In .NET you have Boo and IronPython. In Java, you have Groovy and Jython. A scripting language offers much more power than a typical business rules engine, but is targeted toward people who write code. While they do allow code to be changed after deployment, scripting languages force the programmer to stay in the loop.
Know thy customer, for he is not thee
The problem with both of these solutions is that the person creating the rules has to learn a new skill. When a scripting language is used, the problem is obvious. The rules are written in code. But what's wrong with a graphical workflow? There's no code involved. The user just drags-and-drops parts to build a schematic. Not only does this sound easy, it sounds like fun!
The truth is that it does not matter whether the system is expressed in code or in diagrams. The kind of thinking required in either case is the same. Programmers are used to building things up. Other people are used to breaking things down. A person using a business system can describe their problem and how they want to solve it. But they are not skilled in putting pieces together to build that solution. Whether they are staring at a blinking cursor or a blank canvas, the business user has the same reaction: "Now what?"
Domain specific rules
To design a rules engine that really meets your customer's needs, it needs to speak your customer's language. Don't make them learn a new skill; bring the solution to them. The rules engine should be domain specific.
The best way to capture the language of the domain is to ask for specific examples of business rules. Reassure your customer that the rules engine will be flexible enough to handle several kinds of rules, but write down as many specific rules as possible.
Take, for example, a point-of-sale system. Here are some specific rules that a business user would want to create:
- When the customer buys four sandwiches, they get a fifth one free.
- When the customer buys a sandwich, chips, and a drink, they get a 75 cent discount.
- When the customer buys an ice cream, print a coupon for half off on their next visit.
- After 10 visits of 5 dollars or more, the custom gets 5 dollars off the 11th visit.
When asked for this information, the business user might try to use generic terms like BOGO (buy-one-get-one), discount, and loyalty. You must press the issue. You will need specific catalog items, specific dollar amounts, and specific thresholds. What is a sandwich? You must print out the catalog and have them highlight all of the sandwich items. Without specifics, you are assuming that the business user is making the correct generalizations. Remember, you are good at figuring out what pieces to put together; your customer is not.
In the remainder of this series, we will build a point-of-sale rules engine that can run these rules and others like them. The goal is to provide the business user with all of the power they need, and no more.
Next, If-then is backwards.
Posted in Rules engine | No Comments »
November 23rd, 2009
I was saddened when I was unable to attend PDC this year. One project is winding down, we're staffing up for the next, and I cannot be spared at this particular time. I was not present when Scott Hanselman dragged and dropped his way through a data binding demo. Nevertheless, I was groaning from afar.
I did, however, have the chance to participate in the Not@PDC conference. It was quickly organized via Twitter and blogs to be an online get together for folks who were not lucky enough to be in LA. (Lucky to be in LA? Did I really say that?) It turned out to be a wonderful substitute. OK, not really a substitute; more like consolation.
I presented Data Binding Without INotifyPropertyChanged, a 70-minute demo of Update Controls in WPF, Winforms, and Silverlight. In the video, I show you the most awesome application ever: Microsoft Excel. Excel is awesome because you can use the MVVM pattern. It's true.
As you watch, please forgive the poor video quality and even poorer jokes. I'll be polishing the demo and taking it on the road. My first stop will hopefully be the North Dallas .NET Users' Group. I'll keep you posted.
Posted in Update Controls | No Comments »
|