Updating Rule Applications in Code using irSDK

DG

Dan Gardiner

07/08/2020

Warning: As with the previous posts about Continuous Integration/Continuous Deployment, this post is intended for a technical/developer audience.

Background

A while back, I started a series about CI/CD with a pair of blog posts that walk through examples of ways to include InRule in your CI/CD pipeline. Among other things, I covered how to retrieve a Rule Application from either an irCatalog instance or from a .ruleappx file, as well as how to promote a rule application to a downstream catalog or file destination.

For some of our customers that use environment-specific data or integrations with external resources like REST services or databases, different executions or environments may need to point to different reference data or external resource locations. To enable that, InRule has a variety of options:

  1. 1. Provide overrides as part of the request during execution (either using our standard REST execution service or a during custom execution using irSDK). This is considered a request-level override.
  2. 2. Add settings to the configuration of an execution service with overrides to use for all requests flowing through that execution service. This was added in InRule version 5.6, and is considered a service-level override.
  3. 3. Modify the Rule Application in code using irSDK during the promotion process, so that the updated reference data or external resource locators are modified in the actual Rule Application definition. This requires custom development to implement, and is considered a code-based Rule Application modification.

Our general recommendation is to use either option 1 or 2. Particularly now that option 2 has been released, fewer customers will have a need to implement option 3 – but we do occasionally find customers that have a business need to implement option 3 due to some internal requirements. This Rule Application modification will need to be done between the two previously mentioned steps in the pipeline – after the retrieval of the Rule Application definition, and before it gets Promoted to a downstream target.

A word of caution

By modifying the Rule Application during the promotion process, there will by definition be a disparity between what is checked into the source Catalog and what gets saved to the target Catalog. This means that there needs to be strict governance in place to control/prohibit use of other tools (Catalog Manager, irAuthor) to perform promotions, since they do not take into account the changes made in the pipeline. You will also want to ensure that promotion is never done in a backwards direction, as that would overwrite the settings with the values from the downstream environment.

 

Updating Rule Application in Code

That was a lot of background and context – let’s take a look at how you can actually make it happen!

This post is not going to have an associated console application like the previous posts did, since the items that need to be updated in a given Rule Application will differ substantially from customer to customer. However, we will walk through a variety of different items that you may have a need to update, which should provide the context around where to look to find and update anything in the Rule Application. Each example is structured as a static helper method that will extend the behaviors available on the RuleApplicationDef object when referenced in a static class.

Database Connection Strings

This is an extremely common item to differ between environments – and it can also easily be overridden at a request- or service- level. If you need to modify the Rule Application Definition level, it’s a straightforward process:

internal static void UpdateDbConnectionString(this RuleApplicationDef ruleApp, string dbConnectionName, string newConnectionString)
{
    if(ruleApp.EndPoints.Contains(dbConnectionName) && ruleApp.EndPoints[dbConnectionName] is DatabaseConnection dbConnection)
        dbConnection.ConnectionString = newConnectionString;
}

 

REST Service Root URLs

This is another extremely common one (with the same standard override options), and is also a straightforward item to override in code if needed:

internal static void UpdateRestRootUrl(this RuleApplicationDef ruleApp, string restServiceName, string newRootUrl)
{
    if (ruleApp.EndPoints.Contains(restServiceName) && ruleApp.EndPoints[restServiceName] is RestServiceDef restService)
        restService.RootUrl = newRootUrl;
}

 

REST Operation Header Values

Now we’re getting more complicated! This substitution cannot be performed via configuration, and could be used for things like setting the API Key value in a REST request header. Note that we generally recommend passing in or retrieving the authentication header value during execution, and then populating the header value dynamically. However, if you need to hard-code the updated value in your Rule App definition, the process is again fairly straightforward:

internal static void UpdateRestOperationHeader(this RuleApplicationDef ruleApp, string restOperationName, string headerName, string newHeaderValue)
{
    if (ruleApp.DataElements.Contains(restOperationName) && ruleApp.DataElements[restOperationName] is RestOperationDef restOperation)
    {
        var relevantHeader = restOperation.Headers.FirstOrDefault(h => h.Name == headerName);
        if (relevantHeader != null)
            relevantHeader.Value = newHeaderValue;
    }
}

 

Inline Value Lists

This item, as well as the next one, can be overridden at either the request- or service- level in addition to being set in code. However, given the complexity and amount of data that may be involved, there may be a need to replace it directly in the definition:

// For newData, the Inline Value List's "Value" column will come from the dictionary's Key (which must be unique),
//  and the optional "DisplayName" column will come from the Dictionary item's Value (which can be null)
internal static void UpdateInlineValueList(this RuleApplicationDef ruleApp, string inlineValueListName, Dictionary<string, string> newData)
{
    if (ruleApp.DataElements.Contains(inlineValueListName) && ruleApp.DataElements[inlineValueListName] is InlineValueListDef valueList)
    {
        var data = valueList.Items;
        data.Clear();

        if (newData == null || newData.Count == 0)
            return;

        foreach (var item in newData)
        {
            data.Add(new ValueListItemDef(item.Key, item.Value));
        }
    }
}

 

Inline Tables

As with the previous item, this can also be overridden at other levels or set in the definition directly, albeit with a bit more complexity:

internal static void UpdateInlineTable(this RuleApplicationDef ruleApp, string inlineTableName, object[,] newData)
{
    if (ruleApp.DataElements.Contains(inlineTableName) && ruleApp.DataElements[inlineTableName] is TableDef inlineTable)
    {
        DataTable table = inlineTable.TableSettings.InlineDataTable;
        table.Rows.Clear();

        if (newData == null || newData.Length == 0)
            return;

        for (int rowIndex = 0; rowIndex < newData.GetLength(0); rowIndex++)
        {
            var newRow = table.NewRow();
            for(int columnIndex = 0; columnIndex < newRow.ItemArray.Length; columnIndex++)
            {
                newRow[columnIndex] = newData[rowIndex, columnIndex];
            }
            table.Rows.Add(newRow);
        }
    }
}

 

Entity Field Default Values

Let’s end on a simpler one. We’ve looked at updating all sorts of supporting data, but what about setting schema properties directly? This could be used for something like a flag indicating environment or log level, access token, or any number of other pieces of information:

internal static void UpdateFieldDefaultValue(this RuleApplicationDef ruleApp, string entityName, string fieldName, string newDefaultValue)
{
    if (ruleApp.Entities.Contains(entityName) && ruleApp.Entities[entityName].Fields.Contains(fieldName))
        ruleApp.Entities[entityName].Fields[fieldName].DefaultValue = newDefaultValue;
}

 

Review

As I mentioned before, the examples I have listed are intended to give you some context into where you might look to find various components of a Rule Application to update them using irSDK.

Before going down the route of implementing custom code in your CI/CD pipeline to modify the Rule Application definition, I would very strongly encourage you to look into the functionality introduced in 5.6 that allows for overriding execution settings at a service-level; that should handle the vast majority of situations, and you can find all the details in the InRule Developer Help by navigating to “Application Integration with InRule > Configuration Files > InRule Runtime Service Config File Settings”. If you find that does not cover your needs, using irSDK to modify the definition may prove to be a great solution. Remember: anything that you’re able to set in irAuthor, you’re able to modify using irSDK.