Business Rules With Dgraph - Dgraph Blog

In this blog, we discuss how to use Dgraph database as part of a solution for implementing an application based on business rules. In particular, you will model and implement a Decision Table, a popular technique used to model and implement business rules. You will learn how Dgraph helps you adapt to change quickly and operate a clean and auditable rules-based application.

Understanding Business Rules

Almost all applications implement business rules in one way or another. These rules involve taking certain actions (for example, “approve a loan”) based on certain conditions (such as the borrower’s income). A decision table is a popular, visual algorithm that ties various actions to their respective conditions. In the example shown above, a decision table is used to set an interest rate on a loan.

Several applications, particularly those in the financial domain, operate under strict oversight. Business and regulatory policies translate into business rules that need to be maintained in an application. Additionally, these rules tend to change over time as the regulatory or competitive environment shifts. For example, variable interest rates are calculated based on benchmarks such as LIBOR or Treasury bond rates, which themselves fluctuate over time.

The developers of such applications are often under pressure to keep pace with the stresses caused by these changes in the environment. While responding to these changes, app developers must address other challenges as well, such as traceability, auditing, and execution support across multiple application design and runtimes.

In this blog, you will see an approach that lets you express business rules with Dgraph and helps to address the associated challenges.

Business Rules and Dgraph

Business Rules go through several change cycles due to changes in policies, regulations, etc. Every time a change occurs, developers need to look up all affected artifacts and update them. These could potentially include database records, code, and configuration data. This is certainly a challenge and warrants setting up a change-friendly solution.

One approach to simplify operations is to have a standards-based vocabulary for all rules. There are industry bodies and communities that publish these vocabularies. For example, you could use “Variable Interest Rate” as defined in the FIBO (Financial Industry Business Ontology) vocabulary. If you face such challenges, we have some good news for you!

Dgraph allows nodes to have external types. This could prove very valuable when refactoring any rules associated with floating interest rates. The FIBO vocabulary in particular is very versatile and you can use it to bring clarity to several different types of nodes in your app’s data model. We will use a couple more entries from this vocabulary in our solution.

With the rules vocabulary in place, you can begin to address the decision table itself. It is possible to express our decision table in a graph. Dgraph is quite adept at accommodating any kind of structure, and you could use a simple graph to model the decision table. We will, of course, continue to leverage the FIBO vocabulary so that the decision table expressed in Dgraph continues to have a consistent meaning.

When executing business transactions, such as determining the interest rate for a loan, you need to pull together relevant pieces of transaction information (such as a loan request) and decision table rules. This is done by adding an edge between the relevant action in the decision table to the affected business transaction. Expressing this link is critical as this effectively becomes an “audit record”. At any future point in time, you can revisit these links again to determine what happened, and why, for any specified event in the past.

Finally, you will execute the mutations involved in satisfying the linked rule. In the loan example, this would mean calculating and updating the interest payable annually for a given loan request.

Step 1: Setting up Business Rules

A Decision Table consists of different conditions and actions corresponding to each satisfied condition. Every condition of the decision table is modeled as Dgraph nodes. You also model the resulting action as a node, which is connected to the corresponding condition.

Each branch, or in this case the loan amount range, is given a simple human-readable name and is linked to the actual interest rate (4.5% for Low-Range, 5% for Mid-Range and 6% for High-Range Borrowing respectively). The graph also stores the upper and lower bounds for each branch.

You can annotate the central node with type Formula as the decision table is used to calculate the interest rate. The actual rate type is annotated with the type Variable Interest Rate. This allows us to identify all variable interest rates in the system in the event of a scheduled change event.

Schema and Initial Data

Let’s start by loading the schema and initial into Dgraph, as follows:

<countryName>: string @index(exact) .
<fullName>: string @index(exact) .
<hasDateValue>: datetime @index(month) .
<hasInterest>: [uid] @reverse .
<hasRateValue>: float .
<interestRateApplied>: [uid] @reverse .
<lowerBoundOfAmount>: float @index(float) .
<name>: string @index(exact) .
<ranges>: [uid] @reverse .
<requestedLoanAmount>: float .
<streetName>: string @index(exact) .
<upperBoundOfAmount>: float @index(float) .
{
 set{
    _:p5 <dgraph.type> "https://spec.edmcouncil.org/fibo/ontology/LOAN/LoanContracts/LoanApplications/LoanApplication" .
    _:p5 <name> "fibo-loan-ln-app:LoanApplication" .
    _:p5 <fullName> "Joe Kramer" .
    _:p5 <streetName> "1 Main Street" .
    _:p5 <countryName> "United States of America" .
    _:p5 <requestedLoanAmount> "1500" .
    _:variableHomeLoanInterestLow <dgraph.type> "https://spec.edmcouncil.org/fibo/ontology/FBC/DebtAndEquities/Debt/VariableInterestRate" .
    _:variableHomeLoanInterestLow <hasRateValue> "4.5" .
    _:variableHomeLoanInterestMid <dgraph.type> "https://spec.edmcouncil.org/fibo/ontology/FBC/DebtAndEquities/Debt/VariableInterestRate" .
    _:variableHomeLoanInterestMid <hasRateValue> "5" .
    _:variableHomeLoanInterestHgh <dgraph.type> "https://spec.edmcouncil.org/fibo/ontology/FBC/DebtAndEquities/Debt/VariableInterestRate" .
    _:variableHomeLoanInterestHigh <hasRateValue> "6" .
    _:interestRateSchedule <dgraph.type> "https://spec.edmcouncil.org/fibo/ontology/FND/Utilities/Analytics/Formula" .
    _:interestRateSchedule <name> "LoanRatesTable" .
    _:interestRateSchedule <hasDateValue> "2020-12-01" .
    _:interestRateSchedule <ranges> _:interestRateRange1 .
    _:interestRateSchedule <ranges> _:interestRateRange2 .
    _:interestRateSchedule <ranges> _:interestRateRange3 .
    _:interestRateRange1 <name> "Low-Range Borrowing" .
    _:interestRateRange1 <lowerBoundOfAmount> "0" .
    _:interestRateRange1 <upperBoundOfAmount> "1000" .
    _:interestRateRange1 <hasInterest> _:variableHomeLoanInterestLow .
    _:interestRateRange2 <name> "Mid-Range Borrowing" .
    _:interestRateRange2 <lowerBoundOfAmount> "1000" .
    _:interestRateRange2 <upperBoundOfAmount> "10000" .
    _:interestRateRange2 <hasInterest> _:variableHomeLoanInterestMid .
    _:interestRateRange3 <name> "High-Range Borrowing" .
    _:interestRateRange3 <lowerBoundOfAmount> "10000" .
    _:interestRateRange3 <upperBoundOfAmount> "999999" .
    _:interestRateRange3 <hasInterest> _:variableHomeLoanInterestHigh .
  }
}

This example app uses the fullname, streetName, and countryName predicates to identify the loan applicant. To determine the interest rate for the loan, the app uses the requestedLoanAmount predicate. In this example, the applicant requests a loan for $1500. Also, The dgraph.type and name attributes store the loan application URI provided by FIBO.

The attributes lowerBoundOfAmount, and upperBoundOfAmount form a range for a particular interest rate to be applied. The interest rate, hasInterest, is itself is modeled as a full node. This helps adding additional type information. We use URIs provided by FIBO here as well, which will lend itself for better visibility. The ranges and interest are as per the decision table mentioned previously. These form the conditions and actions component of the decision table.

You can use Ratel, a web-based UI built by Dgraph to work with graph data, to add the schema and mutations you need.

Step 2: Evaluating and Linking Loan Transaction

#mutation
upsert{
  query{
    # uid 0xa is the uid of the loan application. Please
    # update the uid based on the actual value in your
    # system
    qPerson(func: uid(0xa)) {
      uid
      ra as requestedLoanAmount
    }
    q2(func: ge(upperBoundOfAmount, val(ra) ))
       @filter(lt(lowerBoundOfAmount, val(ra))){
      hasInterest{
        ir as hasRateValue
      }
    }
  }
  mutation @if( eq(len(ir),1) ) {
    set{
      uid(ra) <interestRateApplied> uid(ir) .
    }
  }
}

You will now perform an “upsert” mutation. This mutation will pick the requested loan amount and map to the appropriate range. Once the appropriate range is found, the actual interest is linked directly to the requested loan amount. Upon querying, it is visible that the loan request is connected to the interest rate value (5 - meaning 5%). You can further traverse from the interest rate to the range it belongs to, and then to the associated “formula” (or decision table).

These features help you to manage the loan approval in an open, transparent, and standards-based manner. Also, when you need to update or change a business rules app, these features make that easier and less error-prone. Finally, the upsert mutation itself is based on the same, consistent vocabulary.

Here’s the visualization from Ratel:

Step 3: Executing a Calculation Action Based on A Condition

#mutation
upsert{
  query{
    qPerson(func: uid(0xa)) {
      uid
      ra as requestedLoanAmount
      interestRateApplied{
        rate as hasRateValue
      }
      rateToBeApplied as max(val(rate))
      ar as math(( rateToBeApplied * ra )/100  )
    }
  }
  mutation @if( eq(len(rate),1) ) {
    set{
      uid(ra) <annualInterest> val(ar) .
    }
  }
}

You can also write additional mutations that can calculate some parts of the transaction. For example, the annual interest payable is calculated by the mutation shown above. The resultant data, shown below, shows the original requested loan amount, the dynamically linked interest rate and the calculated annual interest payable.

Overall, we have utilized a limited number of artifacts and no ad-hoc coding in the entire process. This helps when adapting to change and provides a faster time-to-market for app updates.

{
  "data": {
    "q1": [
      {
        "uid": "0xa",
        "requestedLoanAmount": 1500,
        "interestRateApplied": [
          {
            "hasRateValue": 5
          }
        ],
        "annualInterest": 75
      }
    ]
  }
}

Conclusion

This blog provides a step-by-step approach to the design, linking and execution of business rules using Dgraph. First, you leveraged Dgraph’s fluid modeling capability to design a decision table. In this decision table, you configured interest rates to be applied to loan application based on requested loan amount. You then applied this decision table to a loan transaction using Dgraph’s mutation capabilities, particularly the upsert block, to explicitly connect a loan application to an interest rate. Finally, you calculated the annual interest amount payable and updated the loan request transaction.

In conclusion, Dgraph provides a unique approach to design, maintain, and audit the business rules in an effective and efficient manner. This helps your application adapt to change and reduce time to market.

Interested in getting started with Dgraph? You can get started here. Please say hello to our community here; we will be glad to help you in your journey!


This is a companion discussion topic for the original entry at https://dgraph.io/blog/post/decisions-with-dgraph/