Chapter 7. Defining Your Environment with the Runtime Manager

By now, we have seen the two main components of a jBPM6 runtime, the KIE session and the Human task service. These components are provided as the basic components needed to run process interactions between humans and systems. As the responsibilities of the process runtime grows, connectors to external systems should be added to transform the process engine into a highly configurable enterprise integration platform, where coordination between different systems can be easily managed in a business-friendly way.

The entire configuration involved in creating a runtime for the process engine, the Human task component, and all the external systems connectors could become cumbersome if not managed in a single place. In this chapter, we will learn about the runtime manager component, which is designed to create a bridge between all the components and configurations involved in a process execution. This component can be used to create a full runtime for the process engine. For this chapter, our goals will be as follows:

  • Understand the role of the runtime manager in the jBPM6 architecture
  • Get familiar with the available runtime managers and interfaces
  • Start extending the API to create our own custom runtime managers

Understanding the role of the runtime manager

The runtime manager's main role in an application is to provide an application-wide point to access process runtimes (all the necessary interconnected services to execute a process execution within a specific domain). The nature of those runtimes and how many different runtimes can exist in a particular domain is a decision that each of the runtime manager implementations available has to make. By default, a runtime will be composed of two main components: the KIE session where the process is executed and the Human task component where human tasks will be handled. Both are grouped and returned from the runtime manager through the RuntimeEngine interface:

public interface RuntimeManager {
    RuntimeEngine getRuntimeEngine(Context<?> context);
    void disposeRuntimeEngine(RuntimeEngine engine);
    String getIdentifier();
    void close();
}

public interface RuntimeEngine {
    KieSession getKieSession();
    TaskService getTaskService();
}

Runtime managers will return a specific number of runtime engines, depending on their specific nature. There is a singleton implementation that will return a single session for all required runtimes in an application, no matter how many you ask. On the other hand, there is a runtime manager for each process instance that will provide a separate session for each process instance started in the runtime. The following diagram shows how the classes involved in the runtime manager component interact with each other:

Understanding the role of the runtime manager

Determining what type of runtime manager we should use for our environment is a complex decision that involves an equilibrium between synchronization of operations and the number of concurrent calls that can be performed by a BPM environment at the same time. We will try to explain the advantages and disadvantages of each type of runtime manager as we explain them. Regarding the architectural decisions on which runtime manager to use, we will see a few considerations to help us in Chapter 10, Integrating KIE Workbench with External Systems.

The nature of the KIE sessions will impact the amount of information shared between process instances and how recoverable that information will be by other threads later on. The KIE sessions have a memory group called working memory, which groups a set of objects that will be evaluated by business rules. If you feed information to this working memory from the process instance or decide to fire rules during the process execution, depending on the level of isolation of the KIE session, the working memory might contain objects from a single process instance or many.

Rules evaluate this working memory to find patterns between different objects. Depending on what a business rule states, you might want to match different objects that come from different process instances, or isolate them on the instance level/request level of each process. For these cases, the runtime manager provides different alternatives to access a process instance in different session environments.

Understanding the runtime environment

Runtime managers provide you with a strategy to access different levels of isolation for your runtime, but it depends on another component to define all the configurations that allow the runtime to run in a specific way. The component that allows you to define these configurations is called the runtime environment, which is a grouping of all the different elements required for the process runtime. In the runtime environment, you can configure several things:

  • Whether your engine will use persistence (we will see persistence configurations in detail in Chapter 8, Implementing Persistence and Transactions) or if it will run in memory only
  • The knowledge definitions you will use in the runtime
  • The specific user group callback object to connect the Human task component to a user and groups data source

The following code section defines the RuntimeEnvironment interface:

public interface RuntimeEnvironment {
    KieBase getKieBase();
    Environment getEnvironment();
    KieSessionConfiguration getConfiguration();
    boolean usePersistence();
    RegisterableItemsFactory getRegisterableItemsFactory();
    UserGroupCallback getUserGroupCallback();
    ClassLoader getClassLoader();
    void close();
}

As you can see, there are methods to provide all the different components involved in providing the necessary information to the runtime to run correctly, including the KIE session configuration properties, class loaders, and runtime connectors, which are defined inside the registerable items factory. This is explained in the next section.

Registerable items factory

The runtime environment has a particular subcomponent that takes care of registering all the different connectors to external systems. This subcomponent is called RegisterableItemsFactory, and it provides a contract for the process engine to populate its work item handlers and different listener types for process and rule executions:

public interface RegisterableItemsFactory {
  Map<String, WorkItemHandler> getWorkItemHandlers(
            RuntimeEngine runtime);
  List<ProcessEventListener> getProcessEventListeners(
            RuntimeEngine runtime);
  List<AgendaEventListener> getAgendaEventListeners(
            RuntimeEngine runtime);
  List<WorkingMemoryEventListener> getWorkingMemoryEventListeners(
            RuntimeEngine runtime);
}

As you might have noticed already, external connectors can be implemented in a bidirectional way—both to expose the internal functionality of the engine to the rest of the application components and to configure signaling methods from external components back into the engine. It is because of this reason that listeners and work item handlers are provided with a reference to the runtime engine, so you can pass it on to your own connectors whenever they might need them.

Defining our runtime environment

Another thing to notice is that all these interfaces (except for RuntimeManager) define just getters for different components that the runtime will need at specific instances of rules and processes execution paths. You can implement your own to return the different implementations you wish as you see fit. However, the jbpm-runtime-manager project provides two implementations: SimpleRuntimeEnvironment and DefaultRuntimeEnvironment.

The SimpleRuntimeEnvironment implementation provides setters as well as getters for all the configuration components, or enough setters to create the configuration components based on simpler parameters. The DefaultRuntimeEnvironment instance extends the first implementation to add the most usual configurations as a default template. This default configuration will provide a persistent environment for sessions to be created, with history logs and the Human task component preconfigured.

Since persistence will be covered in detail in Chapter 8, Implementing Persistence and Transactions, we will save it for later use and just focus on working with SimpleRuntimeEnvironment for now. However, in order to simplify the code a little, we will not use it directly, but through a builder class called RuntimeEnvironmentBuilder. In the following code snippet, we can see how this builder class will let us initialize a RuntimeEnvironment instance really fast:

SimpleRegisterableItemsFactory factory = 
        new SimpleRegisterableItemsFactory();
factory.addWorkItemHandler("Human Task", MyWIHandler.class);
RuntimeEnvironment environment = RuntimeEnvironmentBuilder
        .Factory.get().newEmptyBuilder()
        .userGroupCallback(userGroupCallback)
        .knowledgeBase(kbase)
        .registerableItemsFactory(factory)
        .get();

As you can see in the previous code, we can provide our own implementations of different components to a RuntimeEnvironment instance really fast without creating multiple lines of code to invoke each of the setters provided by a specific implementation.

You might have also noticed the SimpleRegisterableItemsFactory implementation that we used to register a work item handler type (by a class name) to its correspondent key. While the API provides two different implementations of the RuntimeEnvironment interface, it also provides several different implementations or the RegisterableItemsFactory implementation that we can use in different situations. The different implementations available are:

  • SimpleRegisterableItemsFactory: This implementation defines type setters for work item handlers and listeners. When a runtime engine asks for the implementations, it will try to build the different handlers. It will create each of the specified types looking for a constructor with a KieSession parameter, a TaskService parameter, a RuntimeEngine parameter, or no parameter at all.
  • DefaultRegisterableItemsFactory: This implementation extends the previous one to provide default listeners and work item handlers. Particularly, it will create a Human task component work item handler and listeners to trigger rules from processes and keep history logs in a persistent environment.
  • KModuleRegisterableItemsFactory: This implementation extends the previous one to add any configuration defined in the kmodule.xml file of a specific KieModule object. It is constructed with a KieContainer reference and a session name from which to obtain the correspondent mappings.
  • InjectableRegisterableItemsFactory: This implementation extends the default implementation to discover the different configuration components from CDI injection.

Now that we understand how the environment for a runtime manager is configured, and before we start working with the actual RuntimeEngine implementations, we need to understand how the lifecycle of the runtime is expected to be executed.

Runtime lifecycle management

We have already reviewed the idea behind the runtime manager and the runtime engine to understand their purpose. The runtime engine is a grouping of all the running configurations connected together that are provided by the runtime manager. We now need to understand how these components reside in the server and at what times they must be created or discarded. First of all, let's discuss the runtime manager's lifecycle.

The runtime manager usually behaves like a singleton in an application; we just need one instance of it. From that one instance, we will construct one or many runtime engines. So, it is important that even if we create many different runtime managers, they all behave in the same manner; otherwise, we might have different behaviors depending on how many runtime managers we have.

In the next section, we will see how different implementations of the runtime manager interface provide solutions for this problem. Whichever implementation we choose, the behavior against the obtained RuntimeEngine instance is the same as explained in the following sequence diagram:

Runtime lifecycle management

The RuntimeManager implementation will return a specific runtime engine, depending on the RuntimeManager type and the Context parameter we pass to it:

RuntimeManager manager = ...;
RuntimeEngine engine = manager.getRuntimeEngine(
        EmtpyContext.get());

Once we receive a RuntimeEngine instance, we will be able to interact with its subcomponents using the following options:

  • We can use the KIE session to start processes, fire rules, and send signals. The code is as follows:
    engine.getKieSession().startProcess("procId");
    engine.getKieSession().fireAllRules();
    engine.getKieSession().signalEvent("signalX", null);
  • We can use the task service to interact with human tasks. The code is as follows:
    List<TaskSummary> tasks = engine.getTaskService().
            getTasksOwned("john", "en-UK");
    Long taskId = tasks.iterator().next().getId()
    engine.getTaskService().start(taskId, "john");

The RuntimeEngine interface could also be extended to add other interactions if necessary for a particular domain.

When we're done using RuntimeEngine in the current transaction or thread, we let RuntimeManager know whether it can try to free resources by calling the disposeRuntimeEngine method:

manager.disposeRuntimeEngine(engine);

Once we do that, the engine instance will be rendered unusable. If we want to interact with the processes or tasks, we will need to get another instance of RuntimeEngine.

Now that we understand the lifecycle of both the RuntimeManager and RuntimeEngine instances, we can discuss the different types of runtime managers available for use in the jBPM6 code base.

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

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