Recently I was working with a customer that wanted to use rules to do validation. The idea was to pass in a document to the rules engine and get back a list of violations, or an empty list if there were no violations. Turns out that there were a coupe more steps required than I expected so thought I would share my solution in case anyone else is wondering how to return lists from the rules engine.
For the purposes of this blog I modeled a very simple shipping company document that has two main parts. The Package element contains information about the actual item to be shipped, its weight, type of package and destination details. The Billing element details the charges applied.
For the purpose of this blog I want to validate the following:
The Shipment element is sent to the rules engine and the rules engine replies with a ViolationList element that has all the rule violations that were found.
We need to create a new ViolationList within rules so that we can return it from within the decision function. To do this we create a new global variable – I called it ViolationList – and initialize it. Note that I also had some globals that I used to allow changing the weight limits for different package types.
When the rules session is created it will initialize the global variables and assert the input document – the Shipment element. However within rules our ViolationList variable has an uninitialized internal List that is used to hold the actual List of Violation elements. We need to initialize this to an empty RL.list in the Decision Functions “Initial Actions” section.
We can then assert the global variable as a fact to make it available to be the return value of the decision function. After this we can now create the rules.
If a rule fires because of a violation then we need add a Violation element to the list. The easiest way to do this without having the rule check the ViolationList directly is to create a function to add the Violation to the global variable VioaltionList.
The function creates a new Violation and initializes it with the appropriate values before appending it to the list within the ViolationList.
When a rule fires then it just necessary to call the function to add the violation to the list.
In the example above if the address is a residential address and the surcharge has not been applied then the function is called with an appropriate error code and message.
Each time a rule fires we can add the violation to the list by calling the function. If multiple rules fire then we will get multiple violations in the list. We can access the list from a function because it is a global variable. Because we asserted the global variable as a fact in the decision function initialization function it is picked up by the decision function as a return value. When all possible rules have fired then the decision function will return all asserted ViolationList elements, which in this case will always be 1 because we only assert it in the initialization function.
A return from a decision function is always a list of the element you specify, so you may be tempted to just assert individual Violation elements and get those back as a list. That will work if there is at least one element in the list, but the decision function must always return at least one element. So if there are no violations then you will get an error thrown.
Instead of having a completely separate return element you could have the ViolationList as part of the input element and then return the input element from the decision function. This would work but now you would be copying most of the input variables back into the output variable. I prefer to have a cleaner more function like interface that makes it easier to handle the response.
Hope this helps someone. A sample composite project is available for download here. The composite includes some unit tests. You can run these from the EM console and then look at the inputs and outputs to see how things work.