12. Implementing Drools – Open Source SOA

Chapter 12. Implementing Drools

In the previous chapter we discussed what constitutes business rules and how they can be used with a rules engine in a SOA environment to significantly improve the reusability of these assets and promote greater agility within your organization. We then explored JBoss Rules (most commonly called Drools), which will serve as the rule engine for our Open SOA Platform. We spent the latter part of chapter 11 describing many examples that illustrated the key language constructs of Drools. For your less technical users, we covered some of the alternative ways in which rules can be expressed, such as through decision tables and domain-specific languages. What we didn't discuss, and what's the initial focus of this chapter, was how to use the Drools Business Rule Management System (BRMS), known as Guvnor. By the time you complete this chapter, you'll share my enthusiasm for this exciting capability—it truly unlocks the power of rules to your business audience.

The last thing we'll cover before concluding this chapter is how we can combine the capabilities of Drools with the service component framework of Apache Tuscany (an implementation of the Service Component Architecture, or SCA), which we covered in chapters 3 and 4. You'll learn how to create decision services that can be a key foundation for SOA-enabling your enterprise. We'll demonstrate why decoupling rules from their historically intertwined role within application logic transitions them into corporate assets that can be reused across many solutions and processes. As we talked about in the previous chapter, the decisions a company makes are what determines its success. Centralizing these decisions, represented as rules, through Guvnor and exposing them as services, will allow them to become substantial contributors to your journey to SOA.

To introduce Guvnor and the decision services that follow, we'll create a real-life case study.

Case study overview

One of the most common, and often complex, requirements for a company is developing a pricing engine that can be used to calculate the price of products or services being quoted to a customer. This is a challenge because you may want the prices to vary depending on any number of factors, such as customer classification, size of order, shipment options selected, promotional sales, or product availability. Many ERP systems include sophisticated capabilities to assist in product pricing, but you're still limited to whatever parameters they support. Of course, if you don't have a million-dollar ERP system in place, determining pricing can be a much more manual and tedious process. The case study we'll examine describes how a pricing engine can be developed using Drools, and then exposed as services that can be readily utilized by a number of applications. Obviously, given the ubiquity of rules, the notion of services central to the premise of SOA can act as a catalyst toward achieving your SOA objectives.

The premise behind the case study is to create a service that, upon receiving a request that contains order details, will return a computed cost for the products selected. The variables that will impact the pricing calculation include the following:

  • Customer classification, such as whether they are a Gold, Silver, or Standard customer

  • Volume discounts based on item count

  • Shipment calculations based on the carrier selected

In order for the pricing and rules engine to perform its calculations, it requires a substantial amount of data in its working memory. For example, it must know the weight of the products to calculate shipping costs, it must be able to look up the classification of a customer, and it must have access to the pricing of the individual products.

Listing 12.1 shows an example, depicted in XML, of an inbound request.

Example 12.1. Example of XML inbound request for rule engine calculation

As you can see, the inbound request includes a customer identifier

<DecisionResponse>
   <orderId>2020322</orderId>
   <salePrice>72.97</salePrice>
   <shippingPrice>12.5</shippingPrice>
   <totalPrice>60.47</totalPrice>
</DecisionResponse>

In order to populate the Drools working memory with the data needed to perform the calculations, a set of DTOs must be created and populated, which can then be referenced when creating the Drools business rules.

An overview of the DTO objects used within the case study is shown in figure 12.1.

The objects shown in figure 12.1 contain no business logic and are simply containers for capturing the required data needed by Drools. The methods, which aren't shown in the figure, are simply getters and setters (accessor methods) for each of the class member variables. The Order and OrderProduct classes represent the instance (volatile) data used to represent an inbound request, and are the class manifestations of the XML shown in listing 12.1 (in other words, these classes are the input used by the rule engine to calculate the pricing).

Note

The complete Java classes for all the classes shown in figure 12.1, plus all samples that follow in this chapter, can be found in the chapter 12 example code.

Let's now take a look at some of the rules used in our case study. You'll become familiar with some nontrivial type examples that truly illustrate the power of Drools.

Figure 12.1. Case study DTO fact classes intended for use by the Drools working memory<br></br> 

Defining the DRL rules

Because of the number of rules that we'll use to perform the pricing calculation, it represents an ideal scenario for applying the Drools RuleFlow feature. As you may recall from section 11.5 in the previous chapter, you use RuleFlow when you want to manage the sequence by which rules can be activated. Further, it allows you to more logically group rules together, thereby simplifying ongoing maintenance. The RuleFlow diagram used for our case study is shown in figure 12.2.

Figure 12.2. Case study RuleFlow diagram illustrating the grouping logic of the rules<br></br> 

As you can see in figure 12.1, three RuleFlow groups are shown:

  • Pricing—This set of rules is used to calculate base prices for each of the product line items. As we'll show in a moment, the price varies depending on customer classification or whether volume discounts apply.

  • Shipping—These rules calculate the shipping costs associated with the order estimate. A variety of factors influence how this is calculated. At the most basic level, the choice of delivery method is the main factor.

  • Special Discounts—This is where special pricing rules can be applied, and this group, as the last, also calculates the final pricing estimate.

Since the complete DRL rules can be found in the example code for this chapter, out of respect for your time, we won't visit each of them individually. However, let's take a look at a sample or two from each RuleFlow group, so you have some context as we move forward. We'll first look at the Discount-LineItemPrice rule, which is used to determine whether discounts can be applied based on customer classification.

DISCOUNT-LINEITEMPRICE RULE

The Discount-LineItemPrice rule belongs to the Pricing RuleFlow group (the actual id assigned for this group is price-calc). This RuleFlow group is used for rules that determine line item pricing (the line items are captured in the OrderProduct class, which has a one-to-many relationship to Order). In this rule example, the discount price of the product, as captured from the ProductPrice object, is applied to all customers who are classified as either GOLD or SILVER. In natural language pseudo-code, the rule can be described as: "When the customer is either SILVER or GOLD, then use the product discount price for calculating the cost for each of the line items in the order." In DRL form, this is expressed as shown in listing 12.2.

Example 12.2. Rule for calculating line item discounts

Let's examine listing 12.2 in a bit more detail. The first thing we're doing is identifying the inbound quote request (Order) we're working with by assigning that object to the alias order

The OrderProduct (represented by the alias item) includes the class member variable stdPrice. When the instance fact representing the order pricing request is received by Drools, this value is null. However, this field gets assigned a value in the consequence portion of the rule

TOTALENDPRICE RULE

When 10 or more units are ordered for a particular line item, we also want to apply a quantity-based discount. If this condition occurs, the OrderProduct member variable called qntDiscountPrice is assigned as the line item price. So if the standard price of item X is $9.99, and 10 units were specified, then the qntDiscountPrice would be set to $99.90 with a 5 percent discount applied to that value, leaving a total of $94.90. When computing the order's total (minus shipping), this volume discount is then factored into an equation, as we see in the business rule shown in listing 12.3. This rule also happens to be the last one run for the Pricing RuleFlow group (this is controlled by using its salience value, which is set to a low-value of 5).

Example 12.3. Rule to calculate total price based on aggregated line items

In the TotalEndPrice rule (listing 12.3), the accumulate conditional is used to iterate through the OrderProduct line items

Turning to the consequence part of the rule shown in listing 12.3, a new instance of TotalPrice is instantiated

The examples shown in listings 12.2 and 12.3 are good illustrations of nontrivial type rules. If you've been able to successfully follow the logic of these rules, you have a strong command of how rules can be fashioned using Drools. If you're struggling a bit, I suggest reviewing chapter 11 to refresh your understanding.

The remaining rules associated with the Shipping RuleFlow are fairly straightforward and follow a pattern similar to those we've just discussed (that is, primarily iterating through the individual line items to establish an item's weight, then applying a multiple based on the carrier selected along with assigning a minimum charge, if appropriate). Let's move on to examining one last rule, this one from the last RuleFlow group, in which we print out the final total price and update the TotalPrice object within the working memory. This final rule is shown in listing 12.4.

Example 12.4. FinalTotals rule used to calculate final pricing order estimate

As you can see in listing 12.4, the conditions for activating this rule are minimal—if the TotalPrice.salePrice value is 0

In the consequence (then) portion of the rule, we first set the TotalPrice.salePrice variable as the sum of TotalPrice.totalPrice and TotalPrice.shippingPrice

What's the upshot of all of this? The TotalPrice object updated by the rule now contains the computed values that represent the output to be returned to the calling client. The question now becomes, how do we retrieve this data from working memory so that it can be returned to the client application/user? There are a couple of options to achieve this:

  • We could iterate through the working memory fact handles using the method WorkingMemory.iterateFactHandles(), and then check the class name to locate the TotalPrice object that contains the computed estimate.

  • We could store the TotalPrice in a named query so that the method WorkingMemory.getQueryResults(<string>) could be used to locate the results.

The latter option is more straightforward, so let's use this approach. We defined the following query in the DRL file associated with the Special Discounts RuleFlow group:

query "FinalPrice"
  total : TotalPrice ( salePrice > 0.0 );
  order : Order (orderId == total.orderId);
end

This query is activated when the two consequence statements are true. Notice that the first one checks whether a TotalPrice.salePrice has been assigned (that is, salePrice > 0). As you recall from a moment ago, this salePrice was set during the rule shown in listing 12.4. So only after this rule is activated will this pattern be satisfied. In the next section you'll see how this query is retrieved.

Now that we have our rules defined, let's explore the process for invoking them.

Running as an embedded engine

There are several approaches for how Drools can be used. The most common is using it in an embedded fashion. Using this approach, you can incorporate the rule engine directly within your application code. We'll describe later in section 12.3 what's likely a better approach—exposing Drools as a decision service. However, since the decision service approach shares much in common with how it can be used as an embedded engine, it's worthwhile to begin with the embedded approach.

If you use the Eclipse IDE for creating a new Drools project, it will optionally create some example, starter-style classes that you can begin building on. I recommend trying this, because it will shed much light on how Drools can be run using an embedded style setup. We'll expand on what's automatically generated via the Eclipse plug-in and refactor it to make it more suitable for reuse.

Let's begin by providing an overview of the steps required for running Drools in an embedded fashion:

  1. Create the RuleFlow and rules (rule assets). We tackled this in the previous section, and have our rule DRL files and flows created.

  2. Create and populate the RuleBase. This Drools class is used to load, parse, and store the rule assets.

  3. Create a WorkingMemory session from the RuleBase. This creates a working memory container for populating the fact data required by the rules engine for processing.

  4. Load the WorkingMemory. Populate the working memory with fact data.

  5. Start the RuleFlow process. If a RuleFlow process is used, it must be started using WorkingMemory.startProcess(<processId>).

  6. Activate the rules. This is done using the method WorkingMemory.fireAllRules(). This will run the rule engine using the rule assets and working memory.

  7. Query the results from the named query. We covered in the previous section how query results can be retrieved, and this represents the last step in the process (we'll review it in context with the rest of the code for completeness).

Since we already created step 1, let's jump right to step 2, and see how to create a RuleBase.

STEP 2: CREATING AND POPULATING THE RULEBASE

To break things into more discrete functions, we've created a helper class called SessionHelper that we can use to perform the individual steps and also simplify the code. The first static method we'll create in this class is for instantiating the RuleBase (see listing 12.5).

Example 12.5. Static method used to create and populate Drools' RuleBase

The first statement shown in listing 12.5 is used to create an instance of PackageBuilder

Note

Instead of hard-coding the rule file assets within the readEmbeddedRules() method in listing 12.5, you could make the method far more reusable by passing them dynamically as a Map to the method, but to keep things simple, we didn't go this route.

STEP 3: CREATING THE WROKING MEMORY

Now that a RuleBase has been created from the prior step, we can use it to create a WorkingMemory session. To do this, we'll use the RuleBase.newStatefulSession() method (you can optionally create a stateless session, where the working memory isn't preserved after the rules fire). The static method SessionHelper.getEmbeddedSessionURL() used to create the WorkingMemory is shown in listing 12.6.

Example 12.6. Method used for instantiating WorkingMemory from a RuleBase

The first order of business is to call the readEmbeddedRules() method we created in listing 12.5

Now that we have the WorkingMemory instantiated, we can use it to populate the facts necessary for the rule engine to perform its magic.

STEP 4: LOADING THE WORKING MEMORY WITH NONVOLATILE FACTS

The process of loading the fact data into the working memory is very simple. To do so, you call WorkingMemory.Insert(<object>). In our case study, we're using the LoadData class to unmarshal XML files into Java data objects using Apache Commons Digester. Once the objects are populated via this class, we create another method called loadNVWorkingMemory() in the SessionHelper class that loads each of the objects required for the pricing calculation. Here's an example of the ProductPrice objects being loaded into working memory from within the loadNVWorkingMemory() method:

ArrayList<ProductPrice> prices = ld.loadPrices();
for (ProductPrice price:prices) {
  workingMemory.insert(price);
}

The loadNVWorkingMemory() method only is responsible for loading what we term the nonvolatile facts—that is, those facts that aren't altered by the rules and don't frequently change (such as a product catalog). We'll see in a moment how we load the volatile, or instance facts, just prior to invoking the engine. However, before we do this, we have one last housekeeping chore to attend to: loading the RuleFlow process.

STEP 5: STARTING THE RULEFLOW PROCESS

As you recall, we used the RuleFlow process feature in this case study to more easily manage the sequence by which rules can be activated. While you can often do without it, the visual nature of the RuleFlow process diagrams can aid in maintenance and assist rule authors in devising the proper rule activation sequences. You identify the correct RuleFlow process to use by calling the WorkingMemory.startProcess(<rule-flow-process-id>) method. The <rule-flow-process-id> value represents the id assigned when developing the Eclipse IDE RuleFlow process (the Properties view is generally always present when working in the Java perspective, but if not, you can add it to your current perspective by choosing Window > Show View). You can display the process properties by clicking in the main diagram window, and the properties should appear similar to what is shown in figure 12.3.

Note

You don't need to use the Eclipse plug-in to create a RuleFlow—you can do it manually since it's just an XML file.

Figure 12.3. RuleFlow properties window, highlighting the process ID<br></br> 

So the statement needed to start up the RuleFlow looks like this:

workingMemory.startProcess("opensoa.drools.pricing");

We'll show this in context next when we discuss activating the rule engine.

STEP 6 ACTIVATING THE RULE ENGINE

You activate the rule engine with a single method call, WorkingMemory.fireAllRules(). However, first we need to populate the instance data, which represents the input to the rule engine. This instance data is also just working memory objects, but is transitory in nature and will generally be removed once the rules are processed (unlike the nonvolatile working memory, which is persisted). So in our case, the instance data is represented by the Order (and includes an association with one or more OrderProduct classes that represent the individual line items of the order). For testing purposes, you'll find in the example code for this chapter a Java main class called EmbeddedDrools. Listing 12.7 shows the method that's used to invoke the engine.

Example 12.7. Activating the rules engine to process the results

The runEngine() method shown in listing 12.7 requires two parameters: an XML file to be loaded using Commons Digester, representing the instance data (Listing 12.1, for example), and the WorkingMemory instance we created in step 3. The instance data is then processed into working memory

STEP 7: QUERYING THE RESULTS FROM THE NAMED QUERY

As you may recall from section 12.1.1, we created a named query called FinalPrice in the last RuleFlow group that contained the pricing results derived from running the rules engine. The static method SessionHelper.showResults(), shown in listing 12.7, is used to process the query results. Let's examine this in more detail in listing 12.8, as it contains the results of the rule engine processing.

Example 12.8. Method used to return results from the rules engine

The purpose of the showResults() method is to retrieve the query results from the engine, locate the referenced objects returned by the query, clean up the working memory of the results, and return the results to the caller of the method. The WorkingMemory.getQueryResults(<named-query>) method is used to retrieve a named query from the engine. Here the query is named FinalPrice

query "FinalPrice"
  total : TotalPrice ( salePrice > 0.0 );
  order : Order (orderId == total.orderId);
end

Returning to our code in listing 12.8, since we have just a single query with this name, we can safely assume that the first returned QueryResult resulting from the QueryResults iterator is the object we need

What remains is to remove the instance facts from working memory, since they're no longer needed and would cause future complications if left in memory (that is, once the order is estimated, there's no need to keep them in working memory). To remove the instance fact, we use the objects retrieved as a signature to create a FactHandle

Let's recap what we've accomplished so far. We went through the end-to-end process of creating the rules, running them, and then retrieving the results. At this point, we have a completely operational rules engine solution, albeit one that could only be embedded within a given application. Before we wrap up this section, let's consider how things might work using the DSL capability of Drools instead of the DRLs we've created. In the previous chapter, we explained how this works, so we'll assume that you've read that material and won't revisit the basics of how it works. The objective is to reinforce what you learned previously about DSLs by applying them to a real-world type scenario. This will also help contrast the differences between standard DRL rules and the more natural language-like DSL-based rules.

User-friendly rules using a DSL

The main impetus behind using the DSL capability is that you can craft more natural-language representations of your rules. This is particularly relevant when you want to give subject matter experts in your organization the responsibility of rule authoring—something they're often enthusiastic about assuming. Nontechnical users might find it challenging to create DRL rules, as the process assumes a high degree of knowledge of the Drools language. However, as we'll see, DSL-based rules are much easier for the average user to understand, and thus increase the likelihood of their participation in managing them (anytime developers have to translate user requirements to code, there's always an opportunity for subtle errors to be introduced because of the translation that must occur).

For example, let's consider the rule used to determine whether a quantity-based discount can be applied. In DRL format, this rule was defined as follows:

rule "Quantity-Discount"
ruleflow-group 'price-calc'
salience 30
 when
  order : Order();
  item : OrderProduct ( qntDiscountPrice == 0, price : stdPrice,
    cnt >= 10, stdPrice > 0 ) from order.getLines();
  then
   item.setQntDiscountPrice ( price * .90f );
   update ( order );
end

In pseudo-code, this rule is stating that "If an order exists, and no quantity discount has currently been applied but a standard price has been computed and the unit count is >= 10, then apply a 10 percent discount." While a trained eye could read the DRL rule and interpret it the same way, a far more readable representation, in DSL, is

rule "Quantity-Discount-dsl"
ruleflow-group 'price-calc-dsl'
salience 30
 when
  There is an order
  Retrieve calculated order items which exceed a quantity of 10
 then
  Log : "Applying volume discount"
  Apply line item discount of .10 to previously calculated price
  Update the order
end

The pricing.dsl template file, located in the example code for this chapter, contains the definitions that make this DSL feasible. For example, the second condition statement is defined using this template (shown word-wrapped, but normally it's not):

[when]Retrieve calculated order items which exceed a quantity of
{x}=item : OrderProduct ( qntDiscountPrice == 0, price : stdPrice, cnt >=
{x}, stdPrice > 0 ) from order.getLines();

As you can see, the only parameter replacement required by the author is to specify the number of items that qualify for a discount (represented by item). However, this statement does require a prior condition (such as the DSL statement defined as "There is an order") in order to receive a handle to the order alias referenced in the right-hand side of this template for retrieving the order items. The complete example can be found in the source code.

The only difference when using a DSL from what we described in the previous section is that the DSL template must be specified as an additional parameter to the PackageBuilder.addPackageFromDRL() method. Consider this example:

Reader DSLsource = new InputStreamReader(
  DroolsTestDSL.class.getResourceAsStream( "/pricing.dsl" ) );
Reader source = new InputStreamReader(
  DroolsTestDSL.class.getResourceAsStream( "/CalculatePriceFlow.dslr" ) );
builder.addPackageFromDrl( source, DSLsource );

As you can see, we read the DSL template file (pricing.dsl) and DSL rule file (Calculate-PriceFlow.dslr) into a Java Reader, both of which are then passed as parameters to the method addPackageFromDRL(). In other words, when loading a DSL file, you also need to specify the DSL template (by convention, it ends with the .dsl extension). That's the only distinction in what we covered in the last section.

We've now explored the basics of the case study, and this real-life example builds on the knowledge you gained in the previous chapter. With this foundation in place, we can now move toward discussing the Drools Business Rule Management System (BRMS), otherwise known as Guvnor. This feature is essential for enterprise adoption of the business rules approach, since it allows for centralized management of rule assets. It also is the foundation for building SOA-style decision services, which we'll cover in section 12.3.

Rules management using Drools Guvnor

One of the most exciting developments that occurred with the 4.0 release of Drools was the introduction of the Guvnor BRMS. This multiuser web application can be used to centrally house all of your business rules, provide tools for management and editing of the rules, and support versioning and lifecycle management. A fair amount of criticism was leveled against the first release, claiming that it lacked essential functionality such as user management. However, it's important to bear in mind that this was the first release of the BRMS component, and I can happily report that the 5.0 release appears to address many of the cited deficiencies (for our examples, we're using the 5.0CR1 release, which is a beta version).

The official documentation provides an excellent resource for how to install and maintain the BRMS [DroolsUserGuide], so our focus will be on introducing key functionality so that you can assess whether it's an appropriate solution for your environment. Let's start by taking a look at the main navigational hierarchy of Guvnor, which will provide a good overview of its functionality.

Guvnor functionality overview

The Drools team has done an outstanding job of developing the user interface for Guvnor. Built using Google's GWT toolkit, it offers a rich, "thick client-like" interface that's highly intuitive. When first logging into the web application, you'll be presented with the screen shown in figure 12.4.

Figure 12.4. You'll see the Drools Guvnor main menu when logging in as administrator<br></br> 

As the screenshot in figure 12.4 illustrates, several different audiences might use the solution. For rule authors, it's anticipated they'll spend the bulk of their time within the Assets view module. Developers, on the other hand, will be focused on the Packages and Package snapshots modules. Those maintaining the system will primarily work within the Administration module, with quality assurance and testing working within the QA module. Let's take a look into each module so we can understand its purpose and usage.

ASSETS VIEW MODULE

The Assets view module is primarily where rule authors and business users will operate. It serves two purposes: it's a means of navigating through categories of existing rule assets, and it provides the ability to create new rules or potentially archive existing ones. Guvnor includes a categorization capability that lets you define your own custom categories and assign rule assets to one or more of them (it's similar in concept to tagging, though you can't dynamically create new ones since that must be done via the Administration module). A category can be considered more of a logical view of rule assets, as opposed to how they're physically grouped within packages. For example, you may have categories that correspond to your main business functions, such as HR and Finance. These categories can also be used to manage visibility of the rules, since you can specify which groups have rights to view the category tree (the user logged in for the figure 12.4 snapshot has full admin rights, so everything appears). Figure 12.5 shows the Assets view module when a category has been selected that contains rules (in this example, a business user is logged in with restricted module options using the pricing estimator case study rules we've been working with in this chapter).

As figure 12.5 shows, if a category contains rules, it will appear in the rule pane listing on the right. If you double-click on a rule, it will take you into the rule editor, which we'll cover in a moment. Users with permission may also be able to navigate through the rules by rule status. As with categories, rule statuses are defined by the administrator, and typically are values such as Production, QA, Development, and the like. Statuses are used as part of the workflow release cycle, which is why they aren't, by default, made visible to normal business users and authors. If users have the appropriate permission, a pull-down menu will appear below the Asset view that enables them to create a new rule asset, as shown in figure 12.6.

Figure 12.5. Example of Asset view category listing<br></br> 

Figure 12.6. Selecting which type of business rule asset to create using the Create New pull-down<br></br> 

Let's now take a look at the Packages module, which is used by administrators for setting up new rule domains.

PACKAGES MODULE

In chapter 11's Hello World, Drools example, we briefly touched on what a rule package means within Drools. All rule assets are associated with a given package. Similar to Java, it can be thought of as analogous to a namespace, so that similarly named rules can exist in multiple locations without conflicting with one another. In Guvnor, it also plays an important role in rule authoring, as a model of the working memory facts must be loaded into Guvnor so that it can use the objects within its rule editor. So one of the first steps required when using Guvnor for a new rule domain such as our pricing estimator is for the administrator to create a new package where the fact model and rules can then be loaded and associated.

Note

I recommend that you use the same package naming conventions for both your Java working memory objects and their related business rules where they're referenced. For example, if you're using the rule package of mycompany.finance.pricing, then use the same name prefix for your related Java classes. This becomes more important as you move toward a decision services paradigm, where many rule domains may be running within a single rules engine instance.

When logged in as an administrator and selecting the Packages model, you'll see a Create New pull-down menu like the one in figure 12.6. The menu includes an option to create a new package. Once you create the package, a classification of asset types appears in a tree fashion associated with the new package, as shown in figure 12.7.

Figure 12.7. Package assets types<br></br> 

When you click on a given asset type such as Business rule assets, you'll then see a listing of the associated items belonging to that type. Using the Create New pull-down, you can add items to any of the type categories shown.

The first thing you do when configuring a new package is add a new model, which is a standard JAR file containing the working memory fact objects. You use this file when authoring rules so that you can build condition patterns based on the fact objects loaded into the model (think of it as a form a reflection). To add an existing DRL rule file, select the New Package option from the Create New menu. You can then specify the package name (such as opensoa.drools, as we used in our case study), and then select a DRL file to upload. If the package already exists in Drools, it will simply append the rules to the existing package as Technical rule assets without creating a new package. Once you have a package and at least one category defined, you can begin authoring rules using Guvnor.

The various asset types associated with a package are discussed in table 12.1.

Table 12.1. Package asset types<br></br> 

Asset type

Description

Business rule assets

When you're creating business rules using Guvnor's guided DRL or DSL editor, they'll appear within this classification. See section 12.2.2 on creating rules using the guided editors.

Technical rule assets

When you're importing existing DRL rule assets or when creating them in Guvnor as a "technical rule - text editor," they'll appear here.

Functions

You can upload DRL functions, which will then appear as items in this list. Generally, I recommend instead creating Java helper-style classes since it promotes greater reusability.

DSL configurations

You can create new DSL mapping templates using this asset type.

Model

You can upload one or more JAR files that represent the fact model used by the particular package you're working within.

Rule Flows

When you upload a new RuleFlow it will appear in this list when selected.

Enumerations

Enumerations are used in the guided editor to restrict field values to certain values. For example, in our case study, we spoke about STANDARD, GOLD, or SILVER customer classifications—an enumeration could be created restricting choices to these values. We'll illustrate how enumerations are used in section 12.2.2.

Test Scenarios

Drools 5.0 introduced a new testing framework for testing rules. With it, you can create testing scenarios that will appear in this list. You'll see a service-based approach in section 12.3.4 that I believe is superior to this framework.

XML, Properties

As of the 5.0 release, it wasn't entirely clear what this asset type is for—there doesn't appear to be a Create New option to create this asset type (this will presumably change by the final release).

Other assets, documentation

You can upload virtually any supporting file, such as Word documentation, and it will appear in this listing. You can create these asset types by selecting Create New > New File.

The Package view module, as you may have deduced, is intended for administrators, and allows for quick-and-easy access to all assets that constitute a given package. As we pointed out, it also plays an essential role for DSL language developers, as this is where the DSL templates are managed.

One last piece of important functionality available through this module is the ability, when you click a specific package, to view the configuration details of the package such as imported class types and global definitions (you can click Advanced View to modify imports). The Build and validate section, shown in figure 12.8, allows you to validate that the package is error-free—you'll want to perform this periodically to ensure you can successfully build a binary package.

Figure 12.8. Verifying package is error-free using build and validate package<br></br> 

As you can see, in addition to building a binary package, you can download it. But what exactly is a binary package? It combines all rule assets together in a single file that can then be loaded using the Rule Agent capability (which we'll describe how to use in section 12.3). This is a very convenient feature, because it eliminates the need to load the rule assets manually as we had to do in our prior examples, such as in listing 12.5. We'll discuss binary packages more next in our coverage of the Package snapshots module.

Note

What happened to the QA module? We elected to not cover this functionality as it was very much a work-in-progress at the time of this writing and had little supporting documentation. In our experimentation with this feature, we found it lacking for creating anything beyond the most simple type tests.

PACKAGE SNAPSHOTS MODULE

This module serves an important purpose: it creates binary deployment snapshots. A snapshot can be considered a point-in-time code freeze. Once a snapshot is made, any other current in-progress or future changes won't affect it. Indeed, internally the snapshot version is moved into a different location in the Java content repository (which coincidentally, uses Apache Jackrabbit). There are only a few possible actions you can take in this module, as shown in figure 12.9.

Using the Deploy pull-down menu shown in figure 12.9, you can create new deployment snapshots. When doing so, you can choose an existing snapshot name, in which case it will override it with the new one. Alternatively, you can specify a new unique name, thereby creating an entirely new snapshot. Each new snapshot will result in a new tree node displayed below the package tree. When you click on a specific snapshot, a new tab will appear on the right, which when you expand the node, will display all the rule assets associated with that particular snapshot, reflecting the point in time in which the snapshot was taken (remember, a snapshot is a point-in-time freeze of all rule assets associated with the package).

Figure 12.9. Package snapshot module displaying available options<br></br> 

The objective of taking a snapshot is for use by the Rule Agent for loading rule-related assets. As shown in figure 12.9, once a snapshot is generated you can either download a binary package of the snapshot, or right-click on the Deployment URL link to capture the URL (as you'll see in section 12.3, the Rule Agent can be configured to work with a binary file or HTTP URL).

The snapshot capability will likely have significant implications for your enterprise deployment of Drools. With it, you can more effectively manage the release process associated with your rules. If you so choose, you can version the binary using a package such as CVS or Subversion. Creating snapshots also enables rule authors and testers to modify rule assets without inadvertently affecting your production environment. I strongly recommend using it.

Note

Binary packages can also be generated through Ant tasks, which is demonstrated in the example build scripts which accompany this chapter's source code.

We're nearing the end of our functionality tour overview. These features lay the foundation for our coverage of decision services for SOA, which follow in section 12.3. The last module is Administration, and it's used for a combination of housekeeping, code list management, and user permissions.

ADMINISTRATION MODULE

The Administration module has options for managing the subnodes identified in table 12.2.

Table 12.2. Administration subnodes<br></br> 

Node

Description

Categories

In the Assets view module, we described how categories are used for logically grouping rule assets. These categories are set up using this node, and it is intuitive and easy, so we won't describe the process here.

Archive items

For most rule assets, there's an option to archive items that are no longer desired or used, rather than deleting them outright. When this node is selected, you can view all archived items, and they're categorized by type, such as archived snapshots and rules.

Status

You can manage the available statuses that can be assigned to rule assets using this node option.

Import/Export

As of the 5.0 release, this seems something of a work in progress. You can create a zip-based export of your entire repository or of a single package, but there are only options to import from an XML file (oddly, you can't export to XML). Instead, for backing up Guvnor, I suggest reading the section on data management in the Guvnor User's Guide.

Error log

Log messages that are defined as using INFO or ERROR are displayed in this view. They pertain only to actions performed as part of Guvnor's usage, and not to specific rule assets.

User permission mappings

New with the release 5.0, this enables you to create users and assign them predefined roles, such as admin, analyst, and package admin. It's designed for managing authorization, not authentication. That is, you can define a user, but your authorization must be configured using Java Authentication and Authorization Service (JAAS). This is outside the scope of this chapter, but is covered in the official Guvnor User's Guide.

You'll likely use the Administration module only infrequently and only if you're responsible for managing the overall BRMS. During the initial setup, however, statuses and categories will need to be configured.

We've now covered all the main navigation options available through Guvnor, but haven't really touched on the main purpose of the system: to manage the creation of rules. Before concluding this section on Guvnor, let's spend some time on this important area.

Rule authoring using Guvnor

In addition to providing a centralized location to house your business rule assets, one of the main reasons for adopting a BRMS is to allow business users to author and manage their domain rules, ideally through a "zero-install" client such as a web interface. While the Eclipse IDE Drools plug-in offers nice authoring functionality, rolling this solution out to a significantly sized user base is problematic, not to mention the learning curve involved in using Eclipse for nondeveloper types, issues of source code control (which is managed transparently in Guvnor), and... well, you get the picture.

As we'll see, Drools Guvnor, while aiding the rule author with many useful features intended to lower the technical barrier in creating rules, is still far removed from being a tool that can simply be handed off to the average business user. Instead, there's often a subset of business users or analysts in any organization who are fairly technology competent, and Guvnor is intended for these power users (you know the type—those users who become experts at Excel macros or Visual Basic scripting and can cause headaches for IT groups). Our experience is that these individuals, with some training and/or handholding, can quickly become proficient in rule authoring.

To provide the greatest flexibility in rule authoring, you can create rules in Guvnor in one of these five ways:

  • Business rule (using a guided editor)—Probably the most popular of the five, the guided editor presents the user with a wizard-like approach for creating rules, based on the model objects that have been loaded as part of the package. This approach is suitable for technical business users.

  • DRL rule (technical rule using text editor)—The author is presented with a basic text area where he or she can edit a rule in a freehand fashion. The model fact objects are shown as a convenience, but authoring is done by hand. This approach is aimed at advanced users who understand the Drools language.

  • Business rule using a DSL (text editor)—This approach is suitable when using a DSL, and the author can then use hints for constructing the various rule phrases. This approach is suitable for technical business users.

  • Decision table (web using guided editor)—Enables the rule author to dynamically create decision tables. You define the columns that represent the facts and actions, and then use rows to specify the various rule conditions. Decision tables are used when you have a lot of possible rules but a fairly small set of fact objects with little variability in conditions.

  • Decision table (spreadsheet)—Allows the user to upload a spreadsheet file that contains the decision table spreadsheet. The spreadsheet template must adhere to the format, as described in chapter 11.

As you can see, quite a few options are available, and selecting which authoring type to use largely depends on your audience and the type of rules you're working with. Although we won't go into specific details on using each of the editor methods, let's take a look at some of the common features you'll find regardless of the type of authoring you're using.

COMMON EDITOR FEATURES

Figure 12.10 depicts authoring using a DRL rule (technical rule using the text editor).

As you can see, the editor consists of three main panels. The first is the editing window, where the rule author in figure 12.10 is editing the DRL rule by hand. Notice below the editing panel is a Validate option. This allows you to check the rules syntax for errors—which is a very handy feature. The notes area is a documentation mechanism, and to the right is the metadata panel, which is present in all DRL and DSL editors. In that window, you can assign or modify which category the rule is assigned, and edit any tag-related keywords such as subject and type, which is beneficial for later searching. You can even assign external links that can be used for additional documentation. Also in the metadata pane, you can see automatically assigned metadata such as the version number, author ID, and the dates the rule was first created and last modified. Related is the Version history button link, which allows you to go back to previous versions of a rule, and even restore it, if need be. This function can be useful when you're troubleshooting errors that may have been inadvertently introduced in later releases of the rule.

Figure 12.10. Authoring using a DRL rule<br></br> 

The two most common types of editors your users will likely use are the guided business rule and DSL editors. Let's take a brief tour of their capability before concluding our section on Guvnor.

CREATING BUSINESS RULES USING THE DRL GUIDED EDITOR

The DRL guided editor represents a novel approach for creating business rules. Using the fact models associated with each package, and any defined enumerations, it lets you create fairly sophisticated rules without requiring in-depth understanding of the Drools language (and you don't have to set up a DSL). An example of the editor is shown in figure 12.11.

When you first launch the editor, only the WHEN, THEN and (options) portions of the rule are shown. You build each of these respective sections by clicking the large green plus sign to the right of each section. A wizard-style dialog opens where you identify the fact objects along with other part-specific options. Figure 12.11 shows a rule used for assigning minimal shipping amounts where the shipment carrier is DHL and the shipment method is STANDARD. Notice both of these are pull-down values, as we created an enumeration for these values (see the Guvnor User's Guide for instruc-tions on setting up enumerations). When creating conditions, you can also assign variables, as shown in the third condition in the example, where orderId is assigned. These bound variables can then be used downstream in other conditions (such as for associating the TotalPrice fact object) or in the consequence/then portion of the rule. Not shown in the figure is an option that lets you view the source associated with the guided rule. In this case, the source is shown as

Figure 12.11. Example of using the DRL guided editor<br></br> 

rule "DSLExample"
salience 10
dialect "mvel"
when
  Order( shippingCarrier == "DHL" ,
   shippingMethod == "STANDARD" , orderId : orderId)
     totalPrice :
      TotalPrice( orderId == orderId , shippingPrice < "8" ) then
   totalPrice.setShippingPrice( 8 );
   update( totalPrice );
end

As you might imagine, it's far easier for business users to use the guided editor rather than crafting the rules by hand. Even easier still is using the DSL editor, which we'll address next.

CREATING BUSINESS RULES USING THE DSL EDITOR

In chapter 11, we discussed how the DSL functionality works in Drools, and showed how you can use it to create business rules using a nomenclature specific to the domain area in which you're working. The Eclipse IDE plug-in offers excellent support for this feature, and so too does Guvnor. Before using the DSL editor, you must create the DSL template file, which defines the language constructs. Doing this in Guvnor is identical to what we described in the previous chapter. For example:

[when]There is an Instance with field of "{value}"=
  i: Instance(field=="{value}")

Once you've defined the DSL templates, you're ready to use the DSL editor. Figure 12.12 shows an example of the DSL editor in use within Guvnor.

Figure 12.12. Example of DSL editor in use within Guvnor<br></br> 

Figure 12.12 shows the context-sensitive rule-tip mechanism. If you're editing either the when or then portion of a rule and press Ctrl+Spacebar, a pop-up will appear (you can also invoke the pop-up by clicking one of the icons to the right of the editor, as highlighted in the figure). From there, you can select one of the various phrases that are defined with the DSL template file. The phrases are contextually sensitive, so when statements will only appear when working on the conditional portion of the rule. This approach makes things particularly easy for those not versed at all in the Drools language, but does entail some setup by an expert to create the DSL template phrases.

In the last two sections, we've introduced you to the specifics of our pricing estimator case study, and then demonstrated how the rules can be managed in tandem with Guvnor, the Drools BRMS. We've laid the groundwork for what comes next: creating decision services using Drools.

Developing decision services

The term enterprise decision management (EDM) has recently entered into the lexicon of famous IT terms, joining other popular new concepts such as cloud and utility computing. What exactly does EDM mean? While you may get different answers depending on who you ask (it's the same with SOA), at its core EDM is the automation of operational decisions that drive an organization's business. It includes the management and rule repository features we've described in our discussion of the Guvnor BRMS. More importantly, it's a philosophy that dictates that the critical decisions that drive your enterprise be separated from the application code or BPM processes.

Why is this separation important? It's because business rules and decisions often change much more frequently than business processes, and hard-coding them within applications is one major reason why ongoing software maintenance costs are such a substantial portion of total IT software budget on a year-to-year basis. Further, when rules are centralized using a decision services approach, they can be leveraged and reused across multiple applications, and form the basis for ongoing business optimization and improved agility. As James Taylor points out, "Treating decision logic as a manageable enterprise resource in this way means that you can reuse it across multiple applications in many different operational environments" [Taylor]. He later equates this approach to providing the brains for creating composite applications, a key tenet of SOA—the rules (or decision) services approach.

What are the central tenets of a decision service? We'll examine these next.

What are decision services?

A decision service has the following characteristics:

  • Stateless—The decision service should be stateless—each call to it is self-contained and not dependent on any prior call. This facilities scalability and simplifies service virtualization. While each call is stateless, the working memory itself may contain persistent, nonvolatile data, such as a product catalog, which is periodically refreshed.

  • Virtualized—Client systems utilizing the service should do so through a logical URI, so that they need not be aware of the actual specific endpoint used to fulfill the service. This is where a web mediation service such as Apache Synapse can play such an important role, since it can manage directing each service call to the proper endpoint address (Synapse was the topic of chapters 9 and 10).

  • Autonomous—The decision service itself should not be dependent on any specific application, with working memory populated through an abstraction layer independent of its source location. We'll illustrate how this can be done using a cache solution such as PojoCache by JBoss.

  • Protocol and transport neutral—Ideally, the decision service should be accessible via a variety of protocols, such as SOAP over JMS or HTTP, EJB, or REST.

  • Auditable—Each call to the decision service should be auditable so that calls can be analyzed for business optimization and compliance. For example, each call should result in events being generated that can then be processed by an event stream processor (ESP) such as Esper, the topic of chapter 8.

Now that you have a good idea of what constitutes a decision service, let's consider how it can be implemented using the Open SOA Platform stack. We can create the service components using Apache Tuscany and the SCA implementation (which was the topic of chapters 3 and 4), and expose them using any of the available protocols and bindings supported (which include SOAP, JSON, EJB, and RSS/ATOM). The overall architecture is depicted in figure 12.13.

As you can see from the diagram, the service is defined using a WSDL, which enables easy consumption by diverse platforms such as Java and .NET. Guvnor's role is the management and repository of the business rules, and when snapshots are published through Guvnor, the updated rules are reflected in the production decision service.

Figure 12.13. Top-level architecture of decision services, illustrating the role of Apache Tuscany<br></br> 

For the remainder of this chapter, we'll describe how to create such a decision service using our case study as the basis. Let's begin by tackling the overall design and introducing a new technology intended to address challenges in populating the Drools working memory.

Designing the decision service

One of the thorniest issues regarding a decision service is how to populate the nonvolatile data into the Drools working memory. In many cases, this will entail a substantial number of data classes. In our pricing engine case study we introduced in section 12.1, this was exemplified by the product, pricing, and shipping objects that were required by the rules engine to determine a pricing estimate. It's not practical, in many scenarios, to use a web service to populate working memory, because the volume of data required is too substantial. Instead, the approach we'll advocate uses JBoss Cache, PoJo edition [POJOCache] as the vehicle by which to transmit nonvolatile working memory data to the decision service. Let's examine this approach more closely since we haven't previously discussed this technology.

USING JBOSS CACHE FOR WORKING MEMORY PROPAGATION

JBoss Cache is officially described as a tree-structured, clustered, transactional cache. It's used extensively in JBoss products, such as in their application server for clustering support, and is very mature and proven. You could think of it as a simplistic, distributed in-memory database, but instead of using SQL-style query statements, you use fully qualified names (FQNs) derived from a tree-style navigation hierarchy. While this all may sound complicated, as you'll see, it's just the opposite and is very straightforward.

How do we envision using JBoss Cache for populating the Drools working memory? When the decision service is launched, it will create a new cache. Then that cache will be populated with Java data objects that ultimately will be loaded into the Drools working memory associated with the decision service. Who and how those data objects get deposited will depend on your environment. For example, in our case study the nonvolatile working memory includes product, pricing, and shipping cost information. This information would likely be harvested from an ERP-type system using an ESB like Synapse to periodically poll and populate or update the cache. In section 12.3.3, we'll show an example of populating the cache using standard Java code.

Once the cache is populated, some trigger would still be necessary for the Drools decision service to grab the cached objects and populate them into its working memory session (as you'll see in a moment, a Load operation can be used for this purpose). Figure 12.14 shows how the process to harvest and populate working memory objects might look.

Figure 12.14. A process to harvest and populate working memory objects<br></br> 

As you can see, a few steps are involved, and they'd vary depending on where the working memory data originated. However, once such a pattern is identified, it can be reused. For purposes of our case study, we'll pick things up where the Java data transfer objects (DTOs) are being populated into JBoss Cache. We've already briefly discussed the Load operation, but let's take a high-level look at the WSDL to see how it defines all of the various operations required by the decision service we're creating.

TOP-DOWN WSDL DESIGN FOR OUR DECISION SERVICE

When working with Apache Tuscany, you can approach things in two basic ways:

  • A top-down approach in which you define your WSDL first and then generate the components for each operation

  • A bottom-up approach in which you first create your components and then use Tuscany's ability to autogenerate the WSDL

Although the second approach can often be easier and faster, a top-down approach yields a WSDL that's likely more intelligently defined and extensible. For these reasons, I always advocate a top-down approach, which is how I went about constructing the WSDL for the decision service. The first thing I did was identify the necessary operations.

Since our intention is to create a flexible WSDL that's not entirely specific to our case study, I defined operations using a set of verbs that are generic in nature:

  • Decision—The main operation, Decision is called by clients who want a decision rendered. The instance data that's passed is obviously specific to the domain, so in our case study, it represents the details of the order in which the pricing estimate is performed.

  • Load—As previously mentioned, Load is used to update the Drools working memory using the objects culled from JBoss Cache. As currently designed, this is a global operation, and will reload all working memory objects associated with the decision service (that is, not specific to a particular rule domain).

  • Suspend—A global operation, Suspend sets a flag to indicate that the service shouldn't respond to any inbound queries. Suspend would typically be called prior to the Load operation so that erroneous results don't occur while the working memory is in the process of being loaded.

  • Resume—The opposite of Suspend, Resume simply changes a status flag to indicate that the decision service can again receive inbound requests.

You may be wondering how the Decision operation can work if the intention is to support multiple rule domains (our case study is an example of a specific rule domain, whereas something like claims processing would be a different domain). Using XML Schema's extension mechanism, an inbound request for the pricing estimator case study would resemble that shown in listing 12.9.

Example 12.9. Example SOAP request for pricing engine calculation

<!-- soap envelop not shown for brevity -->
<urn:DecisionRequest
  xsi:type="so:PriceCalculatorRequest"
  xmlns:so="urn:opensoa.drools.salesorder">
  <Order xmlns="urn:opensoa.drools.salesorder">
    <header>
      <orderId>2020322</orderId>
      <partyId>WA-23923</partyId>
      <partyContactId>1006</partyContactId>
      <currency>USD</currency>
      <shipping>
        <carrier>USPS</carrier>
        <method>STANDARD</method>
      </shipping>
     </header>
    <lines>
     <!-- line items would go here -->
    </lines>
  </Order>
</urn:DecisionRequest>

In listing 12.9, I've highlighted the DecisionRequest element. Notice how it contains the @xsi:type attribute, whose value is set to so:PriceCalculatorRequest. If you look at the schema associated with this object in the sample code for this chapter (the parent WSDL is called DroolsService.wsdl, and it includes Order.wsdl, which is where this element is defined), you'll see this definition:

<xs:complexType name="PriceCalculatorResponse">
  <xs:complexContent>
     <xs:extension base="drools:DecisionResponseType">
        <xs:sequence>
           <xs:element name="orderId" type="xs:string"/>
           <xs:element name="salePrice"  type="xs:float"/>
           <xs:element name="shippingPrice" type="xs:float"/>
           <xs:element name="totalPrice" type="xs:float"/>
           <xs:element name="comments" type="xs:string" minOccurs="0"/>
           <xs:element name="currency" type="xs:string"/>
        </xs:sequence>
     </xs:extension>
  </xs:complexContent>
</xs:complexType>

The extension base drools:DecisionResponseType is the element type defined for DecisionRequest. The upshot of this approach is that all decision operations will use that same element, but what will differ for each domain area is the corresponding @xsi:type value and subsequent child elements. What's the alternative to this approach? You'd end up creating a multitude of domain-specific operations such as getPricingEstimate, which seems less than desirable. Granted, for each decision domain or package supported, custom code will be required on the backend, as you'll see in a moment.

The WSDL consists of a high-level parent WSDL, which uses the wsdl:import mechanism to load the domain-specific schema elements. In this fashion, the only change required in the parent WSDL for each new domain that's added is to include a wsdl:import statement like this:

<wsdl:import namespace="urn:opensoa.drools.chapter12"
      location="Order.wsdl"/>

The end result for the decision service is a WSDL that looks like the one shown in figure 12.15.

As figure 12.15 shows, separate services such as LoadService and ResumeService are defined, as opposed to grouping all of the operations under a single service. This is done to simplify the components used by Tuscany's SCA implementation, which we'll explore next.

TUSCANY SCA COMPONENT OVERVIEW

To keep our example as straightforward as possible, we'll only define a SOAP over HTTP binding for the decision service.

Note

We cover adding bindings, such as for JMS, in chapter 3.

The SCA implementation consists of only two components: one (DroolsDecisionComponent) for handling the inbound SOAP requests, and the other (SessionManager) for handling the Drools and JBoss Cache sessions (for efficiency reasons, new working memory isn't instantiated for each inbound request but is persisted across the life of the service or until reloaded using the Load operation). A single SCA service called DroolsDecisionService is used, and it includes the four web service bindings that correspond to the services shown in the WSDL in figure 12.15. The SCA service and two components can be viewed graphically when using the SOA Tools plug-in available for Eclipse (http://www.eclipse.org/stp/). Figure 12.16 shows one of the available views, in this case a tree-style depiction of the SCA service, components, and properties that comprise the decision service.

Figure 12.15. Graphical depiction of the decision service WSDL<br></br> 

Figure 12.16 shows the various property values that are passed in declaratively through the composite XML file (Drools.composite). You'll learn what these are in the next section, but suffice it to say that they identify for the SessionManagement component what rule packages to use and the JBoss Cache configuration details.

Figure 12.16. Tree depiction of the decision service using SOA Tools Eclipse plug-in<br></br> 

Now that we have a WSDL in place and a high-level definition of the SCA configuration, we can turn to the fun stuff: code!

Implementing the decision service using Tuscany and Drools

Since we developed the WSDL first as part of our top-down design philosophy, we can use it to generate the Java classes that represent the inputs and outputs for the web service operations. Two schemas are used in the WSDL. The first, defined in the main WSDL file DroolsService.wsdl, uses a namespace of urn:opensoa.drools and is used for defining the various operation parameters. The second, defined in the Orders.wsdl imported file, uses the namespace of urn:opensoa.drools.salesorder. Since we have two schemas located in different files, we'll call the Tuscany tool XSD2JavaGenerator on each respective WSDL file. This will generate Java classes for each of the elements and attributes present in the schemas. Calling this tool is easiest from within Ant; listing 12.10 shows an Ant target configured to generate the classes based on the WSDL files.

Example 12.10. Ant target used to generate Java classes from WSDL

The Ant target uses a Java task to run the XSD2JavaGenerator

LORD OPERATION

The reason we tackled this operation first is because it invokes the same methods that are used at startup for the decision service. When the service starts, the working memory must be populated in order for the rules engine to function. Additionally, we want to keep the working memory session active, so that it doesn't have to be reconstituted for every request. For these reasons, we created the SessionManagerBRMSImpl class, which implements SessionManager and uses the conversational capabilities of SCA to keep this class stateful for the duration of the decision service (see chapter 4). This class receives, via SCA properties, three property values:

  • rulePropertyFile—The Drools Rule Agent mechanism relies on a property file to indicate how to load a rule package. For an example, see the rule.properties file in the source code for this chapter. You can either load the rule package directly from the Guvnor repository using the url property, or download the .pkg file locally and reference it via the file property. In either case, you can specify multiple packages to load by comma-delimiting the location of each package.

  • pojoServiceConfig—The JBoss Cache version we're using is referred to as POJO Cache (it provides some additional functionality above-and-beyond regular JBoss Cache), and it uses an XML configuration file to specify network connectivity settings. For more information on the various setting options, see [POJOCache].

  • pojoAOPConfig—This property specifies another configuration XML file required by POJO Cache and the specification for its contents are described in the official documentation. In all likelihood, no changes will be needed to this file, and the out-of-the-box configuration will suffice.

The final result is this component SCA definition:

<component name="SessionManager">
   <implementation.java
      class="opensoa.drools.service.impl.SessionManagerBRMSImpl"/>
   <property name="rulePropertyFile">/rule.properties</property>
   <property name="pojoServiceConfig">replSync-service.xml</property>
   <property name="pojoAOPConfig">pojocache-aop.xml</property>
</component>

With these properties now available to SessionManagerBRMSImpl, it can proceed to (a) start a new Drools working session instance using the package(s) specified in the rulePropertyFile, and (b) populate the working memory with the objects seeded into the cache.

Loading the rule package involves only a few lines of code, as this method from SessionManagerBRMSImpl illustrates:

private static RuleBase loadRuleBaseFromRuleAgent(String rulePropertyFile) {
  RuleAgent agent = RuleAgent.newRuleAgent( rulePropertyFile );
  RuleBase rulebase = agent.getRuleBase();
  return rulebase;
}

The returned RuleBase can then be used to create the working memory session using its method newStatefulSession(). With the handle to the working memory, the objects in the cache can be loaded. The method used to do so is displayed in listing 12.11.

Example 12.11. Method to populate Drools working memory

The first step in loading the objects is to create an instance of the cache. This is done with the PojoCacheFactory.createCache() method

The Load operation's SOAP request is very terse:

<soapenv:Envelope> <!-- namespaces not shown -->
  <soapenv:Body>
    <urn:ManageService/>
  </soapenv:Body>
</soapenv:Envelope>

You might be wondering how the service knows that this call pertains to the Load operation. As you may remember, separate WSDL services were configured for each operation (shown in figure 12.15), so the actual URL that this is posting to is what reveals the operation: http://localhost:8085/LoadService.

The component responsible for fielding the incoming SOAP requests is implemented by way of the class PriceCalculatorResponseImpl. It includes a method that corresponds to the SOAP operation signature, as shown in listing 12.12.

Example 12.12. Load method used for responding to inbound SOAP requests

First we set the SessionManager.status (implemented by SessionManagerBRMSImpl to false

Note

You may be wondering why the method name, Load, doesn't follow the standard Java method convention of starting with a lowercase letter. This is because it's patterned to match the operation name in the WSDL. (We could change the WSDL, but SOAP naming conventions are often different than Java.) The parameter object and return value are both generated by XSD2JavaGenerator.

Derived from the WSDL code generation, ManageResponseTypeImpl represents the return value that's expected. We populate it with a response that represents what will be returned from the SOAP call

Whew, that's a lot of stuff we covered. Fortunately, since we've discussed the SessionManager implementation, the remaining operations will be simple in comparison. Let's look at how the main operation, Decision, is implemented.

DECISION OPERATION

The Decision operation, unlike the other three, is specific to the rule domain or package being called. For example, in our case study we're just dealing with the rules concerning the calculation of an order's price (listing 12.9 depicted an example inbound SOAP request). Thus, the implementation we'll show is unique to this domain, and an implementation must be provided for each rule domain you're incorporating into the decision service. This becomes clearer as we look at the method used for processing the Decision operation for the pricing engine calculator:

public PriceCalculatorResponseImpl Decision(PriceCalculatorRequestImpl
  order) throws Exception {

  if (sessionManager.isStatus() == false) {
     throw (new Exception("Service unavailable"));
  }

  PriceCalculatorProcessor processor =
    new PriceCalculatorProcessor(order);

  return processor.process(sessionManager);
}

The method signature for this Decision operation, which receives a PriceCalculatorRequestImpl object, clearly illustrates that it's specific to the pricing calculator. You'd create additional Decision methods with their own unique signatures for each rule domain you set up. In the Decision method, you see that we're checking the status, and if it's set to false, we return an error to the client. Such a scenario occurs when the Load operation is called to refresh the working memory, or when the service has been suspended using the Suspend operation. The heart of the processing occurs in the PriceCalculatorProcessor class. This class, which we'll show next, is responsible for invoking the rule engine by its process() method and for preparing a response of its findings.

The PriceCalculatorProcessor.process() method is shown in listing 12.13.

Example 12.13. Method responsible for processing the pricing engine domain rules

The first thing we do in the process() method is receive a handle to the Drools working memory session

The runRules() method is where the real work resides, as shown in listing 12.14.

Example 12.14. Method that invokes the rule engine and prepares a response

Using the workingMemory class variable, which represents the Drools session, we insert the object representing the SOAP request into the working memory

Since we've covered a lot here, let's recap in figure 12.17 the overall process for fulfilling an inbound Decision operation request.

Figure 12.17. Process for fulfilling an inbound Decision operation request<br></br> 

When introducing a new rule domain, the only change necessary is to create a new process type class similar to PriceCalculatorProcessor, create a new method signature in DroolsManagerImpl, and then call the processor from within the new method. Using Spring, a solution could be wired together more elegantly still, but we didn't want to further complicate our example by introducing another technology (while many of you are familiar with Spring, some may not be).

SUSPEND AND RESUME OPERATION

The Suspend and Resume operations are intended for circumstances where you want to temporarily suspend (and eventually resume) the decision service, but don't necessarily just want to turn it off entirely, which would result in network timeout errors. The only thing these operations do is change the stateful SessionManager.status Boolean flag, since that's checked before the service will process any decision request. For example, here's the Suspend method's implementation:

public ManageResponseTypeImpl Suspend(ManageServiceTypeImpl service) throws
  RemoteException, Exception {
  sessionManager.setStatus(false);
  scope = SDOUtil.createHelperContext();;
  dfactory = DroolsFactory.INSTANCE;
  DroolsFactory.INSTANCE.register(scope);
  ManageResponseTypeImpl response =
    (ManageResponseTypeImpl) dfactory.createManageResponseType();
  response.setResultCode(200);
  response.setResult("SUCCESS");

  return response;

}

As you can see, we haven't introduced anything new, so let's turn to how we can test our new decision service.

Testing

Most Java developers are already familiar with unit testing tools such as JUnit or TestNG. These are essential tools in any Java developer's toolbox. However, one of the main drivers behind adopting a BRMS is so that business users can author their own rules, tapping into the considerable business expertise they posses. Such users are obviously not skilled enough to perform unit testing using conventional Java testing tools. In recognition of this, Guvnor is being enhanced to support a testing framework, but it remains a work in progress as of this writing. Instead, I suggest deploying soapUI for testing by rule authors and subject matter experts. Figure 12.18 shows soapUI used for testing our case study's pricing engine rule service.

As figure 12.18 shows, a variety of authoring views are available. The one shown in the figure is the Outline view, but a Form view is also available that derives a form based on the WSDL. Similarly, the response can be viewed in a variety of fashions, from Raw XML to the Overview layout shown in figure 12.18. The capabilities of soapUI extend far beyond what is shown, and it can be used to create comprehensive testing suites that can be run for regression-style automated testing. Used in tandem with Guvnor, a true end-to-end solution can be created that includes authoring, rule management, and testing.

Figure 12.18. soapUI is used for testing the decision service<br></br> 

The decision service we've created using the pricing estimator case study has demonstrated the powerful combination of using Apache Tuscany and JBoss Drools. You've seen how Apache Synapse can play a central role in a real-life environment for harvesting the data necessary for the rule engine to perform its inference logic. You can imagine a scenario where the Esper ESP could be beneficial, in particular as a way to optimize decision yield and spot abnormal or unusual trends.

Summary

The purpose of this chapter was to build on what you learned in chapter 11, where we introduced the Drools rule engine and many of its essential capabilities. In this chapter, we constructed a real-life type case study that consisted of 15 rules with activation sequencing managed via the Drools RuleFlow capability. We then migrated these rules into Guvnor, the Drools BRMS, and explored many of its features. Most significant is the ability Guvnor provides for business users to author and manage business rules, which impact their area of domain expertise. In providing this capability, rules truly become unlocked from their historical binding deep within an application's code and instead become business assets that can be managed and shared across multiple applications or business processes.

We concluded the chapter by describing how a decision service can be created using Apache Tuscany that exposes these rules as web services that can be easily consumed by a multitude of client applications. Thorny issues that have plagued the adoption of decision services, such as how to populate the facts required for the service to perform its logic, were addressed using a distributed cache technology built on JBoss Cache. A framework resulted that can be extended and built on within your enterprise.

Drools is one of the exciting technologies we covered in the Open SOA Platform, and its adoption promises a new paradigm—a business rules approach that reduces development costs and dramatically improves organizational agility!