Select Page

The Why

Business Rule Engines have been around for some time now. The principle they encapsulate is that of storing business rules outside of the application. While one obvious benefit is that of easier maintenance, the reason a team typically tries to use a business rule engine is to delegate maintenance of rules to the people with domain expertise, business analysts. This aim, more often than not, is not met. The rules quickly become complex and start to resemble a programming language themselves – making them unmaintainable by anyone other than the developers.

Our reasons for adopting configuration-driven design were similar to the reason given above. While maintainability was a crucial criteria, another was decoupling the UI from the tools being configured. CloudMunch’s USP is that we can work with *any* tool. We wanted to ensure that configuring tools in our platform should be as easy as plug-and-play. Therefore, we’ve designed our UI such that the look-and-feel, inter-dependencies and validations associated with configuring tools is outsourced to the plugin developer.

v1

                               A typical configuration panel for a plugin

The How

The first step towards achieving this externalization is to come up with a format to represent the UI as data. Plugins written for CloudMunch need to have a JSON definition file. This file, along with its other purposes (like encapsulating the external interfaces and APIs), also details what fields a plugin will have and how to display them.

{

“name”: “Git Checkout”,

“inputs”: {

“branch”: {

“defaultValue”: “master”,

“hint”: “Branch or sha-id of repo to checkout ( master branch will checked out by default ). This will override the value provided in integration”,

“label”: “Branch / SHA-ID”,

“type”: “text”

},

“https_url”: {

“hint”: “Https url of git repo to be cloned.Example https://github.com/amithins/Simple-selenium.git.”,

“label”: “HTTPS Clone URL”,

“type”: “text”

}

}

}

A simple definition file

The example above is a simplistic one and is an example of such fields being stored. Note that each field has a “type”, “label” and (an optional) “hint”. Using different values for the node “type”, we can support any html input type supported by the browser ( ex: radio-buttons, dropdowns, textareas). We can also specify static or runtime values for inputs such as radio-buttons and dropdowns.

For any non-trivial scenario, we will also need this format to store some additional information such as whether a field should be displayed or hidden, whether it is mandatory, the relationship between fields (ex: field ‘B’ is only necessary if field ‘A’ has a particular value) and validations to perform on inputs. As demonstrated below, our definition files can also capture such details.

“https_url”: {

“hint”: “Https url of git repo to be cloned.Example https://github.com/amithins/Simple-selenium.git.”,

“validation_rules”: [

{

“message”: “Valid https clone URL : https://github.com/amithins/Simple-selenium.git”,

“regex”: “^https\\:\\/\\/github\\.com\\/.*\\.git$”,

“rule”: “REGEX”

}

],

“dependency”: [

{

“condition”: “==”,

“value”: “remoteServer”,

“criteria”: “checkout_location”

}

],

“mandatory”: false,

“label”: “HTTPS Clone URL”,

“display”: “yes”,

“type”: “text”

}

Example to show a non-mandatory, dependent field with some validations.

The next step is to parse and display this definition in our UI. Here we use the Factory Pattern. When invoked with a definition, the usecase’s UI delegates display to a component meant for displaying such dynamic views. That component in turn iterates through each field in the plugin and uses a simple switch-case method to delegate the rendering of each field-type to a specific method.

v2

How dynamic fields are rendered

Backbone’s Marionette, the library we use allows us to compose such views and create a layout and Backbone.Syphon allows us to easily read the data entered in these individual views and construct an object. As shown in the image, we even support recursion. After the fields are read, the UI delegates validation to a model constructed using the same definition file ensuring validations specified in the definition are performed.

The Challenges

There have been several challenges in implementing this design. Here are a few examples.

UI: Syphon-needs-a-form

Syphon, while very powerful, only works if the fields being read are within a form element. Since dynamic fields can be rendered in any view, we could not blindly wrap them within a form when rendering (think form-within-form). Therefore, after the user types in (or changes) content, the dynamic view creates a form on-the-fly, populates it with content and then invokes Syphon to read the form and create an object.

Definition: Complex Validations and Dependencies

The format in which we store logic such as dependencies and validations as JSON has evolved with use. As the requirements became more complex, so did the format. A good example to look up if you have similar requirements are business-rule-engines (like json-rules). These libraries have already dealt with and (to some extent) solved the issue of storing logic as JSON.

Conclusion

Configuration-driven-UI has made maintenance of CloudMunch’s UI easy. Today, adding a new plugin to CloudMunch is as simple as creating its definition file and adding it to the CloudMunch plugin ecosystem. We’ve used the same pattern in all screens where the UI needs cannot be predicted during design and depends on the end-user.  Hopefully, we can one day open-source this code as a library for use by others. In the meantime, I hope this blog post will help.