WinMedia

Back to Software

Case Study: Complex Rule Sets

When designing a new feature I look for prior art to inform my approach. In 2013, the options for sophisticated rule-builders (at least insofar that I could find) were limited. One of the solutions closest to what we were trying to achieve was the Smart Playlist builder in iTunes.

Rules in iTunes app from 2013

This proved to be the right direction but not a comprehensive solution.

The Use-Case

LeadConduit, in its essence, is like an http proxy. Basically, data is sent in and converted into our internal format and then sent on to one or more destinations after being converted to the 3rd-party's expected format. Along the way, there are various points where the user might want to reject the data, selectively format certain parts of the data, or have logic about where to send it and when. That's where rules come in.

A rule-builder is much more complicated than I would have guessed when starting the project. It's easy to say, "I want the car to be blue AND I want the car to have a V6" but what about "I want the car to be blue OR I want the car to be red". Further, you could have a case where you want the car to be blue or red AND have a V6. To meet as many cases as possible you need top level rules, compound rules, and rules that can be nested under another rule. You can see how this can be a bit of mind bender.

The solution was to model the data as abstractly as possible:


// NOTE - op = operator, lhv = left-hand value, rhv = right-hand value
{
  op: 'or', // Top level operators allowed you to AND or OR all rules at this level
  rules: [
    { // A basic rule is defined by an lhv, rhv, and an op
      lhv: 'engine',
      op: 'is equal to', // Rules could have various options of operator based on the data type in the lhv
      rhv: 'V6'
      rule-set: { // Optionally you can add a nested rule that is parsed as an AND to the parent
        op: 'and',
        rules: []
      }
    },
    { // A rule group is a rule-set at the level of a single rule and can be parsed as AND or OR
      op: 'or'
      rules: [
        {
          lhv: 'color',
          op: 'is equal to',
          rhv: 'red'
        },
        {
          lhv: 'color',
          op: 'is equal to',
          rhv: 'blue'
        }
      ]
    }
  ]
}

From there I was able to build rule set and rule UI components that could compile recursively based on the data model.

The first implementation of rule sets came in the form of filter steps. A step was one of N number of serialized descriptions of how to handle a given data submission. In this case, you could set arbitrary rules to short circuit the rest of the handling and exit the processing.

Single rule describing a situation where if this is not equal to that, take action

Great! But, what of our more complicated use-cases?

Remember that we wanted this feature to be able to handle basically anything a user could conceive of and be flexible enough to use anywhere. If we look back at our data model, there are 2 other configurations for rules beyond just the basic top level. We have "nested" rules (wherein we can add rules dependent on a specific parent rule) and rule "groups" that resolve at the level in which they're defined but are able to be bundled together with an "and" or "or" operator.

Examples of compound rules

Now we were able to accomplish a number of sophisticated tasks entirely within the UI — including building fairly robust integrations with 3rd party services.

Examples of mappings and rules being used to integrate with 3rd party software

Conclusion

There's more to say about how the earliest implementations within given contexts (like the filter step or mappings) succeeded and failed but that's for another time. The fundamental user experience of how the rule builder worked has remained the same for close to 10 years – having survived design changes and code refactors – and has been one of the most critical points of success for LeadConduit and its ecosystem of tools.