Friday Aug 16, 2013

The Perfect Marriage: Oracle Business Rules & Coherence In-Memory Data Grid. High Scalable Business Rules with Extreme Low Latency

The idea of separating business rules from the application logic is by far an old concept. But in the last ten years, what we have seem is that dozen of platforms and technologies has been created to allow this separation of concerns. One of those technologies is BRMS, acronym of Business Rules Management System. The basic idea of one BRMS is to be a repository of rules, governing those rules in such way that they can be created, updated, tested and controlled by an external interface. Part of the BRMS responsibility it is also provide an API (more than one when possible) that allows external applications to interact with the BRMS, allowing those applications to send data over the network, and that data can trigger the execution of zero, one or multiples rules in the BRMS repository. This rule execution occurs outside of those external applications, minimizing their process memory footprint and generating much less CPU overhead since the execution processing of the rules happens in a separated server/cluster. This architecture approach is very powerful, allowing:

  • Rules can be managed (created, updated) outside of the application code
  • Rules can be reused across different applications, no matter their technology
  • Less CPU overhead and smaller memory footprint in the applications
  • More control over rules, auditing of changes and enterprise log history
  • Integration with other IT artifacts like dictionaries, processes, services

With this context in place, we are all agree that the usage of one BRMS is a mandatory approach on every IT architecture due its power, if it were not for the fact that BRMS technologies introduces a lot of overhead in the overall transaction latency. In the middle of the external application that invokes the BRMS to execute rules and the BRMS platform itself, there is the network channel. This means that we must deal with network I/O and their technical implications (serialization, instability, buffering bytes approach) when we send/receive data to/from the BRMS. No matter if the BRMS provides an SOAP API, an REST API or any other TCP/IP based API, the overall transaction latency is compromised by the network overhead.

Another huge problem of BRMS platforms is scalability. When the BRMS platform is first introduced to an architecture, it handles an acceptable number of TPS (Transactions Per Second), which nowadays varies from 1K TPS to 5K TPS. But when other applications starts using the same BRMS platform, or the number of transactions just naturally grows, you can face scenarios when your BRMS platform must deal with 20K TPS or even 100K TPS. What happens when a huge numbers of objects are allocated in the heap space of the Java based server? The memory footprint starts to reach its maximum size and the garbage collector starts to run to reclaim the unused memory and/or redesign the layout space. No matter what job the garbage collector has to do, it will use the entire processing power to runs its job as soon as possible, since the amount of garbage to handle will be huge. This is true for the almost BRMS platforms of the market, no matter if its from one vendor or another. If the BRMS platform are Java based, when those servers JVM reach more than 16 GB of space in average, they starts to face a huge performance problem due garbage collection.

Differently from other architecture designs in which the load is distributed across a cluster, BRMS platforms must handle the entire processing in a single server due a general concept of BRMS platforms known as execution agenda and working memory. All the facts (the data sent as input) are maintained in this agenda in a single server, making the BRMS platform a pinned service, in which they do their job in a singleton fashion. In this situation, when you need to scale, you can introduce series of equally servers, below a corporate load-balancer that instead of distribute load, it divides entire transaction volumes across those servers. Because each server below the load-balancer handle the entire volume by itself, those servers limit concurrency by the number of processors available in their mainboard. If you need more compute power, due lack of concurrency, you are forced to buy a much higher server. Those servers are huge, expensive and costs a lot of money since they need to be big enough in terms of processors to handle thousands of executions simultaneously and completely alone. Not a very smart approach when you considering to handle millions of TPS.

With this situation in mind, it is necessary to design an architecture that would allow business rules execution be distributed across different servers. To achieve this behavior, it is necessary to use another software component that could share data (business entities, fact types, data transfer objects) across different processes, running in the same or different hardware boxes. And more important than that, a software component that would allow transaction latency to be short enough, reducing a lot of milliseconds introduced by network overhead. In other words, this software component must bring data to the unique hardware layer that really doesn't implies in I/O overhead, which is memory.

Recently, in order to deal with this problem and provide for a customer an scalable plus high performance way to use Oracle Business Rules, I designed an solution that solves both problems in a once, without losing the power of separation of concerns provided by BRMS platforms. In-Memory Data Grid technologies like Oracle Coherence has the power of handling massive amounts of data (MB, GB or even TB) completely in-memory. Moreover, this kind of technology has been written from scratch to distribute data across a number of servers, so scalability is never a problem here. When you integrate BRMS with In-Memory Data Grid technologies, you can do both of the two worlds: scalability plus high performance and also extreme low latency. And when I say extreme low latency I mean, sub-milliseconds of latency. Something around less than 650 μs in my tests.

This article will show how to integrate Oracle Business Rules with Oracle Coherence. The steps showed here can be reproduced for a huge number of scenarios, making your investment on Oracle Fusion Middleware (Cloud Application Foundation and/or SOA Suite stack) even more attractive.

The Business Scenario: Automatic Promotions for Bank Customers

Before we move to the implementation details of this article, we need to understand the business scenario used as didactic. We are about to simulate an automatic decision system that create promotions for banking customers based on their profiles. The idea here is let the BRMS platform decide which promotions to offer based on customer profiles that applications send it. This automatic promotion system should allow applications like internet banking sites, mobile applications or kiosk terminals, to present promotions (up-selling/cross-selling) to its final customers.

Building the Solution Domain Model

Let's start the development of the example. The first thing to do is the creation of the domain model, which means that we need to design and implement the business entities that will drive the client-side application execution, as such the business rules. The automatic promotion system will be composed of three entities: promotions, products and customers. A promotion it is something that the bank would offer to the customer, with contextual information about the business value of one or more products, derived from the customer profile. Here is the implementation of the promotion entity:

package com.acme.architecture.multichannel.domain;

import com.tangosol.io.pof.annotation.Portable;
import com.tangosol.io.pof.annotation.PortableProperty;

@Portable public class Promotion {
    
    @PortableProperty(0) private String id;
    @PortableProperty(1) private String description;
    
    public Promotion() {}
    
    public Promotion(String id, String description) {
        setId(id);
        setDescription(description);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
    
}

A product is something that the customer hire from the bank. Some kind of service or item that make the customer account more valuable to the bank and more attractive to the customer since it is a differentiator. Here is the implementation of the product entity:

package com.acme.architecture.multichannel.domain;

import com.tangosol.io.pof.annotation.Portable;
import com.tangosol.io.pof.annotation.PortableProperty;

@Portable public class Product {
    
    @PortableProperty(0) private int id;
    @PortableProperty(1) private String name;
    
    public Product() {}
    
    public Product(int id, String name) {
        setId(id);
        setName(name);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

And finally, we need to design the customer entity. The customer entity will be the representation of the person or company that hires one or more products from the bank. Here is the implementation of the customer entity:

package com.acme.architecture.multichannel.domain;

import com.tangosol.io.pof.annotation.Portable;
import com.tangosol.io.pof.annotation.PortableProperty;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Portable public class Customer {
    
    @PortableProperty(0) private String ssn;
    @PortableProperty(1) private String firstName;
    @PortableProperty(2) private String lastName;
    @PortableProperty(3) private Date birthDate;
    
    @PortableProperty(4) private String account;
    @PortableProperty(5) private String agency;
    @PortableProperty(6) private double balance;
    @PortableProperty(7) private char custType;
    
    @PortableProperty(8) private Set<Product> products;
    @PortableProperty(9) private List<Promotion> promotions;
    
    public Customer() {}
    
    public Customer(String ssn, String firstName, String lastName,
                    Date birthDate, String account, String agency,
                    double balance, char custType, Set<Product> products) {
        setSsn(ssn);
        setFirstName(firstName);
        setLastName(lastName);
        setBirthDate(birthDate);
        setAccount(account);
        setAgency(agency);
        setBalance(balance);
        setCustType(custType);
        setProducts(products);
    }
    
    public void addPromotion(String id, String description) {
        getPromotions().add(new Promotion(id, description));
    }

    public String getSsn() {
        return ssn;
    }

    public void setSsn(String ssn) {
        this.ssn = ssn;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getAgency() {
        return agency;
    }

    public void setAgency(String agency) {
        this.agency = agency;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public char getCustType() {
        return custType;
    }

    public void setCustType(char custType) {
        this.custType = custType;
    }

    public Set<Product> getProducts() {
        return products;
    }

    public void setProducts(Set<Product> products) {
        this.products = products;
    }

    public List<Promotion> getPromotions() {
        if (promotions == null) {
            promotions = new ArrayList<Promotion>();
        }
        return promotions;
    }

    public void setPromotions(List<Promotion> promotions) {
        this.promotions = promotions;
    }
    
} 

As you can see in the code, the customer entity has a relationship with the two other entities. Build this code and package those three entities into a JAR file. We can now move to the second part of the implementation which is the creation of one SOA project that includes an business rules dictionary.

Creating the Business Rules Dictionary

Business rules in the Oracle Business Rules product are defined in an artifact called dictionary. In order to create an dictionary, you must use the Oracle JDeveloper IDE plus the SOA extension for JDeveloper. I will assume here that you are familiar with those tools, so I will not enter in too much detail about them. In JDeveloper, create a new SOA project, and after that create a business rules dictionary. With the dictionary in place, you must configure the dictionary to consider our domain model as fact types.


Now you can write down some business rules. Using the JDeveloper business rules editor, define the following rules as shown in the picture below.


For testing purposes, the variable "MinimumBalanceForCreditCard" it is just a global variable of type java.lang.Double that contains a constant value. Finally, you are required to expose those business rules through an decision function. As you probably already know, decision functions are constructions that make easier external applications to interact with Oracle Business Rules, minimizing the developers effort to deal with the Oracle Business Rules API, besides providing a very nice contract-based access point. Create one decision point that receives an customer as input, and returns the same customer as output. Don't forget to associate the ruleset with the decision function.

Integrating Oracle Business Rules and Coherence through Interceptors

Now here came the most exciting part of the article: the integration between Oracle Business Rules and Oracle Coherence In-Memory Data Grid. Starting from 12.1.2 version of Coherence, Oracle announced an new API called Live Events. This new API allows applications to listen/consume events from Coherence, no matter what type of event it is being generated. You can learn more about Coherence Live Events in this Youtube presentation.

Using both Coherence and Oracle Business Rules main libraries, implement the following event interceptor at your favorite Java development environment:

package com.oracle.coherence.events;

import com.tangosol.net.events.EventInterceptor;
import com.tangosol.net.events.annotation.Interceptor;
import com.tangosol.net.events.partition.cache.EntryEvent;
import com.tangosol.util.BinaryEntry;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import oracle.rules.sdk2.decisionpoint.DecisionPoint;
import oracle.rules.sdk2.decisionpoint.DecisionPointBuilder;
import oracle.rules.sdk2.decisionpoint.DecisionPointDictionaryFinder;
import oracle.rules.sdk2.decisionpoint.DecisionPointInstance;
import oracle.rules.sdk2.dictionary.RuleDictionary;
import oracle.rules.sdk2.exception.SDKException;

/**
 * @author ricardo.s.ferreira@oracle.com
 */

@Interceptor
public class FSRulesInterceptor implements EventInterceptor<EntryEvent> {

    private List<EntryEvent.Type> types;
    private String dictionaryLocation;
    private String decisionFunctionName;
    private boolean dictionaryAutoUpdate;
    private long dictionaryTimestamp;

    private void parseEntryEventTypes(String entryEventTypes) {
        types = new ArrayList<EntryEvent.Type>();
        String[] listTypes = entryEventTypes.split(COMMA);
        for (String type : listTypes) {
            types.add(EntryEvent.Type.valueOf(type.trim()));
        }
    }

    private RuleDictionary loadRuleDictionary()
        throws FileNotFoundException, SDKException, IOException {
        File dictionaryFile = new File(dictionaryLocation);
        dictionaryTimestamp = dictionaryFile.lastModified();
        RuleDictionary ruleDictionary = RuleDictionary.readDictionary(
            new FileReader(dictionaryFile), new DecisionPointDictionaryFinder(null));
        return ruleDictionary;
    }

    private DecisionPoint createDecisionPoint() {
        DecisionPoint decisionPoint = null;
        try {
            decisionPoint =
                    new DecisionPointBuilder()
                    .with(loadRuleDictionary())
                    .with(decisionFunctionName).build();
        } catch (Exception ex) {
            throw new RuntimeException("Unable to create the DecisionPoint", ex);
        }
        return decisionPoint;
    }

    private void updateDecisionPointIfNecessary() {
        File dictionaryFile = new File(dictionaryLocation);
        if (dictionaryFile.lastModified() != dictionaryTimestamp) {
            decisionPoint.release();
            decisionPoint = createDecisionPoint();
        }
    }

    private boolean eventTypeIsAllowed(EntryEvent.Type entryEventType) {
        return types.contains(entryEventType);
    }

    public FSRulesInterceptor(String entryEventTypes,
                              String dictionaryLocation,
                              String decisionFunctionName) {
        parseEntryEventTypes(entryEventTypes);
        this.dictionaryLocation = dictionaryLocation;
        this.decisionFunctionName = decisionFunctionName;
        decisionPoint = createDecisionPoint();
    }

    public FSRulesInterceptor(String entryEventTypes,
                              String dictionaryLocation,
                              String decisionFunctionName,
                              boolean dictionaryAutoUpdate) {
        this(entryEventTypes, dictionaryLocation, decisionFunctionName);
        this.dictionaryAutoUpdate = dictionaryAutoUpdate;
    }

    public void onEvent(EntryEvent entryEvent) {

        BinaryEntry binaryEntry = null;
        Iterator<BinaryEntry> iter = null;
        Set<BinaryEntry> entrySet = null;
        DecisionPointInstance dPointInst = null;
        List<Object> inputs, outputs = null;

        try {

            if (eventTypeIsAllowed(entryEvent.getType())) {

                if (dictionaryAutoUpdate) {
                    updateDecisionPointIfNecessary();
                }

                inputs = new ArrayList<Object>();
                entrySet = entryEvent.getEntrySet();
                for (BinaryEntry binaryEntryInput : entrySet) {
                    inputs.add(binaryEntryInput.getValue());
                }

                dPointInst = decisionPoint.getInstance();
                dPointInst.setInputs(inputs);
                outputs = dPointInst.invoke();

                if (entryEvent.getType() == EntryEvent.Type.INSERTING ||
                    entryEvent.getType() == EntryEvent.Type.UPDATING) {
                    if (outputs != null && !outputs.isEmpty()) {
                        iter = entrySet.iterator();
                        for (Object output : outputs) {
                            if (iter.hasNext()) {
                                binaryEntry = iter.next();
                                binaryEntry.setValue(output);
                            }
                        }
                    }
                }
                
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }
    
    private static final String COMMA = ",";
    private static DecisionPoint decisionPoint;

}

If you are familiar with the Oracle Business Rules Java API, you won't find any difficult to understand this code. What it does is simply create an DecisionPoint object during the constructor phase and put this object into a static variable, which allow this object to be shared across the entire JVM. Remember that the JVM in this context is a Coherence node, so what I am saying is that each Coherence node will hold an instance of one DecisionPoint. On the onEvent() method, there is the algorithm that checks which type of event the implementation should intercept, and also checks if the DecisionPoint instance should be updated. This last check is done based on the timestamp of the dictionary file.

After creating an DecisionPointInstance, the intercepted entries became the input variables for the business rules execution. The interceptor triggers the rules engine through the invoke() method, and after that it replaces the original intercepted entries with the result that came back from the business rules agenda. But only if one of the following events had happened: INSERTING or UPDATING. This check is necessary for two reasons. First, those are the only event types that occurs in the same thread of the cache transaction. Second, other event types like INSERTED or UPDATED happens in another thread, which means that they are triggered asynchronously by Coherence.

Setting Up an Coherence Distributed Cache with the Business Rules Interceptor

Now we can start the configuration of the Coherence cache. Since we are using POF as the serialization strategy, we need to assembly an POF configuration file. Starting from the 12.1.2 version of Coherence, there is a new tool called pof-config-gen that introspects JAR files searching for annotated classes with @Portable. Create a POF configuration file that should contain the following content:

<?xml version='1.0'?>

<pof-config
   xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
   xmlns='http://xmlns.oracle.com/coherence/coherence-pof-config'
   xsi:schemaLocation='http://xmlns.oracle.com/coherence/coherence-pof-config coherence-pof-config.xsd'>
	
   <user-type-list>
      <include>coherence-pof-config.xml</include>
      <user-type>
         <type-id>1001</type-id>
         <class-name>com.acme.architecture.multichannel.domain.Promotion</class-name>
      </user-type>
      <user-type>
         <type-id>1002</type-id>
         <class-name>com.acme.architecture.multichannel.domain.Product</class-name>
      </user-type>
      <user-type>
         <type-id>1003</type-id>
         <class-name>com.acme.architecture.multichannel.domain.Customer</class-name>
      </user-type>
   </user-type-list>
	
</pof-config>

And as expected, we also need to create an Coherence cache configuration file. Create one file called coherence-cache-config.xml and fill it with the following contents:

<?xml version="1.0"?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">

   <defaults>
      <serializer>pof</serializer>
   </defaults>

   <caching-scheme-mapping>
      <cache-mapping>
         <cache-name>customers</cache-name>
         <scheme-name>customersScheme</scheme-name>
      </cache-mapping>
   </caching-scheme-mapping>

   <caching-schemes>
	
      <distributed-scheme>
         <scheme-name>customersScheme</scheme-name>
         <service-name>DistribService</service-name>
         <backing-map-scheme>
            <local-scheme />
         </backing-map-scheme>
         <autostart>true</autostart>
         <interceptors>
            <interceptor>
               <name>rulesInterceptor</name>
               <instance>
                  <class-name>com.oracle.coherence.events.FSRulesInterceptor</class-name>
                  <init-params>
                     <init-param>
                        <param-type>java.lang.String</param-type>
                        <param-value>INSERTING, UPDATING</param-value>
                     </init-param>
                     <init-param>
                        <param-type>java.lang.String</param-type>
                        <param-value>C:\\multiChannelArchitecture.rules</param-value>
                     </init-param>
                     <init-param>
                        <param-type>java.lang.String</param-type>
                        <param-value>BankingDecisionFunction</param-value>
                     </init-param>
                     <init-param>
                        <param-type>java.lang.Boolean</param-type>
                        <param-value>true</param-value>
                     </init-param>
                  </init-params>
               </instance>
            </interceptor>
         </interceptors>
         <async-backup>true</async-backup>
      </distributed-scheme>
		
      <proxy-scheme>
         <scheme-name>customersProxy</scheme-name>
         <service-name>ProxyService</service-name>
         <acceptor-config>
            <tcp-acceptor>
               <local-address>
                  <address>cloud.app.foundation</address>
                  <port>5555</port>
               </local-address>
            </tcp-acceptor>
         </acceptor-config>
         <autostart>true</autostart>
      </proxy-scheme>

   </caching-schemes>
	
</cache-config>    

This cache configuration file is very straightforward. There is only three important things to consider here. First, we are using the new interceptor section to declare our interceptor and pass constructor arguments for it. Second, we used another feature from Coherence 12.1.2 version, which is the asynchronous backup feature. Using this feature dramatically reduces the latency of one single transaction, since backups are written after (in another thread) that the primary entry has been written. Not necessarily a pre-condition for the interceptor stuff works, but in the context of BRMS, should be a great idea. Third, we also defined a proxy-scheme that expose an TCP/IP endpoint, so we can use the Coherence*Extend feature later in this article, to allow a C++ application to access the same cache.

Testing the Scenario

Now that we have all the configuration in place, we can start the tests. Start an Coherence node JVM with the configuration file from the previous section. When you start the Coherence, a DecisionPoint object pointing to the business rules dictionary will be created in-memory. Implement a Java program to test the behavior of the implementation as the listing below:

package com.acme.architecture.multichannel.test;

import com.acme.architecture.multichannel.domain.Customer;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;

import java.util.Date;

public class Application {
    
    public static void main(String[] args) {
        
        NamedCache customers = CacheFactory.getCache("customers");
        
        // Simulating a simple customer of type 'Person'...
        // Should trigger the rule 'Basic Products for First Customers'
        // Expected number of promotions: 02
        String ssn = "12345";
        Customer personCust = new Customer(ssn, "Ricardo", "Ferreira",
                                         new Date(1981, 10, 05), "245671",
                                         "3158", 98000, 'P', null);
        
        long startTime = System.currentTimeMillis();
        customers.put(ssn, personCust);
        personCust = (Customer) customers.get(ssn);
        long elapsedTime = System.currentTimeMillis() - startTime;
        
        System.out.println();
        System.out.println("   ---> Number of Promotions: " +
                           personCust.getPromotions().size());
        System.out.println("   ---> Elapsed Time: " + elapsedTime + " ms");
        
        // Simulating a simple customer of type 'Company'...
        // Should trigger the rule 'Corporate Credit Card for Enterprise Customers'
        // Expected number of promotions: 01
        ssn = "54321";
        Customer companyCust = new Customer(ssn, "Huge", "Company",
                                         new Date(1981, 10, 05), "235437",
                                         "7856", 8900000, 'C', null);
        
        startTime = System.currentTimeMillis();
        customers.put(ssn, companyCust);
        companyCust = (Customer) customers.get(ssn);
        elapsedTime = (System.currentTimeMillis() - startTime);
        
        System.out.println("   ---> Number of Promotions: " +
                           companyCust.getPromotions().size());
        System.out.println("   ---> Elapsed Time: " + elapsedTime + " ms");
        System.out.println();
        
    }
    
}

This Java application can be executed with the storage-enabled parameter set to false. Executing this code will give you an output similar to this:

2013-08-15 23:37:53.058/1.378 Oracle Coherence 12.1.2.0.0 <Info> (thread=Main Thread, member=n/a): Loaded operational configuration from "jar:file:/C:/mw-home/coherence/lib/coherence.jar!/tangosol-coherence.xml"
2013-08-15 23:37:53.093/1.413 Oracle Coherence 12.1.2.0.0 <Info> (thread=Main Thread, member=n/a): Loaded operational overrides from "jar:file:/C:/mw-home/coherence/lib/coherence.jar!/tangosol-coherence-override-dev.xml"
2013-08-15 23:37:53.093/1.413 Oracle Coherence 12.1.2.0.0 <D5> (thread=Main Thread, member=n/a): Optional configuration override "/tangosol-coherence-override.xml" is not specified
2013-08-15 23:37:53.093/1.413 Oracle Coherence 12.1.2.0.0 <D5> (thread=Main Thread, member=n/a): Optional configuration override "cache-factory-config.xml" is not specified
2013-08-15 23:37:53.093/1.413 Oracle Coherence 12.1.2.0.0 <D5> (thread=Main Thread, member=n/a): Optional configuration override "cache-factory-builder-config.xml" is not specified
2013-08-15 23:37:53.093/1.413 Oracle Coherence 12.1.2.0.0 <D5> (thread=Main Thread, member=n/a): Optional configuration override "/custom-mbeans.xml" is not specified

Oracle Coherence Version 12.1.2.0.0 Build 44396
 Grid Edition: Development mode
Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

2013-08-15 23:37:53.380/1.700 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Main Thread, member=n/a): Loaded cache configuration from "file:/C:/poc-itau-brms/resources/coherence-cache-config.xml"
2013-08-15 23:37:55.615/3.935 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Main Thread, member=n/a): Created cache factory com.tangosol.net.ExtensibleConfigurableCacheFactory
2013-08-15 23:37:56.206/4.526 Oracle Coherence GE 12.1.2.0.0 <D4> (thread=Main Thread, member=n/a): TCMP bound to /10.0.3.15:8090 using SystemDatagramSocketProvider
2013-08-15 23:37:56.528/4.848 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Cluster, member=n/a): Failed to satisfy the variance: allowed=16, actual=54
2013-08-15 23:37:56.528/4.848 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Cluster, member=n/a): Increasing allowable variance to 20
2013-08-15 23:37:56.885/5.205 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Cluster, member=n/a): This Member(Id=2, Timestamp=2013-08-15 23:37:56.653, Address=10.0.3.15:8090, MachineId=2319, Location=site:,process:3484, Role=AcmeArchitectureApplication, Edition=Grid Edition, Mode=Development, CpuCount=3, SocketCount=3) joined cluster "cluster:0x50DB" with senior Member(Id=1, Timestamp=2013-08-15 21:16:58.769, Address=10.0.3.15:8088, MachineId=2319, Location=site:,process:3000, Role=CoherenceServer, Edition=Grid Edition, Mode=Development, CpuCount=3, SocketCount=3)
2013-08-15 23:37:57.189/5.509 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Main Thread, member=n/a): Started cluster Name=cluster:0x50DB

Group{Address=224.12.1.0, Port=12100, TTL=4}

MasterMemberSet(
  ThisMember=Member(Id=2, Timestamp=2013-08-15 23:37:56.653, Address=10.0.3.15:8090, MachineId=2319, Location=site:,process:3484, Role=AcmeArchitectureApplication)
  OldestMember=Member(Id=1, Timestamp=2013-08-15 21:16:58.769, Address=10.0.3.15:8088, MachineId=2319, Location=site:,process:3000, Role=CoherenceServer)
  ActualMemberSet=MemberSet(Size=2
    Member(Id=1, Timestamp=2013-08-15 21:16:58.769, Address=10.0.3.15:8088, MachineId=2319, Location=site:,process:3000, Role=CoherenceServer)
    Member(Id=2, Timestamp=2013-08-15 23:37:56.653, Address=10.0.3.15:8090, MachineId=2319, Location=site:,process:3484, Role=AcmeArchitectureApplication)
    )
  MemberId|ServiceVersion|ServiceJoined|MemberState
    1|12.1.2|2013-08-15 21:16:58.769|JOINED,
    2|12.1.2|2013-08-15 23:37:56.653|JOINED
  RecycleMillis=1200000
  RecycleSet=MemberSet(Size=0
    )
  )

TcpRing{Connections=[1]}
IpMonitor{Addresses=0}

2013-08-15 23:37:57.243/5.581 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Cluster, member=2): Loaded POF configuration from "file:/C:/poc-itau-brms/resources/pof-config.xml"
2013-08-15 23:37:57.261/5.581 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Cluster, member=2): Loaded included POF configuration from "jar:file:/C:/mw-home/coherence/lib/coherence.jar!/coherence-pof-config.xml"
2013-08-15 23:37:57.386/5.706 Oracle Coherence GE 12.1.2.0.0 <D5> (thread=Invocation:Management, member=2): Service Management joined the cluster with senior service member 1
2013-08-15 23:37:57.494/5.814 Oracle Coherence GE 12.1.2.0.0 <Info> (thread=Main Thread, member=2): Loaded Reporter configuration from "jar:file:/C:/mw-home/coherence/lib/coherence.jar!/reports/report-group.xml"
2013-08-15 23:37:58.012/6.332 Oracle Coherence GE 12.1.2.0.0 <D5> (thread=DistributedCache:DistribService, member=2): Service DistribService joined the cluster with senior service member 1

   ---> Number of Promotions: 2
   ---> Elapsed Time: 53 ms
   ---> Number of Promotions: 1
   ---> Elapsed Time: 0 ms

2013-08-15 23:37:58.137/6.457 Oracle Coherence GE 12.1.2.0.0 <D4> (thread=ShutdownHook, member=2): ShutdownHook: stopping cluster node

As you can see in the output, the number of promotions showed reveals that the business rules were really executed, since during the instantiation of the customer object promotions weren't provided. The output also tells us another important thing: transaction latency. For the first cache entry we got 53 ms as overall latency, quite short if you consider what happened behind the scenes. But the second cache entry is even much more faster, with 0 ms of latency. This means that the actual time necessary to execute the entire transaction was something below of one millisecond, giving us an real sub-millisecond latency scenario, measured in microseconds.

High Scalable Business Rules

It is not so obvious when you understand this implementation for first time, but another important aspect of this design is scalability. Since the cache type that we used was the distributed one, also known as partitioned, the overall cache entries are equally distributed among all Coherence nodes available. If we use only one node, of course that this one node will handle the entire dataset by itself. But if we use four nodes, each node will handle 25% of the dataset. This means that if we insert one million customer objects in the cache, each node will handle only 250K customers.

This type of data storage offers a huge benefit for Oracle Business Rules, which is the truly data load distribution. Remember that I said before that each Coherence node will hold one DecisionPoint instance? Since each node handle only a percentage of the entire dataset, its reasonable to think that each node will fire rules only for the data that it manages. This happens this way because Coherence interceptors are executed in the JVM that the data lives, not in the entire data grid since it is not a distributed processing. For instance, if the customer "A" is primarily stored in the "JVM 1", and this customer "A" has its fields updated by one client application, business rules will be fired and executed only in the "JVM 1". The other JVMs will not execute any business rules. This means that CPU overhead can be balanced across the cluster of servers, allowing the In-Memory Data Grid scale up horizontally, using the overall compute power of different servers available in the cluster.

API Transparency and Multiple Programming Language Support

Once the Oracle Business Rules is encapsulated in Coherence through an interceptor, there is another great advantage of this design: API transparency. Developers don't need to write custom code to interact with Oracle Business Rules. In fact, they don't ever need to know that business rules are being executed when objects are written in Coherence. Since all happens behind the scenes, this approach free developers from extra complexity, allowing them to work only in a data-oriented fashion which is very productive and less error prone.

And because Oracle Coherence offers you not only a Java API to interact with the In-Memory Data Grid, but also a C++, .NET and an REST API, you can leverage several types of clients and applications to trigger business rules executions. In fact, I have created a very small C++ application using Microsoft Visual Studio to test this behavior. The application code below inserts 1K customers into the In-Memory Data Grid, with an average transaction latency of ~5 ms, using a VM with 3 vCores and 10 GB of RAM.

#include "stdafx.h"
#include <windows.h>
#include <cstring>
#include <iostream>
#include "Customer.hpp"

#include "coherence/lang.ns"
#include "coherence/net/CacheFactory.hpp"
#include "coherence/net/NamedCache.hpp"

using namespace coherence::lang;
using coherence::net::NamedCache;
using coherence::net::CacheFactory;

int NUMBER_OF_ENTRIES = 1000;

__int64 currentTimeMillis()
{
	static const __int64 magic = 116444736000000000;
	SYSTEMTIME st;
	GetSystemTime(&st);
	FILETIME   ft;
	SystemTimeToFileTime(&st,&ft);
	__int64 t;
	memcpy(&t,&ft,sizeof t);
	return (t - magic)/10000;
}

int _tmain(int argc, _TCHAR* argv[])
{

	Customer customer;
	std::string _customerKey;
	String::View customerKey;
	__int64 startTime = 0, endTime = 0;
	__int64 elapsedTime = 0;
	
	NamedCache::Handle customer = CacheFactory::getCache("customers");

	startTime = currentTimeMillis();
	for (int i = 0; i < NUMBER_OF_ENTRIES; i++)
	{
		std::ostringstream stream;
		stream << "customer-" << (i + 1);
		_customerKey = stream.str();
		customerKey = String::create(_customerKey);
		std::string firstName = _customerKey;
		std::string agency = "3158";
		std::string account = "457899";
		char custType = 'P';
		double balance = 98000;
		Customer customer(customerKey, firstName,
			agency, account, custType, balance);
		Managed<Customer>::View customerWrapper = Managed<Customer>::create(customer);
		customers->put(customerKey, customerWrapper);
		Managed<Customer>::View result =
			cast<Managed<Customer>::View> (customers->get(customerKey));
	}
	endTime = currentTimeMillis();
	elapsedTime = endTime - startTime;

	std::cout << std::endl;
	std::cout << "   Elapsed Time..................: "
		<< elapsedTime << " ms" << std::endl;
	std::cout << std::endl;

	CacheFactory::shutdown();
	getchar();
	return 0;

}

An Alternative Version of the Interceptor for MDS Scenarios

The interceptor created in this article uses the Oracle Business Rules Java API to read the dictionary directly from the file system. This approach suggests two things: first, that the repository of the dictionary will be the file system. Second, that the authoring and management of the dictionary will be done through JDeveloper. This can lead into some lost of the BRMS power since business users won't feel comfortable authoring their rules in a technological environment such as JDeveloper. Administrators won't have the power of see who changed what since virtually any person can open the file in JDeveloper and change its contents.

A better way to manage this is storing the dictionary in a MDS repository, which is part of the Oracle SOA Suite platform. Storing the dictionary in the MDS repository allows business users to interact with business rules through the SOA composer, a very nice web tool, more simpler and easy-2-use than JDeveloper. Administrators can also track down changes, since everything in the MDS are audited, transaction based and securely controlled, since you have to first log in the console to get access to the composer.

I have implemented another version of the interceptor, making full use of the power of Oracle SOA Suite and MDS repositories. The implementation of MDSRulesInterceptor.java is being tested for over a month and is performing quite well, just like the FSRulesInterceptor.java implementation. In the future, I will post here this implementation, but for now just keep in mind the powerful things that can be done with Oracle Business Rules and Coherence In-Memory Data Grid. Oracle Fusion Middleware really rocks isn't?


Wednesday Aug 29, 2012

Integrating Coherence & Java EE 6 Applications using ActiveCache

OK, so you are a developer and are starting a new Java EE 6 application using the most wonderful features of the Java EE platform like Enterprise JavaBeans, JavaServer Faces, CDI, JPA e another cool stuff technologies. And your architecture need to hold piece of data into distributed caches to improve application's performance, scalability and reliability?

If this is your current facing scenario, maybe you should look closely in the solutions provided by Oracle WebLogic Server. Oracle had integrated WebLogic Server and its champion data caching technology called Oracle Coherence. This seamless integration between this two products provides a comprehensive environment to develop applications without the complexity of extra Java code to manage cache as a dependency, since Oracle provides an DI ("Dependency Injection") mechanism for Coherence, the same DI mechanism available in standard Java EE applications. This feature is called ActiveCache. In this article, I will show you how to configure ActiveCache in WebLogic and at your Java EE application.

Configuring WebLogic to manage Coherence

Before you start changing your application to use Coherence, you need to configure your Coherence distributed cache. The good news is, you can manage all this stuff without writing a single line of code of XML or even Java. This configuration can be done entirely in the WebLogic administration console. The first thing to do is the setup of a Coherence cluster. A Coherence cluster is a set of Coherence JVMs configured to form one single view of the cache. This means that you can insert or remove members of the cluster without the client application (the application that generates or consume data from the cache) knows about the changes. This concept allows your solution to scale-out without changing the application server JVMs. You can growth your application only in the data grid layer.

To start the configuration, you need to configure an machine that points to the server in which you want to execute the Coherence JVMs. WebLogic Server allows you to do this very easily using the Administration Console. For this example, consider the machine name as "coherence-server".

Remember that in order to the machine concept works, you need to ensure that the NodeManager are being executed in the target server that the machine points to. The NodeManager script can be found in <WLS_HOME>/server/bin/startNodeManager.sh.

The next thing to do is to configure an Coherence cluster. In the WebLogic administration console, navigate to Environment > Coherence Clusters and click in "New" button.

In the field "Name", set the value to "my-coherence-cluster". Click in next.

Specify a valid cluster address and port. The Coherence members will communicate with each other through this address and port. This configuration section tells Coherence to form a cluster using unicast of messages instead of multicast which is the standard. Since the method used will be unicast, you need to configure a valid cluster address and cluster port.

The Coherence cluster has been configured successfully. Now it is time to configure the Coherence members and add them to the cluster. In the WebLogic administration console, navigate to Environment > Coherence Servers and click in "New" button.

In the field "Name" set the value to "coh-server-1". In the field "Machine", associate this new Coherence server to the machine "coherence-server". In the field "Cluster", associate this new Coherence server to the cluster named "my-coherence-cluster". Click in the "Finish" button.

For this example, I will configure only one Coherence server. This means that the Coherence cluster will be composed by only one member. In production scenarios, you will have thousands of Coherence members, all of them distributed in different machines with different configurations. The idea behind Coherence clusters is exactly this: form a virtual data grid composed by different members that will be joined or removed to/from the cluster dynamically.

Before start the Coherence server, you need to configure its classpath. When the JVM of an Coherence server starts, its loads a couple classes that need to be available in runtime. To configure the classpath of the Coherence server, click into the Coherence server name. This action will bring the configuration page of the Coherence server. Click in the "Server Start" tab.

In the "Server Start" tab you will find a lot of fields that can be configured to control the behavior of the Coherence server JVM. In the "Class Path" field, you need to list the following JAR files:

- <WLS_HOME>/modules/features/weblogic.server.modules.coherence.server_12.1.1.0.jar

- <COHERENCE_HOME>/lib/coherence.jar

Remember to use a valid file separator compatible with the target operating system that you are using. Click in the "Save" button to update the configuration of the Coherence server.

Now you are ready to go. Start the Coherence server using the "Control" tab of WebLogic administration console. This will instruct WebLogic to send a command to the target machine. This command, once it is received by the target machine, will be responsible to start a new JVM for the Coherence server, according to the parameters that we have configured.

Configuring your Java EE Application to Access Coherence

Now lets pass to the funny part of the configuration. The first thing to do is to inform your Java EE application which Coherence cluster to join. Oracle had updated WebLogic server deployment descriptors so you will not have to change your code or the containers deployment descriptors like application.xml, ejb-jar.xml or web.xml.

In this example, I will show you how to enable DI ("Dependency Injection") to a Coherence cache from a Servlet 3.0 component. In the WEB-INF/weblogic.xml deployment descriptor, put the following metadata information:

<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
	xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
	http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">
	
	<wls:context-root>myWebApp</wls:context-root>
	
	<wls:coherence-cluster-ref>
		<wls:coherence-cluster-name>my-coherence-cluster</wls:coherence-cluster-name>
	</wls:coherence-cluster-ref>
	
</wls:weblogic-web-app>

As you can see, using the "coherence-cluster-name" tag, we are informing our Java EE application that it should join the "my-coherence-cluster" when it loads in the web container. Without this information, the application will not be able to access the predefined Coherence cluster. It will form its own Coherence cluster without any members. So never forget to put this information.

Now put the coherence.jar and active-cache-1.0.jar dependencies at your WEB-INF/lib application classpath. You need to deploy this dependencies so ActiveCache can automatically take care of the Coherence cluster join phase. This dependencies can be found in the following locations:

- <WLS_HOME>/common/deployable-libraries/active-cache-1.0.jar

- <COHERENCE_HOME>/lib/coherence.jar

Finally, you need to write down the access code to the Coherence cache at your Servlet. In the following example, we have a Servlet 3.0 component that access a Coherence cache named "transactions" and prints into the browser output the content (the ammount property) of one specific transaction.

package com.oracle.coherence.demo.activecache;

import java.io.IOException;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.tangosol.net.NamedCache;

@WebServlet("/demo/specificTransaction")
public class TransactionServletExample extends HttpServlet {
	
	@Resource(mappedName = "transactions") NamedCache transactions;
	
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		
		int transId = Integer.parseInt(request.getParameter("transId"));
		Transaction transaction = (Transaction) transactions.get(transId);
		response.getWriter().println("<center>" + transaction.getAmmount() + "</center>");
		
	}

}

Thats it! No more configuration is necessary and you have all set to start producing and getting data to/from Coherence. As you can see in the example code, the Coherence cache are treated as a normal dependency in the Java EE container. The magic happens behind the scenes when the ActiveCache allows your application to join the defined Coherence cluster.

The most interesting thing about this approach is, no matter which type of Coherence cache your are using (Distributed, Partitioned, Replicated, WAN-Remote) for the client application, it is just a simple attribute member of com.tangosol.net.NamedCache type. And its all managed by the Java EE container as an dependency. This means that if you inject the same dependency (the Coherence cache named "transactions") in another Java EE component (JSF managed-bean, Stateless EJB) the cache will be the same. Cool isn't it?

Thanks to the CDI technology, we can extend the same support for non-Java EE standards components like simple POJOs. This means that you are not forced to only use Servlets, EJBs or JSF in order to inject Coherence caches. You can do the same approach for regular POJOs created for you and managed by lightweight containers running inside Oracle WebLogic Server.

Friday Oct 28, 2011

Enabling HPC (High Performance Computing) with InfiniBand in Java™ Applications

For many IT managers, choosing a computing fabric often falls short of receiving the center stage attention it deserves. Familiarity and time have set infrastructure architects on a seemingly intransigent course toward settling for fabrics that have become the "de-facto" norm. On one hand is Ethernet, which is regarded as the solution for an almost ridiculously broad range of needs, from web surfing to high-performance, every microsecond counts storage traffic. On the other hand is Fibre Channel, which provides more deterministic performance and bandwidth, but at the cost of bifurcating the enterprise network while doubling the cabling, administrative overhead, and total cost of ownership. But even with separate fabrics, these networks sometimes fall short of today's requirements. So slowly but surely some enterprises are waking up to the potential of new fabrics.

There is no other component of the data center that is as important as the fabric, and the I/O challenges facing enterprises today have been setting the stage for next-generation fabrics for several years. One of the more mature solutions, InfiniBand, is sometimes at the forefront when users are driven to select a new fabric. New customers are often surprised at what InfiniBand can deliver, and how easily it integrates into today's data center architectures. InfiniBand has been leading the charge toward consolidated, flexible, low latency, high bandwidth, lossless I/O connectivity. While other fabrics have just recently turned to addressing next generation requirements, with technologies such as Fibre Channel over Ethernet (FCoE) still seeking general acceptance and even standardization, InfiniBand's maturity and e bandwidth advantages continue to attract new customers.

Although InfiniBand remains a small industry compared to the Ethernet juggernaut, it continues to grow aggressively, and this year it is growing beyond projections. InfiniBand often plays a role in enterprises with huge datasets. The majority of Fortune 1000 companies are involved in high-throughput processing behind a wide variety of systems, including business analytics, content creation, content trans-coding, real-time financial applications, messaging systems, consolidated server infrastructures, and more. In these cases, InfiniBand has worked its way into the enterprise as a localized fabric that, via transparent interconnection to existing networks, is sometimes even hidden from the eyes of administrators.

For high performance computing environments, the capacity to move data across a network quickly and efficiently is a requirement. Such networks are typically described as requiring high throughput and low latency. High throughput refers to an environment that can deliver a large amount of processing capacity over a long period of time. Low latency refers to the minimal delay between processing input and providing output, such as you would expect in a real-time application.

In these environments, conventional networking using socket streams can create bottlenecks when it comes to moving data. Introduced in 1999 by the InfiniBand Trade Association, InfiniBand (IB for short) was created to address the need for high performance computing. One of the most important features of IB is Remote Direct Memory Access (RDMA). RDMA enables moving data directly from the memory of one computer to another computer, bypassing the operating system of both computers and resulting in significant performance gains. The usage of InfiniBand network is particularly interesting when applied in engineered systems like Oracle Exalogic and SPARC SuperCluster T4-4. In this type of system, you can boost the performance of your application from 3X to 10X without changing a single line of code.

The Sockets Direct Protocol or SDP for short is a networking protocol developed to support stream connections over InfiniBand fabric. SDP support was introduced to the JDK 7 release of the Java Platform, for applications deployed in the Solaris and Linux operating systems. The Solaris operating system has supported SDP and InfiniBand since Solaris 10. On the Linux, world the InfiniBand package is called OFED (OpenFabrics Enterprise Distribution). The JDK 7 release supports the 1.4.2 and 1.5 versions of OFED.

SDP support is essentially a TCP bypass technology. When SDP is enabled and an application attempts to open a TCP connection, the TCP mechanism is bypassed and communication goes directly to the InfiniBand network. For example, when your application attempts to bind to a TCP address, the underlying software will decide, based on information in the configuration file, if it should be rebound to an SDP protocol. This process can happen during the binding process or the connecting process, but happens only once for each socket.

There are no API changes required in your code to take advantage of the SDP protocol: the implementation is transparent and is supported by the classic networking and the NIO (New I/O) channels. SDP support is disabled by default. The steps to enable SDP support are:

  • Create an text-based file that will act as the SDP configuration file
  • Set the system property that specifies the location of this configuration file.

Creating an Configuration File for SDP

An SDP configuration file is a text file, and you decide where on the file system this file will reside. Every line in the configuration file is either a comment or a rule. A comment is indicated by the hash character "#" at the beginning of the line, and everything following the hash character will be ignored.

There are two types of rules, as follows:

  • A "bind" rule indicates that the SDP protocol transport should be used when a TCP socket binds to an address and port that match the rule.
  • A "connect" rule indicates that the SDP protocol transport should be used when an unbound TCP socket attempts to connect to an address and port that match the rule.

A rule has the following format:

("bind"|"connect")1*LWSP-char(hostname|ipaddress)["/"prefix])1*LWSP-char("*"|port)["-"("*"|port)]
The first keyword indicates whether the rule is a bind or a connect rule. The next token specifies either a host name or a literal IP address. When you specify a literal IP address, you can also specify a prefix, which indicates an IP address range. The third and final token is a port number or a range of port numbers. Consider the following notation in this sample configuration file:
# Enable SDP when binding to 192.168.0.1
bind 192.168.0.1 *

# Enable SDP when connecting to all
# application services on 192.168.0.*
connect 192.168.0.0/24     1024-*

# Enable SDP when connecting to the HTTP server
# or a database on oracle.infiniband.sdp.com
connect oracle.infiniband.sdp.com   80
connect oracle.infiniband.sdp.com   3306
The first rule in the sample file specifies that SDP is enabled for any port (*) on the local IP address 192.168.0.1. You would add a bind rule for each local address assigned to an InfiniBand adaptor. An InfiniBand adaptor is the equivalent of a network interface card (NIC) for InfiniBand. If you had several InfiniBand adaptors, you would use a bind rule for each address that is assigned to those adaptors. The second rule in the sample file specifies that whenever connecting to 192.168.0.* and the target port is 1024 or greater, SDP will be enabled. The prefix on the IP address /24 indicates that the first 24 bits of the 32-bit IP address should match the specified address. Each portion of the IP address uses 8 bits, so 24 bits indicates that the IP address should match 192.168.0 and the final byte can be any value. The "-*" notation on the port token specifies "and above." A range of ports, such as 1024—2056, would also be valid and would include the end points of the specified range.

The final rules in the sample file specify a host name called oracle.infiniband.sdp.com, first with the port assigned to an HTTP server (80) and then with the port assigned to a database (3306). Unlike a literal IP address, a host name can translate into multiple addresses. When you specify a host name, it matches all addresses that the host name is registered to in the name service.

How Enable the SDP Protocol?

SDP support is disabled by default. To enable SDP support, set the com.sun.sdp.conf system property by providing the location of the configuration file. The following example starts an application named MyApplication using a configuration file named sdp.conf:

     java -Dcom.sun.sdp.conf=sdp.conf -Djava.net.preferIPv4Stack=true MyApplication

MyApplication refers to the client application written in Java that is attempting to connect to the InfiniBand adaptor. Note that this example specifies another system property, java.net.preferIPv4Stack. Maybe, not everything discussed so far will work perfectly fine. There are some technical issues that you should be aware when enabling SDP, most of them, related with the IPv4 or IPv6 stacks. If something in your tests goes wrong, it is a nice idea ensure with your operating system administrators if the InfiniBand is correctly configured at this layer.

APIs and Supported Java Classes

    All classes in the JDK that read or write network sockets can use SDP. As said before, there are no changes in the way you write the code. This is particularly interesting if you are only installing a already deployed application in a cluster that uses InfiniBand. When SDP support is enabled, it just works without any change to your code. Compiling is not necessary. However, it is important to know that a socket is bound only once. A connection is an implicit bind. So, if the socket hasn't been previously bound and connect is invoked, the binding occurs at that time. For example, consider the following code:

    AsynchronousSocketChannel asyncSocketChannel = AsynchronousSocketChannel.open();
    InetSocketAddress localAddress = new InetSocketAddress("10.0.0.3", 8020);
    InetSocketAddress remoteAddress = new InetSocketAddress("192.168.0.1", 1521);
    asyncSocketChannel.bind(localAddress);
    Future result = asyncSocketChannel.connect(remoteAddress);
    

    In this example, the asynchronous socket channel is bound to a local TCP address when bind is invoked on the socket. Then, the code attempts to connect to a remote address by using the same socket. If the remote address uses InfiniBand, as specified in the configuration file, the connection will not be converted to SDP because the socket was previously bound.

    Conclusion

    InfiniBand is a "de-facto" standard for high performance computing environments that needs to guarantee extreme low latency and superior throughput. But to really take advantage of this technology, your applications need to be properly prepared. This article showed how you can maximize the performance of Java applications enabling the SDP support, the network protocol that "talks" with the InfiniBand network.

    Tuesday Oct 11, 2011

    Getting Started with Oracle Tuxedo: Creating a COBOL-based Application

    This article will show how COBOL developers can create, with minimum effort, robust distributed and service-oriented applications using the facilities offered by Oracle Tuxedo. Through a step by step example, you will learn how to configure and setup an Oracle Tuxedo development environment, how to implement COBOL code that enable client and server interactions and how to deploy and test the application into Oracle Tuxedo. For this article, is expected that you have a basic knowledge of the Linux operating system and basic knowledge about programming, if you are not a COBOL developer of course.

    What is exactly the Oracle Tuxedo?

    Simply put, Oracle Tuxedo is an application server for non-Java developers. This means that developers of programming languages like C/C++, COBOL, Python and Ruby can implement distributed applications using enterprise features like messaging, clustering, security, load-balancing, scalability and thread management from an middleware implementation. The main difference is that the platform itself are not based on Java, and the programming language used to develop the applications are not restricted to Java.

    Historically speaking, the concept of an application server had been used in distributed architectures to promote loosely coupling between client and server applications (reason because it's commonly named as middleware) and the reuse of common features that, in the old days, developers had to write every time they need to create a distributed application, features like messaging, clustering, security, load-balancing scalability and thread management, most of them, features that represent critical non-functional requirements. Instead of create every time those features, you can just delegate to a middleware that host those features and reuse across different applications, since they will offer the same behavior for every application. Another key thing about application servers is the fact that they introduce a programing model that forces developers to focus only in business logic instead of basic infrastructure.

    With this programming model in mind, you can write distributed applications without worrying about that they are distributed, meaning that you don't have to worry about how client applications invokes services from server applications, how the message are serialized and unserialized between remote computers and how cross-cutting concerns (aspects) are applied at every interaction. Oracle Tuxedo has been very popular in the market as an application server, and it has more than 25 years of maturity and evolution.

    In fact, the firsts line of code of Tuxedo was written in 1983 at the AT&T laboratories. Years later, Novell acquired the Unix System Laboratories, the AT&T division responsible for the Tuxedo development. In a exclusive formal agreement, BEA Systems started the development of Tuxedo in non-Netware platforms and became the principal maintainer and distributor of Tuxedo technology. Since the acquisition by Oracle in 2008, Tuxedo was renamed to Oracle Tuxedo and now are part of the Oracle Fusion Middleware stack, being massively optimized year after year.

    COBOL? Why not C/C++ or Java?

    You probably are wondering why this article will be focused in the COBOL programming language instead of more popular and equally powerful programming languages like C/C++ or Java. COBOL is a structured programming language largely used worldwide at many organizations, due to it's popularity in the 80's and the 90's and the highly mainframe adoption. In fact, most banking organizations today runs their critical transactions at mainframes and those transactions are written in COBOL. Even in the x86 architectures we found many applications written at this programming language, so it is a little bit fair with COBOL developers dedicate this article for them.

    In the C/C++ world, the concept and usage of application servers are pretty common. In the Java world is almost a rule of development, so it's natural for Java developers (actually, Java EE developers) use application servers. Unfortunately, this scenario does not apply for COBOL developers. Having said that, Oracle Tuxedo was designed from the source to handle C/C++ implementations, and if you are a C/C++ developer, you will not find any difficulties to work with Oracle Tuxedo. It is unnecessary to say that, if you are a Java developer, there are a bunch of application server implementations available in the market today, like Oracle WebLogic and Oracle GlassFish. There is no need to use Oracle Tuxedo as application server.

    Setting Up an Oracle Tuxedo Development Environment for COBOL

    Let's start the development of a simple distributed application using Oracle Tuxedo and the COBOL programming language. For this article, I have used a Linux operating system (Fedora 14) as platform. If you intend to use another operating system, check if both Oracle Tuxedo and the COBOL compiler supports it. The first thing to configure is a proper COBOL compiler. Oracle Tuxedo does not distribute any compiler, neither for C/C++. You need to use a certified compiler in order to develop using Oracle Tuxedo. Fortunately, there are many COBOL compilers available today. Oracle certifies two compilers in particular: Micro Focus COBOL compiler and COBOL-IT compiler suite. The main difference between this two implementations are the fact that Micro Focus COBOL compiler are proprietary, and demands that you pay licenses to use it. COBOL-IT on the other hand are free and open source. The company basically gives you support and consultancy through subscriptions. For this article, we will use COBOL-IT in the development of the example.

    You will need a C/C++ compiler too. During the compilation of COBOL source code files, Oracle Tuxedo translate COBOL code to native C/C++ code, and after that, it compiles it to the target platform as a native executable. This means that implicitly, a C/C++ program compilation occurs, even being you used COBOL as programming language. There no restrictions about which C/C++ compiler to use. If you are a Linux user, the ANSI GNU compiler will be available already in the platform. Depending of your Linux distribution, some other packages must be installed too.

    Download the COBOL-IT compiler suite clicking here. The COBOL-IT compiler suite installation is pretty simple. Just download the zipped files and unzip into a folder of your preference. In my environment, I have unzipped in the /home/riferrei/cobol-it-std-64 folder. After unzip the files, you need to define two environment variables:

          COBOLITDIR=/home/riferrei/cobol-it-std-64

          PATH=$PATH:$COBOLITDIR/bin

    After defining these two environment variables, you are ready to use your COBOL-IT compiler suite. Let's create a simple "Hello World" COBOL application to certify that the COBOL-IT compiler are really working. Create a file in your environment named hello.cbl, and in this file write the following COBOL code:

           IDENTIFICATION DIVISION.
           PROGRAM-ID. 'hello'.
           ENVIRONMENT DIVISION.
           INPUT-OUTPUT SECTION.
           FILE-CONTROL.
           DATA DIVISION.
           FILE SECTION.
    
           WORKING-STORAGE SECTION.
    
           01  HELLOMSG    PIC X(80).
    
           LINKAGE SECTION.
    
           PROCEDURE DIVISION.
    
           MAIN SECTION.
                MOVE "Hello World using COBOL" TO HELLOMSG
                DISPLAY HELLOMSG
                ACCEPT  HELLOMSG
                EXIT PROGRAM.
    

    To compile this source code and to generate a native executable program, you need to use the COBOL-IT compiler, using the following command:

         cobc -x hello.cbl

    As you can see, the COBOL-IT compiler used in the command cobc available in the /bin installation directory of COBOL-IT compiler suite. After type this command,  you should see a native executable program generated in the same directory of the file hello.cbl. Executing this program should generate, unsurprisingly, a console output with the following message: "Hello World using COBOL".

    Now that your COBOL-IT compiler suite are up and running, it is time to move forward and start the configuration of Oracle Tuxedo. For this article, I have used the 11g R1 version of Oracle Tuxedo, which in the time of the development of this article was the latest version available. Oracle Tuxedo is freely available for learning and evaluation. You can download the installation software here. Install the software following the recommendations documented in the Oracle Tuxedo installation guide. I will not repeat those instructions here since you can easily follow from the official documentation.

    The configuration of Oracle Tuxedo is very straightforward. In the essence, you have to define a series of environment variables that change the behavior about how Oracle Tuxedo will compile, execute and manage the COBOL applications. It is important no remember that the instructions available here applies exclusively for COBOL development with Oracle Tuxedo. If you want to use Oracle Tuxedo with C/C++ programming language, there are another instructions available. I summarized the list of environment variables that, for the development of this article, you need to define.

    It is a good idea to put those environment variables in the system scope. If you are a Linux operating system user, could be e good idea define those environment variables in the .bash_profile configuration file of your home directory.

    Creating a COBOL-based Application using Oracle Tuxedo

    When you develop applications using Oracle Tuxedo, you actually create a distributed application. This means that you should create at least two applications: The client-tier, which will be the "presentation layer" for the end-user, and the server-tier, which provides one or more services interfaces to be consumed from the client-tier. If you look closely, It is basically the same architecture that Java EE provides. The programming model used in Oracle Tuxedo can be ATMI and CORBA. For this article, I will use ATMI since is the only programming model available for COBOL programming language. But for most advanced applications, specially those ones based on C/C++ programming language, I encourage you to use CORBA instead of ATMI. This approach frees your code from Oracle Tuxedo specific API's, turning your business logic actually portable between other CORBA implementations like Progress Orbix, Micro Focus Visibroker, etc.

    We will create a simple example of distributed application where the server-tier expose a service that takes a string as parameter and converts to the upper case mode. The client-tier will actually take an string from the command-line and send it as parameter to the server-tier for processing. The first thing to do is to create a folder that will be our application directory. Create a folder named MY_TUX_APP. After this, you need to define two environment variables:

          APPDIR=/home/riferrei/MY_TUX_APP

          TUXCONFIG=$APPDIR/tuxconfig

    These two environment variables are used in the deployment phase of the Oracle Tuxedo development, which means that they are applied to every application that are deployed. The other environment variables are defined only once and reused across different deployments. Enter in the $APPDIR directory. Let's start the development of the application by the server-tier layer. Create a file named serverApp.cbl and write the following COBOL code:

            IDENTIFICATION DIVISION.
            PROGRAM-ID. serverApp.
            AUTHOR. TUXEDO DEVELOPMENT.
            ENVIRONMENT DIVISION.
            CONFIGURATION SECTION.
            DATA DIVISION.
            WORKING-STORAGE SECTION.
    
            01  TPSVCRET-REC.
            COPY TPSVCRET.
    
            01  TPTYPE-REC.
            COPY TPTYPE.
    
            01 TPSTATUS-REC.
            COPY TPSTATUS.
    
            01  TPSVCDEF-REC.
            COPY TPSVCDEF.
    
            01  LOGMSG.
                    05  FILLER        PIC X(10) VALUE  
                            "server :".
                    05  LOGMSG-TEXT   PIC X(50).
            01  LOGMSG-LEN            PIC S9(9)  COMP-5.
    
            01 RECV-STRING            PIC X(100).
            01 SEND-STRING            PIC X(100).
    
            LINKAGE SECTION.
    
            PROCEDURE DIVISION.
    
           START-FUNDUPSR.
               MOVE LENGTH OF LOGMSG TO LOGMSG-LEN. 
               MOVE "Started" TO LOGMSG-TEXT.
               PERFORM DO-USERLOG. 
    
               MOVE LENGTH OF RECV-STRING TO LEN.
               CALL "TPSVCSTART" USING TPSVCDEF-REC 
                            TPTYPE-REC 
                            RECV-STRING
                            TPSTATUS-REC.      
    
               IF NOT TPOK
                    MOVE "TPSVCSTART Failed" TO LOGMSG-TEXT
                        PERFORM DO-USERLOG 
                    PERFORM EXIT-PROGRAM 
               END-IF.
    
               IF TPTRUNCATE 
                    MOVE "Data was truncated" TO LOGMSG-TEXT
                        PERFORM DO-USERLOG 
                    PERFORM EXIT-PROGRAM 
               END-IF.
    
               INSPECT RECV-STRING CONVERTING
               "abcdefghijklmnopqrstuvwxyz" TO
               "ABCDEFGHIJKLMNOPQRSTUVWXYZ".
               MOVE "Success" TO LOGMSG-TEXT.
               PERFORM DO-USERLOG.
    
               SET TPSUCCESS TO TRUE.
               COPY TPRETURN REPLACING 
                    DATA-REC BY RECV-STRING.
    
           DO-USERLOG.
               CALL "USERLOG" USING LOGMSG 
                    LOGMSG-LEN 
                    TPSTATUS-REC.
           EXIT-PROGRAM.
               MOVE "Failed" TO LOGMSG-TEXT.
               PERFORM DO-USERLOG.
               SET TPFAIL TO TRUE.
               COPY TPRETURN REPLACING 
                    DATA-REC BY RECV-STRING.
    

    This COBOL application receives a string parameters and converts to the upper case mode. You can see in the code that every interaction or "phase" are sent to a logger mechanism called user-log. This a interesting approach supported by Oracle Tuxedo that enable developers to debug "what is happening" in runtime. Functions like TPSVCSTART and USERLOG are included in the source code dynamically, are are part of the Oracle Tuxedo ATMI API. To compile this application and generate a native executable program, type the following command:

          buildserver -C -o serverApp -f serverApp.cbl -s serverApp

    An native executable program named serverApp will be generated in the current directory. Let's understand which each parameter of the buildserver command does. The "-C" parameter tells to Oracle Tuxedo that a COBOL compilation will be done. Without this parameter, Oracle Tuxedo assumes that a C/C++ compilation will occur. The "-o" parameter tells what will be the name of the native executable program. The "-f" parameter tells which source code must be compiled. Finally, the "-s" parameter tells what service will be published when this server get up and running.

    Let's create the client-tier application. Being in the same folder ($APPDIR), create a file named clientApp.cbl, and inside this file, write the following COBOL code:

            IDENTIFICATION DIVISION.
            PROGRAM-ID. clientApp.
            AUTHOR. TUXEDO DEVELOPMENT.
            ENVIRONMENT DIVISION.
            CONFIGURATION SECTION.
            SPECIAL-NAMES.
                SYSERR IS STANDARD-ERROR.
    
            DATA DIVISION.
            WORKING-STORAGE SECTION.
    
            01  PARM-CNT PIC 9(05).
    
            01  TPTYPE-REC. 
            COPY TPTYPE.
    
            01  TPSTATUS-REC. 
            COPY TPSTATUS.
    
            01  TPSVCDEF-REC. 
            COPY TPSVCDEF.
    
            01  TPINFDEF-REC VALUE LOW-VALUES.
            COPY TPINFDEF.
    
            01  LOGMSG.
                05  FILLER		PIC X(8) VALUE  "client:".
                05  LOGMSG-TEXT	PIC X(50).
            01  LOGMSG-LEN		PIC S9(9)  COMP-5.
    
            01  USER-DATA-REC	PIC X(75).
            01  SEND-STRING		PIC X(100) VALUE SPACES.
            01  RECV-STRING		PIC X(100) VALUE SPACES.
    
            PROCEDURE DIVISION.
            START-CSIMPCL.
              MOVE LENGTH OF LOGMSG TO LOGMSG-LEN. 
              ACCEPT PARM-CNT FROM ARGUMENT-NUMBER.
              IF PARM-CNT IS NOT EQUAL TO 1 THEN
                  DISPLAY "Usage: clientApp String"
                  STOP RUN
              END-IF.
    
              ACCEPT SEND-STRING FROM ARGUMENT-VALUE.
              DISPLAY "SEND-STRING:" SEND-STRING.
          
              MOVE "Started" TO LOGMSG-TEXT.
              PERFORM DO-USERLOG.
          
              PERFORM DO-TPINIT. 
              PERFORM DO-TPCALL. 
              DISPLAY "RECV-STRING:" RECV-STRING.
              PERFORM DO-TPTERM. 
              PERFORM EXIT-PROGRAM. 
          
            DO-TPINIT.
              MOVE SPACES TO USRNAME.
              MOVE SPACES TO CLTNAME.
              MOVE SPACES TO PASSWD.
              MOVE SPACES TO GRPNAME.
              MOVE ZERO TO DATALEN.
              SET TPU-DIP TO TRUE.
    
              CALL "TPINITIALIZE" USING TPINFDEF-REC 
                    USER-DATA-REC 
                    TPSTATUS-REC.      
          
              IF NOT TPOK
                    MOVE "TPINITIALIZE Failed" TO LOGMSG-TEXT
                    PERFORM DO-USERLOG
                    PERFORM EXIT-PROGRAM
              END-IF.
          
            DO-TPCALL.
              MOVE 100 TO LEN.
              MOVE "STRING" TO REC-TYPE.
          
              MOVE "serverApp" TO SERVICE-NAME.
              SET TPBLOCK TO TRUE.
              SET TPNOTRAN TO TRUE.
              SET TPNOTIME TO TRUE.
              SET TPSIGRSTRT TO TRUE.
              SET TPCHANGE TO TRUE.
           
              CALL "TPCALL" USING TPSVCDEF-REC 
                    TPTYPE-REC 
                    SEND-STRING
                    TPTYPE-REC 
                    RECV-STRING
                    TPSTATUS-REC. 
          
              IF NOT TPOK
                    MOVE "TPCALL Failed" TO LOGMSG-TEXT
                    PERFORM DO-USERLOG 
              END-IF.
          
            DO-TPTERM.
              CALL "TPTERM" USING TPSTATUS-REC.      
              IF  NOT TPOK
                    MOVE "TPTERM Failed" TO LOGMSG-TEXT
                    PERFORM DO-USERLOG
              END-IF.
          
            DO-USERLOG.
              CALL "USERLOG" USING LOGMSG 
                    LOGMSG-LEN 
                    TPSTATUS-REC.
          
            EXIT-PROGRAM.
              MOVE "Ended" TO LOGMSG-TEXT.
              PERFORM DO-USERLOG.
              STOP RUN.
    

    Let's compile this client-tier application. To do this, just type the following command:

          buildclient -C -o clientApp -f clientApp.cbl

    As you can see, the parameters used in the compilation are the same that we used in the compilation of the server-tier, with the exception of the parameter "-s" in which of the client-tier are unnecessary. At this point, you should have two executable applications in the directory, one name clientApp and another named serverApp. To start the deployment phase of this application, it is necessary to create the configuration file for it. This configuration file are called UBBCONFIG file. The UBBCONFIG file acts as the external configuration descriptor that defines the application. If you are familiar with Java EE development, think in this configuration file as a deployment descriptor. In the directory of the application, create a file named ubbConfig and edit this file as showed in the listing below:

    *RESOURCES
    IPCKEY	123459
    
    DOMAINID	ubbConfig
    MASTER		simple
    MAXACCESSERS	5
    MAXSERVERS	5
    MAXSERVICES	10
    MODEL		SHM
    LDBAL		N
    
    *MACHINES
    DEFAULT:
    
    APPDIR="/home/riferrei/MY_TUX_APP"
    TUXCONFIG="/home/riferrei/MY_TUX_APP/tuxconfig"
    TUXDIR="/home/riferrei/oracle/tuxedo11gR1"
    
    riferrei_linux_64	LMID=simple
    
    *GROUPS
    GROUP1
    	LMID=simple	GRPNO=1	OPENINFO=NONE
    
    *SERVERS
    DEFAULT:
    		CLOPT="-A"
    
    serverApp	SRVGRP=GROUP1 SRVID=1
    
    *SERVICES
    serverApp
    

    There are one section of this configuration file that you must change to get the example working, the "MACHINES-DEFAULT" section. Change the variables "APPDIR", "TUXCONFIG" and "TUXDIR" to reflect your file system format and directories location. You also need to change the hostname of the server. As you can see in the UBBCONFIG file, there is a mapping between the machine "riferrei_linux_64" to the element "LMID=simple". Change the hostname to reflect the hostname of your machine. I have found some problems when I used hostnames with special characters like "-" or ".". If some problems occurs in the UBBCONFIG loading, maybe this could be the cause.

    Deploying and Testing the Application in Oracle Tuxedo

    It is time to deploy and test the application. Every application in Oracle Tuxedo must have a binary version of their UBBCONFIG file. To generate this binary version, Oracle Tuxedo gives you a very simple utility program named tmloadcf. Being in the application directory of the application, type the following command:

         tmloadcf ubbConfig

    A binary version of the UBBCONFIG file named tuxconfig will be generated in the current directory. Now we are all set. Let's start the deployment of the application and begin the tests. To deploy the application and start the server service, type the following command:

         tmboot -y

    You should see in the console that a few messages will appear. Which these messages basically says is about the correct execution of the admin and server services, which means that they will be ready to accept client requests and ready to process the incoming messages. Type the following command:

         ./clientApp "Oracle Tuxedo"

    With this command, we actually execute the client-tier application and we pass a string from the command line parameters. After type the command above, you should see in the console the following output:

         SEND-STRING:Oracle Tuxedo
         RECV-STRING:ORACLE TUXEDO

    This means that our server-tier application received the message and passed to the serverApp service. The service on the other hand transformed the string passed as parameter to the upper case format and sent back to the caller application. In the "middle" of these two applications, Oracle Tuxedo did take care of the messaging and transaction execution. To shutdown the services and the server application, type the following command:

         tmshutdown

    I have recorded a video that capture the entire application development since the compilation process. If you feel that some step was not done correctly, run the video below to follow step by step the sequence of commands needed to compile, deploy and test the application created at this article.


    Conclusion

    This article gave you an overview about Oracle Tuxedo e how it can be used to create distributed applications using the COBOL programming language. It showed how to set up a development environment that enable COBOL developers to build a simple but complete application in Oracle Tuxedo. I hope that this article could help you and your team to explore the features that only Oracle Tuxedo offers.

    About

    Ricardo Ferreira is just a regular person that lives in Brazil and is passionate for technology, movies and his whole family. Currently is working at Oracle Corporation.

    Search

    Categories
    Archives
    « April 2014
    SunMonTueWedThuFriSat
      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
       
           
    Today