Lesson 3 - How Leaf Parts Use DK

DK is Declarative Knowledge. DK declares the policy for a part to follow in a particular reuse case.

Let's build a leaf part that uses DK to calculate the penalty on late payments. We'll call it the PaymentPenaltyCalculator part. The reuse policy needs to declare the interest rate per day to use, the minimum penalty and grace period in days.

Here's the part's DK:

    <partdk>
        <InterestRatePerDay>.001</InterestRatePerDay>        
        <MinimumPenalty>5.00</MinimumPenalty> 
        <GracePeriodDays>7</GracePeriodDays> 
        </partdk>

The beauty of DK is we can easily vary it, while the part code remains untoucned. Thus we can reuse the PaymentPenaltyCalculator part in many different systems, or even different places in the same system, and have the part behave differently by giving it different DK each time.

Let's create directory system03, copy and modify the files from system02, and add the file Calculator.dk with the above DK in it. We also rewrte PartA to be the PaymentPenaltyCalculator part. We make small changes in the _Contianer.dk file like this:

The container DK:

<partdk>
    <parts>
        <part>
            <name>Calculator</name>
            <type>uhr.tutorial.system03.PaymentPenaltyCalculator</type>
            </part>
        </parts>
    <start_commands>
        <invocation>
            <part>Calculator</part>
            <command>RunTest</command>
            </invocation>
        </start_commands>        
    </partdk>

For a part to have its DK provided automatically it must implement the ParamDriven interface. Here's the part. Notice how it also implements the familiar StateCommandable interface so we can run the test.

The reusable PaymentPenaltyCalculator part.

package uhr.tutorial.system03;

import uhr.core.dk.ParamDriven;
import uhr.core.dk.ParamDrivenAide;
import uhr.core.dk.ParamDrivenAideStd;
import uhr.core.role.StateCommandable;
import uhr.core.role.StateCommandableAide;
import uhr.core.role.StateCommandableAideFactory;
import uhr.core.tron.Flexitron;

public class PaymentPenaltyCalculator implements StateCommandable, ParamDriven {

//---------- Internal Fields -------------------------------------
protected StateCommandableAide stateAide = 
            StateCommandableAideFactory.createAide("RunTest", this);
            
protected Flexitron       dk;
protected ParamDrivenAide paramAide = new ParamDrivenAideStd(this);

//---------- StateCommandable Implementation ---------------------
public StateCommandableAide getStateCommandableAide() {
    return stateAide;
}
public void doStateCommand(String command) {
    if (command.equals("RunTest")) {
        print(" - Hello World 3 with DK");
        paramAide.provideDK(); // Since DK is deferred
        double penalty = calculatePenalty(100, 10);
    }
}
//---------- ParamDriven Implementation --------------------------
public ParamDrivenAide getParamDrivenAide() {
    return paramAide;
}
public void setDK(Object dk) {
    this.dk = (Flexitron)dk;
    print(".setDK() - The dk = " + dk);
}
//---------- Protected Methods -----------------------------------
protected double calculatePenalty(double paymentAmount, int daysLate) {
    // Get calculation policy from my dk
    double interestRate    = dk.getDouble("InterestRatePerDay");
    double minimumPenalty  = dk.getDouble("MinimumPenalty");
    int    gracePeriodDays = dk.getInt("GracePeriodDays");
    
    // Calculate
    double penalty = 0;
    if (daysLate <= gracePeriodDays) {
        // Do nothing, no penalty
    } else {
        // Calculate penalty
        // Add minimumPenalty
        penalty = minimumPenalty;
        // Add interest. We use a simplified InterestRatePerDay.
        penalty += paymentAmount * interestRate;
    }
    // Show work done
    print(" >>>----> Calculated penalty of " + penalty + " on payment of "
    + paymentAmount + " that was " + daysLate + " days late.");
    
    // Done
    return penalty;
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println(text);
}

} // End class

The output when we run this is:

Hello World 3 with DK
The dk = [InterestRatePerDay=.001, MinimumPenalty=5.00, GracePeriodDays=7]
Calculated penalty of 5.1 on payment of 100.0 that was 10 days late.

+Root
    Calculator

Shucks, the part code is getting a little long now. It's true, reusable parts have more code. But the good news is you only write a part once. :-) In the long run, UHR parts have much less code than typical system classes due to the various UHR mechanisms like DK.

Notice how the part's DK is provided automatically when we call paramAide.provideDK(). This slight bit of effort is necessary because DK is available on a "deferred" basis. If DK was not deferred to all the parts in a system, the system would be very slow to startup.

The real work is in the calculatePaymentPenalty() method. Notice how this method uses the policy in the DK for the calculation. Gone are all the wild and wooly ways of getting such parameters to where they are needed, or worse yet, hard coding such parameters in constants.

To summarize, DK is a standard, uniform mechanism for giving reusable parts the policy they need to carry out their procedures. In a way, DK is the facts of your Business Rules, and parts are your Business Procedures.


Go to the next lesson.