This is the 14th day of my participation in the More text Challenge. For more details, see more text Challenge

Activiti – Crystalball profile

  • Activiti-CrystalballCrystalBall is a simulation engine for the Activiti business process management platform.CrystalBallYou can use the user simulation process scenario:
    • Decision support: for production processes, such as whether more data should be added to the system to meet deadlines
    • Optimization and validation: Test changes and verify impact
    • Training: The simulator can be used to train employees before use
  • CrystalBall is independent:
    • There is no need to create a separate simulation model and engine
    • There is no need to create a different report for the simulation
    • You don’t need a lot of data for the simulation engine
  • The CrystalBall simulator is based on Activiti:
    • Easy to copy data
    • Start simulator
    • Replay process behavior from history

Inside the CrystalBall

  • CrystalBall is a discrete event simulator
  • An implementation is CrystalBall org. Activiti. CrystalBall. Simulator. SimpleSimulationRun:
init();

    SimulationEvent event = removeSimulationEvent();

    while(! simulationEnd(event)) { executeEvent(event); event = removeSimulationEvent(); } close();Copy the code
  • SimulationRun can execute simulated events generated by different sources

Historical analysis

  • One of the use cases the simulator can use is to analyze history
  • The production environment does not provide any opportunity to repeat and debug bugs, which is why it is almost impossible to restore the process engine to exactly the same state as when a production problem occurred. The reasons are as follows:
    • Timing: Process instances may be executed for several months
    • Concurrency: Process instances run together with other instances, and problems may arise only if they are executed concurrently
    • Users: Many users can participate in process instances, and process instances can affect the state in question
  • Simulators can better expose the above problems:
    • The simulation process is virtual and does not rely on the real environment
    • The Activiti process engine itself is virtual and there is no need to create a virtual process engine to serve as a simulation environment
    • Concurrent scenarios are also native
    • User behavior is logged and can be reproduced, predicted and generated as needed
  • The best way to analyze history is to reproduce it. Real life is hard to reproduce, but simulators can do it

Historical event

  • The most important thing in reproducing history is to record the events that affected the states
  • Processes are driven by user events and there are two event sources available:
    • Process instances: Only raw Activiti-Crystalball projects are supported
    • ActivitiEvent Log: You can add an ActivitiEventListener to the engine that you want to log. Event logs can be saved for later analysis
  • A basic implementation: ActivitiEventListener org. Activiti. Crystalball. Simulator. Delegate. Event. Impl. InMemoryRecordActivitiEventListener
 @Override
  public void onEvent(ActivitiEvent event) {
    Collection<SimulationEvent> simulationEvents = transform(event);
    store(simulationEvents);
  }
Copy the code
  • Events are saved and history can be recreated

The playback

  • The nice thing about playback is you can play it over and over again until you fully understand what’s going on
  • The Crystalball simulator is based on real data, real user behavior

  • Example:The best way to understand how playback works is to explain it step by step
    • Based on JUnit test cases: org. Activiti. Crystalball. Simulator. Delegate. Event. PlaybackRunTest
<process id="theSimplestProcess" name="Without task Process">
    <documentation>This is a process for testing purposes</documentation>

    <startEvent id="theStart"/>
    <sequenceFlow id="flow1" sourceRef="theStart" targetRef="theEnd"/>
    <endEvent id="theEnd"/>

  </process>
Copy the code

Process publishing, which can be used for real and simulated runs:

  • Record events
// get process engine with record listener to log events
  ProcessEngine processEngine = (new RecordableProcessEngineFactory(THE_SIMPLEST_PROCESS, listener))
  .getObject();

  // start process instance with variables
  Map<String,Object> variables = new HashMap<String, Object>();
  variables.put(TEST_VARIABLE, TEST_VALUE);
  processEngine.getRuntimeService().startProcessInstanceByKey(SIMPLEST_PROCESS, BUSINESS_KEY,variables);

  // check process engine status - there should be one process instance in the history
  checkStatus(processEngine);

  // close and destroy process engine
  EventRecorderTestUtils.closeProcessEngine(processEngine, listener);
  ProcessEngines.destroy();
Copy the code

After the startProcessInstanceByKey method calls, record ActivitiEventType. ENTITY_CREATED

  • Start the simulation run:
final SimpleSimulationRun.Builder builder = new SimpleSimulationRun.Builder();
  // init simulation run
  // get process engine factory - the only difference from RecordableProcessEngineFactory that log listener is not added
  DefaultSimulationProcessEngineFactory simulationProcessEngineFactory = new DefaultSimulationProcessEngineFactory(THE_SIMPLEST_PROCESS);
  // configure simulation run
  builder.processEngine(simulationProcessEngineFactory)
         // set playback event calendar from recorded events
         .eventCalendar(new PlaybackEventCalendarFactory(new SimulationEventComparator(), listener.getSimulationEvents()))
         // set handlers for simulation events
         .customEventHandlerMap(EventRecorderTestUtils.getHandlers());
  SimpleSimulationRun simRun = builder.build();

  simRun.execute(new NoExecutionVariableScope());

  // check the status - the same method which was used in record events method
  checkStatus(simulationProcessEngineFactory.getObject());

  // close and destroy process engine
  simRun.getProcessEngine().close();
  ProcessEngines.destroy();
Copy the code

  • Other examples in the org. Activiti. Crystalball. Simulator. Delegate. Event. PlaybackProcessStartTest

Debug the process Engine

  • Playback limits all simulated events to be performed once
  • The debugger allows process events to break themselves up into smaller steps, observing the state of the process engine between the steps
    • SimpleSimulationRun implements the SimulationDebugger interface.SimulationDebugger can execute simulated events step by step, and can simulate execution at specific times:
 /** * Allows to run simulation in debug mode */
  public interface SimulationDebugger {
  /**
  * initialize simulation run
  * @param execution - variable scope to transfer variables from and to simulation run
  */
  void init(VariableScope execution);

  /** * step one simulation event forward */
  void step(a);

  /** * continue in the simulation run */
  void runContinue(a);

  /** * execute simulation run till simulationTime */
  void runTo(long simulationTime);

  /** * execute simulation run till simulation event of the specific type */
  void runTo(String simulationEventType);

  /** * close simulation run */
  void close(a);
  }
Copy the code
  • Execute SimpleSimulationRunTest to watch the process engine debugger in action

The replay

  • Playback requires creating another process engine instance to simulate the environment configuration
  • Replay works on a real process engine, and replay performs simulated events in a running process engine:
    • The conclusion is that the replay runs in real time, which means it is executed immediately **

Replay a process instance example: ReplyRunTest

  • Part ONE: Initialize the process engine, start a process instance, and complete the process instance’s tasks
ProcessEngine processEngine = initProcessEngine();

  TaskService taskService = processEngine.getTaskService();
  RuntimeService runtimeService = processEngine.getRuntimeService();

  Map<String, Object> variables = new HashMap<String, Object>();
  variables.put(TEST_VARIABLE, TEST_VALUE);
  ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(USERTASK_PROCESS, BUSINESS_KEY,
  variables);

  Task task = taskService.createTaskQuery().taskDefinitionKey("userTask").singleResult();
  TimeUnit.MILLISECONDS.sleep(50);
  taskService.complete(task.getId());
Copy the code

Use the process engine is the foundation of InMemoryStandaloneProcessEngine: Activiti configuration InMemoryRecordActivitiEventListener (record events, and converted to analog event) and UserTaskExecutionListener (when creating new user tasks, a new task will replay process instance, finish the task on the events The calendar)

  • The second part: Starts the mock debugger on the same engine engine as the original process
    • Use StartReplayProcessEventHandler replace StartProcessEventHandler rebroadcast event handler
    • StartReplayProcessEventHandler acquisition process instance Id to replay, in the initial position processing process instance startup
    • StartProcessEventHandler At the start, a new process instance is created, containing a variable. A variable called _replay processInstanceId. The variable is used to hold the replayed process instance Id
    • withSimpleSimulationRundifferent,ReplaySimulationRun:
      • Process engine instances are not created and closed
      • The simulation time is not modified
final SimulationDebugger simRun = new ReplaySimulationRun(processEngine,
getReplayHandlers(processInstance.getId()));
Copy the code
  • Start replay process instance:
    • Initially, there are no running process instances
    • There is only one completed process instance in history
    • After initialization, a mock event is added to the event calendar – used to start the process instance and replay the completed process instance
 simRun.init();

  // original process is finished - there should not be any running process instance/task
  assertEquals(0, runtimeService.createProcessInstanceQuery().processDefinitionKey(USERTASK_PROCESS).count());
  assertEquals(0, taskService.createTaskQuery().taskDefinitionKey("userTask").count());

  simRun.step();

  // replay process was started
  assertEquals(1, runtimeService.createProcessInstanceQuery().processDefinitionKey(USERTASK_PROCESS).count());
  // there should be one task
  assertEquals(1, taskService.createTaskQuery().taskDefinitionKey("userTask").count());
Copy the code
  • Task creation, UserTaskExecutionListener will create a new simulation event to end user tasks:
  simRun.step();

  // userTask was completed - replay process was finished
  assertEquals(0, runtimeService.createProcessInstanceQuery().processDefinitionKey(USERTASK_PROCESS).count());
  assertEquals(0, taskService.createTaskQuery().taskDefinitionKey("userTask").count());
Copy the code
  • The simulation is over. At this point you can proceed to start another process instance or event and then close simRun and the process engine:
  simRun.close();
  processEngine.close();
  ProcessEngines.destroy();
Copy the code