Organizing our projects

The more complex our rules become, the more important it is to have tests for them and keep them organized as much as possible. In this section, we will discuss how we organized our example projects for keeping our rules, their tests, and related classes in a structure that can be easily maintained.

We recommend this way of structuring the projects so that each of them keeps a very well-defined scope, set of dependencies, and they can be tested independently. You should keep all the application infrastructural code (user interfaces, system integration, services, and so on) in separate Maven modules as well, therefore, the infrastructure can be maintained in separate cycles from the business knowledge that tends to be updated more frequently by the business needs.

The example repository contains a high-level parent project that is composed by each individual chapter modules. Each individual chapter contains the following three main modules:

  • -kjar: This will contain our business assets such as rules, business processes, and so on
  • -tests: We will include all the tests here
  • -extras (optional): This module usually contains classes to extend the Drools and jBPM functionality, such as custom evaluators for rules, work item handlers for business processes, and so on

The -kjar and -tests modules are considered to be knowledge projects as they contain the rules, definitions, and tests to ensure that the defined knowledge is behaving correctly. As you can see in the following image, the *-test project will depend on the domain model project from your application. It might also depend on the service layer to execute operations; however, as good practice, these services can be mocked. From the application perspective, it is most likely that the Services and User Interfaces modules end up having dependencies to knowledge-related projects. If the knowledge-related projects are only defining the core business logic, the services from your application will end up using them in order to make decisions internally. From the user interface's perspective, we can also define knowledge projects to assist the user at the UI level, as shown in the following diagram:

Organizing our projects

At this point, don't worry about this structure, it will become more clear when we move forward on the next chapters. Now, if you take a look at the chapter-02-test/ project, you will find that we need to define some extra dependencies for the testing frameworks that we will be using, as follows:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest-library</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <scope>test</scope>
</dependency>

After adding these dependencies to our projects, we can create our first JUnit test to make sure that our rules are working as expected. Notice that we have defined several rules in different rule files throughout this chapter, and for now, we are going to load all of them in the same KieSession. This means that when we insert information in the engine, all the rules will be evaluated and probably fired.

The first test that we will look into is the one in the ClassifyItemsTest.java class and it is located in the src/tests/java/org/drools/devguide/chapter-02 directory in the chapter-02/chapter-02-test project, as follows:

public class ClassifyItemsTest extends BaseTest {
    @Test
    public void simpleClassification() {
        KieSession kSession = createDefaultSession();
        Item item = new Item("A", 123.0, 234.0);
        kSession.insert(item);
        int fired = kSession.fireAllRules();
        assertThat(1, is(fired));
        assertThat(Category.LOW_RANGE, is(item.getCategory()));
    }
}

This test shows how to make sure that our item is categorized correctly. We encourage you to open the classify-item-rules.drl file, look at the other rules, and write tests to make sure that other categories are also being processed correctly.

When we compile this project using Maven (clean install), these tests will be executed, assuring us that our items are classified accordingly. If we use a continuous build system (such as Jenkins CI at https://jenkins-ci.org), we will get notified as soon as a rule changes and one of these tests breaks. Allowing us to review whether the test needs to be changed or the change introduced in the rules is breaking some other policies that are already defined in the system.

Another test class called OrderDiscountTest.java, is located in the same package as the previous one. Lets see the code for this as follows:

@Test
public void highRangeOrderDiscountTest() {
  KieSession kSession = createDefaultSession();
  Order o = ModelFactory.getOrderWithFiveHighRangeItems();
        
  kSession.insert(o.getCustomer());
  kSession.insert(o.getOrderLines().get(0));  
  kSession.insert(o.getOrderLines().get(1));
  kSession.insert(o.getOrderLines().get(2));
  kSession.insert(o.getOrderLines().get(3));
  kSession.insert(o.getOrderLines().get(4));
  kSession.insert(o.getOrderLines().get(0).getItem());
  kSession.insert(o.getOrderLines().get(1).getItem());
  kSession.insert(o.getOrderLines().get(2).getItem());
  kSession.insert(o.getOrderLines().get(3).getItem());
  kSession.insert(o.getOrderLines().get(4).getItem());
  kSession.insert(o);
        
  int fired = kSession.fireAllRules();

  // We have 5 Items that are categorized -> 5 rules were fired
  // We have 1 Customer that needs to be categorized -> 1 rule fired
  // We have just one order with all HIGH RAnge items -> 1 rule fired
  // One Coupon is created for the SILVER Customer -> 1 rule fired
  assertThat(8, is(fired));
  assertThat(o.getCustomer().getCategory(), is(Category.SILVER));
  assertThat(o.getDiscount(), not(nullValue()));
  assertThat(o.getDiscount().getPercentage(), is(10.0));
  assertThat(o.getOrderLines().get(0).getItem().getCategory(), is(Item.Category.HIGH_RANGE));
  ..
        
  // The Coupon Object was created by the Rule Engine so we need to get it from the KieSession
  Collection<Coupon> coupons = getFactsFromSession(kSession, Coupon.class);
  assertThat(1, is(coupons.size()));

}

A common requirement for these tests is to consume our domain models, which can contain complex structures and a lot of data. Usually, we end up having helpers to retrieve these information from a data store such as a database or we can create local instances for the sake of the tests.

For these tests, we are using the ModelFactory helper, which initialize different orders, customers, and items for us. Feel free to review the different methods of this factory to understand the information that is being initialized there. We encourage you to create more methods in the ModelFactory class to provide more data for your tests.

This test loads all the rules (in no particular order) that we defined so far in the following DRL files:

  • classify-customer-rules.drl
  • classify-item-rules.drl
  • coupons-creation.drl
  • order-discount-rules.drl

After KieSession is created by the helper method createDefaultSession(), ModelFactory provides us with an order that gives us access to OrderLines, Customer, and Items. In order to enable the Rule Engine to work and match these objects with the previously defined rules, we need to insert each of them using the insert() method. As you may have noticed, this could become a problem quite easily if we need to insert all the objects separately. There are a couple of alternatives to solve this issue and we will cover them in the following chapters. For now, we need to know if we have rules that filter facts by type. We need to make these facts available to the Engine.

Now, after inserting all the facts to KieSession, the evaluations are done by the engine and matches are created. It is important to understand that not all matches need to be executed on the fireAllRules() call, some of them might be cancelled and new matches can also be created during execution. We will analyze a detailed rules-execution flow in Chapter 3, Drools Runtime. For now, we need to understand why we have eight rules fired in the test and in order to do this, we need to do some simple math.

We know that one rule will be fired per item that needs to be categorized. In this test, we have five Items, therefore, we have five rules fired there. We don't know the order in which these rules will be fired; however, we know that they will be fired before a rule that depends on this categorization. The same happens with the customer, therefore, one more rule fired. Again, we don't know or care about the order in which the items and customers are categorized. Finally, one rule is fired for the Coupon creation and one rule is fired to apply the discount to the order. As we mentioned earlier, the firing order is not important, the results are, and for this reason, the test checks whether the objects have being changed as expected. At the end of the test, we cannot check the Coupon object as we didn't have any reference from it. The Coupon object was created by the Rule Engine internally, and for us to get hold of it, we can use the getFactsFromSession (kSession, Coupon.class) helper method, which will get all the Coupon objects that were inserted as facts in KieSession.

If you had written more rules about coupons, customers, orders, and so on, these tests might fail. If you did so, make sure to update the tests so that they keep passing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.186.219