Saturday, 29 May 2010

Madura Rules part 1

My earlier posts described Madura Objects, which is a way to define Java objects that automatically call a validation engine whenever they change. A valid change is accepted and an invalid one is rejected, leaving the state of the objects unchanged (ie still valid). The objects are just POJOs with an added interface and one method used for fetching metadata. They are all generated using JAXB with a plugin, so you don't have to write any of the code to do all this.

But so far this only handles single field validation, relationships between fields cannot be described. So you can say a string field must be no more than 20 characters long and maybe has to look like an email address. But you can't say if the customer type field is "A" then the business type must be "Ag". Nor can you say there must be no more than n items in a list.

The way to manage this is to add a rules engine to the Madura Objects validation engine. I have implemented this as a plugin because, though I might think my own rules engine is the greatest, other people might want to implement their own. Also I'm not yet certain if I will open source the rules engine so I need to keep it separate from the open source Madura Objects.

What do the rules look like?

rule: Customer "Determine business from customerType"
{
  if (customerType == "A")
  {
    business = "Ag";
  }
}

This is a classic rule in that it has a condition and an action. Multiple actions are fine, but there's only one in this example. The syntax intentionally looks like Java. We have a Customer object and we want to ensure that if we set the customerType field to "A" the business field will be set to 'Ag'. Remember this happens automatically. It is probably obvious enough but the 'Customer' just after 'rule:' means the fields 'customerType' and 'business' are fields on the Customer object.

It is worth noting that if there is already a value in 'business', and it is different, then this rule will throw a constraint violation exception and roll back the last change.

constraint: Customer "check the count" "No more than 10 invoices"
{
  !(invoiceCount > 10);
}

This kind of rule has just a condition, no action. The condition must always be true. So, after any relevant change this rule is checked. If it fails the change is rejected and rolled back. In this case we might have invoiceCount derived from counting the number of invoices attached to this customer. Attaching the 11th invoice would be ejected by this rule. We can attach a message to this rule, "No more than 10 invoices" which is returned in the exception that is thrown.

formula: Customer "figure the invoice count"
{
  invoiceCount = count(invoices);
}

This is a formula rule, basically an algebraic statement that is aways enforced. This is how we figured the invoiceCount we used in the constraint rule. Yes, we could have combined the previous example and this one with a more complex condition, but that would not make such a good example.

With these rules slipped behind the validation engine you can perform complex validations as well as derive new values. New values that are deemed inconsistent with what has already been supplied are rejected using an exception. The inconsistent value is rolled back.

In my next post I will describe how this is configured.
Post a Comment