Persistence and transactions

Drools is usually used in situations where persisting its contents is somewhat impractical. Either we want to execute rules as quickly as possible, and don't want the overhead of having to persist on a database, or we want to keep as much information as possible from the session in memory, so we can reuse it rapidly. The situation with jBPM is a bit different, because we are going to need to use the Kie Session to keep track of the immediate (automatic) steps of a process; then we might have long wait periods while a task is completed or until a signal is received. This scenario implies a sizable need to release resources when not being used; for that, the Kie Session provides persistence mechanisms.

In the following sub sections, we're going to see how Drools and jBPM provide a series of storing mechanisms to release resources from memory and place them into persistence. We will explore:

  • Different persistence configurations we can use
  • How transactions are managed and demarcated
  • How we can use different strategies to customize how the data is persisted

How is state persisted?

As we have already seen, each process instance can interact with a lot of different things within a Kie Session: rules, other processes, external systems, and so on. The best way to keep Kie Session state properly persisted is to wrap every method we have in the Kie Session with a persistence mechanism. To do so, Drools relies on the command-pattern to create a command-based implementation of the Kie Session.

The command-pattern is a way of encapsulating a method call as a specific object; instead of doing multiple different method calls, you always end up doing the same call to the "execute" with a different command parameter, as introduced in Chapter 5, Understanding KIE Sessions. This means that, for each method of the Kie Session (insert, fireAllRules, and so on), there is an equivalent command object (InsertObjectCommand, FireAllRulesCommand, and so on) that will be passed to a single "execute" method.

The command-pattern's main purpose is to wrap the call of the execute method with anything we need. In our case, the execute method (being internally used for any operation done on the Kie session) will be preceded by a transaction initialization, and followed by a commit or a rollback depending on the success of the operation. The following diagram shows how the command-pattern is implemented for the Kie Session:

How is state persisted?

This class structure allows Drools to provide a way of wrapping every call in some extra operations, defined in the SingleSessionComandService class. We can see how they operate in the following sequence diagram:

How is state persisted?

As you can see, instead of directly implementing a startProcess method, jBPM provides a StartProcessCommand, which does the same operation but lets the engine wrap the call in a transaction. In this same manner, each operation has its corresponding command in Drools.

After this pattern is implemented, the Kie Session will have a place to start and finish a transaction for each call done into it, but it will still need information about how to do it (which transaction manager to use, what type of persistence to use for persisting objects, and a few other configurations). Currently there are two persistence implementations available in Drools and jBPM off-the-shelf: JPA and Infinispan. Even though JPA is the recommended one to use because it is the most maintained of the two, we will explore each in some detail to understand the hookup points for Drools and jBPM persistence by comparison. Drools and jBPM also allow us to write our own persistence layer—for example, if we want to persist the state of the Kie Session using any other storage mechanism.

JPA implementation

This is the default suggested implementation for Drools persistence. It relies on the Java Persistence API to store all the content of the Kie Session, its Process instances, and extra info in tables in a relational database. It relies on a few configuration elements:

  • A persistence.xml configuration file in the META-INF classpath folder
  • Drools and jBPM persistence JPA dependencies in the class path
  • An EntityManagerFactory (from JPA) passed to the Kie Session initialization

We can see the persistence.xml with minimal requirements in the chapter-10-persistence-jpa project in the source bundle. It contains all the initial classes needed to persist Kie Sessions and process instances. The main classes to pay attention to are SessionInfo (which persists the Kie Session data), ProcessInstanceInfo (which persists the process instance data), and WorkItemInfo (which persists the work items under execution). Each of these tables only persists information relevant to recreating the same state in a different thread depending only on the database, so most of the information is just binary data stored in a blob. Remember that every time we invoke a method on the Kie Session, every piece of data in it will be persisted, so performance is key to this operation.

Once we have the right persistence.xml, we need to add two important dependencies into the classpath by adding these two XML blocks into our pom.xml:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-persistence-jpa</artifactId>
    <version>6.3.0.Final</version>
</dependency>
<dependency>
    <groupId>org.jbpm</groupId>
    <artifactId>jbpm-persistence-jpa</artifactId>
    <version>6.3.0.Final</version>
</dependency>

These dependencies will add the needed components in our application to start working with persistent Kie Sessions. Also, we will need to add all the relevant dependencies for the JPA implementation we want to use. Drools and jBPM Persistence modules don't enforce any specific JPA implementation to make it as adjustable as possible to the client's needs, though the JPA version targeted by the provided configuration examples is JPA 2.0. In our chapter-10-persistence-jpa/pom.xml file, we have hibernate dependencies, a Bitronix Transaction Manager (org.codehaus.btm:btm:2.1.4), and an H2 database (com.h2database:h2:1.3.168) we use for testing.

After we have these components, the next thing to do is get into our code. Most of the change is done to the way we obtain the Kie Session. After the Kie Session is created, we can use it like any other Kie Session object we've used before. In chapter-10-persistence-jpa, there is a test called PersistentProcessTest where we can see this initialization in detail. This test is based on the "process order" business process we've seen earlier, and relies on special database access methods to create and load Kie Sessions into and from the database, respectively. Let's see the block of code needed to create a new persistent Kie Session:

KieServices ks = KieServices.Factory.get();
KieBase kieBase = ...KieSessionConfiguration kieSessionConf = ...Environment env = EnvironmentFactory.newEnvironment();
EntityManagerFactory emf = Persistence.createEntityManagerFactory("org.jbpm.persistence.jpa");env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, emf);
...
KieStoreServices kstore = ks.getStoreServices();
KieSession ksession = kstore.newKieSession(env, kSessionConf, kieBase);

The first few methods are used to create a Kie Base, an optional Kie Session configuration bean, and an environment variable. The environment variable will be a collection of elements needed to initialize the Kie Session. The one required element is this entity manager factory from JPA, which references the persistence unit defined in our persistence.xml file. It can also have other parameters; we will explore these in detail later on.

For loading an existing Kie Session, there is another method:

KieSession oldKieSession = Long id = oldKieSession.getIdentifier();
KieSession reloadedKieSession = kstore.loadKieSession(id, env, kSessionConf, kieBase);

The loadKieSession method lets us obtain an existing Kie Session from the database. The parameters are the same as for creating a Kie Session, plus an ID we can obtain from a persistent Kie Session. The Kie Base needs to be a parameter when we create or load the Kie Session because, in order to make serialization/deserialization of the Kie Session as fast as possible, only runtime data is stored in the database.

After we've restored our Kie Session, we can use it like any other Kie Session we have just created. We will be able to register listeners, work item handlers, globals, and channels. This will have to be done also to load the Kie Session, since listeners, work item managers, channels, and global variable references are not persisted in the database.

Infinispan implementation

Infinispan (http://infinispan.org/) is an open source data grid framework and platform. It provides a way to store information so that it is both distributed and easy to access, using a NoSQL (Not-Only-SQL) document structure. In a way that is very similar to how Drools and jBPM provide a persistence mechanism for JPA, it also provides a mechanism for working with Infinispan as storage for our Kie Sessions and process instances. This implementation, as with JPA, depends on three things:

  • An infinispan.xml configuration file in the classpath
  • Drools and jBPM persistence Infinispan dependencies in the class path
  • A DefaultCacheManager (from Infinispan) passed to the Kie Session initialization

An example of the infinispan.xml is provided in the chapter-10/chapter-10-persistence-inf project in the code bundle. We won't go into too much detail about Infinispan configuration, but will just mention that the internal entity that will hold all the information for this storage is called EntityHolder. It will store Kie Sessions and process instance data in a Base64-encoded string representing the binary data of the serialized runtime.

As for the dependencies, we will need to add three main dependencies for our Infinispan persistence run:

<dependency>
    <groupId>org.kie</groupId>
    <artifactId>drools-infinispan-persistence</artifactId>
    <version>6.3.0.Final</version>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>jbpm-infinispan-persistence</artifactId>
    <version>6.3.0.Final</version>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>jbpm-infinispan-persistence</artifactId>
    <version>6.3.0.Final</version>
    <type>test-jar</type>
</dependency>

The third one is related to using Bitronix as the transaction manager, but it is not enforced by the implementation.

The code we will use for using our persistent Kie Session is very similar to the one we saw for JPA. We will have two calls we can make (one for creating a persistent Kie Session, and one for loading it back from the storage), but the calls will be done through a helper class called InfinispanKnowledgeService, and the environment variable will hold a DefaultCacheManager instead of an EntityManagerFactory. Let's examine an example of the code for the creation of a persistent Kie Session:

KieServices ks = KieServices.Factory.get();
KieBase kieBase = KieSessionConfiguration kieSessionConf = Environment env = EnvironmentFactory.newEnvironment();
DefaultCacheManager cm = new DefaultCacheManager("infinispan.xml");
env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, cm);
...
KieSession ksession = InifnispanKnowledgeService.newStatefulKnowledgeSession(env, kSessionConf, kieBase);

The previous code will register a DefaultCacheManager using the same key the JPA implementation used to register an entity manager factory. After that is provided, along with a Kie Base and an optional Kie Session configuration, we will use the newStatefulKnowledgeSession method of the helper class to obtain an Infinispan persisted Kie Session.

Here's the code for loading a persistent Kie Session:

KieSession oldKieSession = Long id = oldKieSession.getIdentifier();
KieSession reloadedKieSession = InfinispanKnowledgeService.loadStatefulKnowledgeSession(      id, env, kSessionConf, kieBase);

The Infinispan implementation is a valid alternative to the JPA implementation, but it is relatively new and very few people are currently using it. Use it with caution, as it is not currently recommended for production environments.

Extending persisted data

Now that we've seen the different flavors of persistent Kie Sessions, and what information is stored, we should note a few important restrictions:

  • Only binary data is stored, so querying from outside the engine would be difficult
  • The model is somewhat fixed, so reusing possibly existing persistence mechanisms and relating them to our persistent Kie Session is complicated

To solve these issues, there are a few tricks and tools we can use. The first one we will discuss is event listeners.

Event listeners can be registered in the Kie Session to notify external components about changes in the agenda, the working memory, and the process instance state. When using a persistent Kie Session, the calls inside event listeners are executed inside the same transaction that wraps each operation of the Kie Session, so we can extend items that are persisted from the Kie Session in these methods.

Actually, this is something that the Kie Session already does out of the box. There is an AuditLoggerFactory class that we can use to build event listeners that can export more data to other tables (specifically, ProcessInstanceLog, NodeInstanceLog, and VariableInstanceLog tables). This audit logger can be found in the jbpm-audit dependency and can be added to our Kie Session with the following code:

ksession.addEventListener(AuditLoggerFactory.newJPAInstance(environment));

Tip

When using persistent Kie Sessions, process instance information is only kept in memory while the processes are referenced and active. Using this audit logger is the only way to actually query for a completed process instance. Refer to the end of the test in PersistentProcessTest to see the audit log service for info retrieval.

The other type of persistence extension mechanism provided by the Kie Session is called object marshaling strategies. They give the Kie Session a special way to map certain types of objects in and out of the database.

By default, all entities inserted into the persistent Kie Session will be persisted serialized into the binary blob of data for the Kie Session. This is done in this way because the default object marshaling strategy used by the persistent Kie Session is prepared to take any object in the working memory and serialize/deserialize it on read or write operations from the database.

However, we can define more types of object marshaling strategy, and the Kie Session environment allows us to define more than one strategy at the same time. Let's take a look at how the environment variable can hold information about object marshaling strategies:

Environment env = EnvironmentFactory.newEnvironment();env.set( EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, new ObjectMarshallingStrategy[] {
    new JPAPlaceholderResolverStrategy(emf),new SerializablePlaceholderResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT)});

In the previous code, we set two object marshaling strategies. As we can see, they are passed to the environment as an array of ObjectMarshallingStrategy objects. Each one of its elements will define an accept method (to decide if an object should be serialized or deserialized using this strategy). The engine will take this array of strategies and, for each one in the presented order, it will test whether it accepts each object that has to be written into/read from the database. If it doesn't, the engine will try with the next available strategy.

Specifically in this case, we're using two out of the box implementations of this type of strategy. The first strategy will try to read objects from a JPA-based entity. If an object is not a JPA entity, it will not accept it. For those cases, it will store the data entirely inside the binary blob of the SessionInfo table, by using the serialization strategy.

We can find a more detailed example of how these strategies interact with the Kie Session in the OMSTest file in the chapter-10/chapter-10-persistence-jpa project.

Transaction management

Transactions are managed internally by the Kie Session, to guarantee that, whenever we call an operation inside the Kie Session, we're inside one transaction. This means any call to persistence mechanisms inside event listeners, work item handlers, and any possible interaction component, should take into account joining an existing transaction when we implement them.

Transactions will be searched in the JNDI context of the application if not provided explicitly. This makes the configuration easy in most application servers. However, to create a test, if we want to specify a transaction manager, user transaction, or transaction synchronization registry, we can do so through the Environment bean we use to create or load the Kie Session:

Environment env = EnvironmentFactory.newEnvironment();
env.set(EnvironmentName.TRANSACTION, myUserTransaction);
env.set(EnvironmentName.TRANSACTION_MANAGER, transManager);
env.set(EnvironmentName.TRANSACTION_SYNCHRONIZATION_REGISTRY, transSynchronizationRegistry);

All of these components are optional. You can see an example of how this is used to configure a Bitronix Transaction Manager instance inside PersistentProcessTest in the chapter-10-persistence-jpa project.

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

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