Using Spring 4 WebSocket, sockJS and Stomp support to implement two way server client communication

One exciting new feature of Spring 4 is the support for WebSocket, SockJS and STOMP messaging. This allows two way communication between the server and its clients in a Spring MVC web application using the standard point-to-point and publish-subscribe messaging protocols. In this post, I will demonstrate how to set up a basic boilerplate project to start using this new feature. It is in part based on this article.

Maven Setup

First we need to add the Spring messaging modules in the POM file:

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-messaging</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-websocket</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>

Spring MVC Configuration

Next, we need to add the message broker config to the Spring MVC config XML file.

<beans
 ...
 xmlns:websocket="http://www.springframework.org/schema/websocket"
 xsi:schemaLocation="
 ...
 http://www.springframework.org/schema/websocket
 http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">
<websocket:message-broker application-destination-prefix="/app">
       <websocket:stomp-endpoint path="/hello">
            <websocket:sockjs/>
       </websocket:stomp-endpoint>
       <websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>
<!-- Other MVC config omitted here-->

The main thing here is the set up of the message broker for handling the message exchange between the server and its clients. This is done via the <message-broker> and its child tags. The tag <websocket:simple-broker> indicates we are using in-memory message broker.

It is easy to understand together with the server and client codes so I will include them below first before attempting to give a bit more explanations by cross-referencing the client and server codes.

Spring MVC Controller

Below is my Spring MVC Controller

 @Controller
 public class MessageController {
      @MessageMapping("/hello")
      @SendTo("/topic/greetings")
      public Greeting greeting(HelloMessage message) throws Exception {
           return new Greeting("Hello, " + message.getName() + "!");
     }
}

The method argument HelloMessage and output Greeting are just POJOs representing the body of the messages being sent and returned.

public class Greeting {
    private String content;
    public Greeting(String content) {
           this.content = content;
    }
    public String getContent() {
      return content;
    }
}
public class HelloMessage {
    private String name;
    public String getName() {
        return name;
    }
}

Client sockJS and STOMP codes

On the client side, I use the sockJS protocol fallback option as outlined in the Spring documentation. The javascript codes are included below

// Create stomp client over sockJS protocol (see Note 1)
 var socket = new SockJS("/hello");
 var stompClient = Stomp.over(socket);

 // callback function to be called when stomp client is connected to server (see Note 2)
 var connectCallback = function() {
      alert("connected!");
      stompClient.subscribe('/topic/greetings', function(greeting){
           alert(JSON.parse(greeting.body).content);
      });
 }; 

 // callback function to be called when stomp client could not connect to server (see Note 3)
 var errorCallback = function(error) {
      // display the error's message header:
      alert(error.headers.message);
 };

 // Connect as guest (Note 4)
 stompClient.connect("guest", "guest", connectCallback, errorCallback);

Note

  1. The client starts by create a sockJS by specifying the endpoint (ie. /hello) to connect to and then a stomp client is created over the socket. The endpoint here should match that defined in the Spring MVC configuration in the lines. Note also the 2nd line referring to sockJS.

    <websocket:stomp-endpoint path=”/hello”>
    <websocket:sockjs/>
    </websocket:stomp-endpoint>

  2. Then a callback function is created and assigned to a variable connectCallback. This is called when a successful connection is made by the stomp client. This allows us to start making subscriptions to messages (as in codes, repeated below) and sending messages. Note the subscription is for the topic “/topic/greetings”

    stompClient.subscribe(‘/topic/greetings’, function(greeting){
    alert(JSON.parse(greeting.body).content);
    });

  3. A error callback function is defined if stomp client fails to connect to server.
  4. This line makes the connection registering the callback functions.

Now we are ready to send messages from the client, e.g. using the following javascript function

// function to send message
 function fnSayHi() {
       stompClient.send("/app/hello", {}, JSON.stringify({ 'name': 'Joe' }));
 }

The message will be sent to the Spring MVC message handler method greeting() as defined via the annotation @MessageMapping(“/hello”).

 <websocket:message-broker application-destination-prefix=”/app”>

Note the prefix “/app” is defined in the Spring config as  application-destination-prefix attribute of the message broker: Note also, the use of @SendTo annotation to direct the message to a given destination. I repeat the controller method below

 @MessageMapping("/hello")
 @SendTo("/topic/greetings")
 public Greeting greeting(HelloMessage message) throws Exception {
      return new Greeting("Hello, " + message.getName() + "!");
 }

That’s it for now.

Advertisements

Using JBoss Drools to Implement a E-commerce Promotion Rule Engine – Part 2

In my last post, I outline several common E-commerce promotion rules to be implemented with JBoss Drools Expert. I also describe the domain objects for representing the facts used by the rule engine as well as set up the unit test harness. I will finish off with this post by providing details on how each rule can be defined with the purpose of demonstrating some capabilities of Drools.

Drools Rules

Rule 1: Large order discount

Rule file

// 1. Apply discount if order is over a certain amount
 rule "$50 discount for order over $1000 but less than $2000"
 no-loop
 when
      $order : Order(
           $order.lineTotal >= 1000 &&
           $order.lineTotal < 2000
      )
 then
      modify($order) {
           setOrderDiscountAmount(50);
      }
      System.out.println("Apply $50 discount for order over $1000:");
      System.out.println($order);
 end
rule "200 discount for order over $2000"
 no-loop
 when
      $order : Order(
           $order.lineTotal >= 2000
      )
 then
      modify($order) {
           setOrderDiscountAmount(200);
      }
      System.out.println("Apply $200 discount for order over $2000:");
      System.out.println($order);
 end

Note

  1. 2 rules are defined here. The first one applies when the order total (lineTotal) is between $1000 and $2000. The second rule applies for any order over $2000.
  2. The modify keyword in the action (then) clause tells the rule engine that the rule modifies the fact, in this case the order. This would have triggered the same  rule again. Drools use the rule attribute no-loop to stop this from happening

Unit Test

@Test
 public void testLargeOrderDiscount_over1000() {
      // Add facts to working memory
      Order order = new Order();
      order.addOrderLine(new OrderLine("SKU 123", 1, 1200));
      ksession.insert(order);
      assertEquals(1, ksession.fireAllRules());
 }
Output:
Apply $50 discount for order over $1000:
 Order total(discount): 1200.0(50.0)
 [SKU 123] x1: 1200.0(-0.0) = 1200.0

Rule 2: Clearance products – 10% of list of defined products

Rule File

// 2. Clearance products
 rule "Clearance products"
 no-loop
 when
      $order : Order()
      $item : OrderLine() from $order.lines
      ClearanceProductList(skus contains $item.getSku())
 then
      System.out.println("Apply discount for clearance product " + $item.getSku() + ":");
      $item.setLineDiscountAmount(10);
      System.out.println($order);
 end

Note

  1. Note the use of keyword from in the when clause to iterate list of order items from the order
  2. Each item found in (1) is set in variable $item, which is then used to check in the next line ClearanceProductList(…) whether the line item is one of the discounted product list skus by using the contains keyword.
  3. Discount is applied to the item in the then clause  $item.setLineDiscountAmount(10);

Unit Test

@Test
 public void testClearanceProductDiscount() {
     // Add facts to working memory
     Order order = new Order();
     order.addOrderLine(new OrderLine("SKU 123", 1, 100));
     order.addOrderLine(new OrderLine("SKU 456", 1, 200));
     ClearanceProductList productList = new ClearanceProductList();
     productList.add("SKU 123");
     productList.add("SKU ABC");
     ksession.insert(order);
     ksession.insert(productList);
     assertEquals(2, ksession.fireAllRules());
}
Output:
Apply discount for clearance product SKU 123:
 Order total(discount): 290.0(0.0)
 [SKU 123] x1: 100.0(-10.0) = 90.0
 [SKU 456] x1: 200.0(-0.0) = 200.0

Rule 3: Time based sales – 10% off from a list of defined products within a certain date range, e.g. Christmas sales between 1/12 to 31/12.

Rule File

// 3. Time based sales
rule "Time based sales"
no-loop
 when
      $order : Order()
      $item : OrderLine() from $order.lines
      $sales : TimeBasedSales(
           fromDate < LocalDate.now().toDate(),
           toDate > LocalDate.now().toDate(),
           products contains $item.getSku()
     )
 then
      $item.setLineDiscountAmount($item.getUnitPrice() * $item.getQty() * 0.1);
      System.out.println("Apply time based discount for product " + $item.getSku() + ":");
      System.out.println($order);
end

Note:

  1. The date comparisons for fromDate and toDate in the last lines of the when clause.

Unit Test

@Test
 public void testTimeBasedSales() {
      // Add facts to working memory
      Order order = new Order();
      order.addOrderLine(new OrderLine("SKU 123", 1, 100));
      order.addOrderLine(new OrderLine("SKU 456", 1, 200));
      TimeBasedSales sales = new TimeBasedSales(
      LocalDate.now().minusDays(5).toDate(),
      LocalDate.now().plusDays(5).toDate());
      sales.addProduct("SKU 456");

      ksession.insert(order);
      ksession.insert(sales); 
      assertEquals(2, ksession.fireAllRules()); 
 }
Output:
Apply time based discount for product SKU 456:
Order total(discount): 280.0(0.0)
[SKU 123] x1: 100.0(-0.0) = 100.0
[SKU 456] x1: 200.0(-20.0) = 180.0

Note:

  1. I setup the TimeBasedSales object based on current date. Drools supports concept of pseudo system clock which allows you to test time related rules independent of current real time. See here for details.

Rule 4: Special Tuesday – everything 5% off on Tuesday

Rule File

// 4. 5% discount everything Tuesday
rule "5% discount everything tuesday"
calendars "tuesdays"
 when
      $order : Order()
 then
      System.out.println("Apply special Tuesday discount:");
      $order.setOrderDiscountAmount(0.05 * $order.getLineTotal());
System.out.println($order);
end

Note

  1. This rule makes use of attribute calendars to define the pre-condition that the rule will be considered. The value “tuesdays” correspond to the calendar of same name defined in the knowledge base session. Below is the code in the unit test. Note the use of Quartz to define days of week excluded.
private void getTuesdayCalendar() {
      //in this case I'm using a DailyCalendar but you can use whatever implementation of Calendar you want
       org.quartz.impl.calendar.WeeklyCalendar tuesdays = new org.quartz.impl.calendar.WeeklyCalendar();
       tuesdays.setDaysExcluded(new boolean[] {true, true, true, false, true, true, true, true}); // index 1 = Sunday, etc.
      //convert the calendar into a org.drools.time.Calendar
      org.drools.time.Calendar tuesdayCalendar = QuartzHelper.quartzCalendarAdapter(tuesdays);

      //Register the calendar in the session with a name. You must use this name in your rules.
      ksession.getCalendars().set( "tuesdays", tuesdayCalendar);
}

Unit Test

@Test
 public void testSpecialTuesdaysApplied() {
      // Move to next Tuesday
      advanceToDayOfWeek(2);
      // Add facts to working memory
      Order order = new Order();
      order.addOrderLine(new OrderLine("SKU 123", 1, 100));
      order.addOrderLine(new OrderLine("SKU 456", 1, 200));
      ksession.insert(order);
      assertEquals(2, ksession.fireAllRules());
 }

Output:
Apply special Tuesday discount:
Order total(discount): 300.0(15.0)
[SKU 123] x1: 100.0(-0.0) = 100.0
[SKU 456] x1: 200.0(-0.0) = 200.0

Note

  1. I use the psuedo system clock and method advanceToDayOfWeek(2) to move the clock to next Tuesday to trigger the rule.

Rules Interactions

In a real world commerce environment, a promotion engine will have a considerably bigger number of business rules. Importantly, those rules will be interdependent to each other. Typically, only one rule out of a number of different rules can apply. Alternatively, there is a specific order in which certain rules should apply. Rules can potentially be conflicting. This is where a rule engine like Drools comes in handy as it provides a comprehensive set of features to support these and other common scenarios.

salience

This rule attribute is used to define the priority in which a rule is fired in relation to other rules. For example, a large value discount (Rule 1) should only be applied after all the other line item discounts are applied.

activation-group

This rule attribute is used to group a number of related rules so that when one rule in the group fires, the other rules become inactive. For example, if a product is in clearance category, only the clearance discount is applied.

Final Thoughts

I hope this and last posts demonstrate how common ecommerce problem such as promotion can be solved using a business rule engine such as Drools. Obviously, the business rules will be more complex in the real world but this article should illustrate by example the benefits I listed in Part 1, which I explain in more details below:

  1. Declarative Programming – business rules are defined declaratively in pseudo human readable format (when … then) which promotes common understanding between business and developers.
  2. Separation of Data and Logic – all the logics are defined as rules. This is a significant advantage as it scales much better than having to implement the logics in codes. Also, business rules change often in real world and it is advantageous here as the rules can be modified without need of code changes.
  3. Centralization of Knowledge – the set of rules defined declaratively become the knowledge base of the system (and the business), which will faciliate its update and maintainence.
  4. Explanation of outcomes or actions – the rule engine can answer the why-questions as it knows what rules are fired based on what facts. This is considerably more difficult to implement without the Inference Engine implemented by the rule engine.

There are other ecommerce problems that could benefit from Drools. For example, the determination of product pricing based on order, customer, product costs, etc.

As for Drools, I only touch on the basic features. JBoss has good documentation which can be found here. A few features worth exploring include

  1. Decision table – instead of defining rules in drl files as shown here, drools also support writing rules in MS-Excel spreadsheets
  2. Rule template – instead of defining individual rules, one can define a template with variables and then create the individual rules by filling in the values of the variables from data in database or MS-Excel files.
  3. Event processing in the API

That’s all for now.

Using JBoss Drools to Implement a E-commerce Promotion Rule Engine – Part I

In this and the following articles, I will demonstrate how to implement a basic promotion engine using JBoss Drools framework. The objective here is not to provide a complete implementation but to show how Drools and business rule engine in general can be used to solve common ecommerce problems. Also to demonstrate some of the features provided by Drools Rule Engine.

Introduction

From a software system prospective, a business rule engine allows business to define their business rules declaratively. The system can then, via its inference engine, to match the rules defined with the “facts” it observes at run time. Some of the major advantages of using a rule engine are:

  1. Declarative Programming
  2. Separation of Data and Logic
  3. Centralization of Knowledge
  4. Explanation of outcomes or actions

A more comprehensive list of advantages and more detailed explanation can be found here.

E-commerce Promotion Rules

I define the following business rules for a ecommerce or brick-and-mortar store to decide when and what promotional discount(s) should  be applied for an order. They are fictitious rules that should be commonly understandable.

  1. Large order discount
    1. 5% discount for order total over $1000 and less than $2000
    2. 10% discount for order total over $2000
  2. Clearance products – 10% off from a list of defined products
  3. Time based sales – 10% off from a list of defined products within a certain date range, e.g. Christmas sales between 1/12 to 31/12.
  4. Special Tuesday – everything 5% off on Tuesday

Domain Objects (Facts)

The first step to do is to define the business domain objects which will act as the facts to be processed by the rule engine. Our domain consists of the following classes:

  • Order – represents a single sales order
  • OrderLine – represents an item in an order
  • ClearanceProductList – represents a list of discounted products
  • TimeBasedSales – represented a list of products to apply discount to when date of order falls within the defined date range.

A discount can be applied to each item (i.e. OrderLine) of an order. On top of that, an order discount can then be applied on the order total.

I include the code snippets below:

Order.java

public class Order {

     private double orderDiscountAmount; // order discount amount

     private List<OrderLine> lines = new ArrayList<OrderLine>();

 

     // calculate discounted total amount of all the items in this order
     public double getLineTotal() {
             double lineTotal = 0;
             for (OrderLine line : lines) {
                     lineTotal += line.getLineAmount();
              }
             return lineTotal;
     }

    // getter and setter methods below omitted here

OrderLine.java

public class OrderLine {

 

         private String sku;
         private int qty; // quantity bought
         private double unitPrice; // price for each unit
        private double lineDiscountAmount; // discount applied to this item

 

        public double getLineAmount() {
                 return unitPrice * qty – lineDiscountAmount;
        }

       // getter and setter methods below omitted here

ClearanceProductList

public class ClearanceProductList {

        private List<String> skus = new ArrayList<String>();

 

       // getter and setter methods below omitted here

TimeBasedSales

public class TimeBasedSales {

         private Date fromDate;
         private Date toDate;
         private List<String> products = new ArrayList<String>();

 

       // getter and setter methods below omitted here

 

JUnit Test class

Include below is the unit test class snippets that I will use to run various facts (i.e. orders) against the rule engine. It also demonstrates how to set up Drools.

public class BlogRuleTest {

     private Logger logger = Logger.getLogger(getClass());
     private StatefulKnowledgeSession ksession;

     @Before
      public void setUp() {
          KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
          knowledgeBuilder.add (

                     ResourceFactory.newClassPathResource(“com/drools/blog/blog.drl”,  getClass()),                                                  ResourceType.DRL);

            // verify rule file has no errors

           if (knowledgeBuilder.hasErrors()) {
                            Iterator<KnowledgeBuilderError> iterator = knowledgeBuilder.getErrors().iterator();
                            while (iterator.hasNext()) {
                                         logger.error(iterator.next().getMessage());
                             }
                            fail(“Rule file has error”);
            }

            KnowledgeSessionConfiguration config =                                                              KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
            config.setOption( ClockTypeOption.get(“pseudo”) );

            KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
            kbase.addKnowledgePackages(knowledgeBuilder.getKnowledgePackages());

             ksession = kbase.newStatefulKnowledgeSession(config, null);

             // Default to a non-Tuesday
            advanceToDayOfWeek(1);

            getTuesdayCalendar();
 }

 

@After
public void after() {
       ksession.dispose();
}

Note:

  1. The rule file blog.drl is expected to be in folder com/drools/blog/blog.drl under the classpath, e.g. under /src/main/resources.
  2. I create stateful knowledge session above. This is not really required for the task at hand. See Drools documentation for an explanation of the difference between stateless and stateful knowledge sessions.
  3. SessionPseudoClock is used here to allow us to test time based rule. More on this later when we define the “Special Tuesday” rule
  4. The session is disposed after each unit test is run in the after() method.

On to Drools Rules

OK. We are ready to implement the Drools rules. Let do this in the next post.