6

工作流框架中的仿真引擎!实现用户流程场景的仿真

 3 years ago
source link: https://segmentfault.com/a/1190000040173071
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Activiti-Crystalball简介

  • Activiti-Crystalball(CrystalBall)是Activiti业务流程管理平台的仿真引擎 .CrystalBall可以使用用用户模拟流程场景:

    • 决策支持: 对于生产流程, 比如是否应该向系统添加更多资料以达到截止日期
    • 优化和验证: 测试修改并验证影响
    • 培训: 模拟器可以用来在使用前培训员工
  • CrystalBall是独立的:

    • 不需要创建单独的模拟模型和引擎
    • 不需要为模拟创建不同的报告
    • 不需要为模拟引擎准备很多数据
  • CrystalBall模拟器是基于Activiti的:

    • 容易复制数据
    • 启动模拟器
    • 从历史中重播流程行为

      CrystalBall内部

  • CrystalBall是一个离散事件模拟器
  • CrystalBall的一个实现是org.activiti.crystalball.simulator.SimpleSimulationRun:

    init();
    
      SimulationEvent event = removeSimulationEvent();
    
      while (!simulationEnd(event)) {
        executeEvent(event);
        event = removeSimulationEvent();
      }
    
      close();
  • SimulationRun可以执行由不同源生成的模拟事件

  • 模拟器可以使用的用例之一是分析历史
  • 生产环境没有提供任何重复和调试bug的机会,这就是为什么基本不可能把流程引擎恢复到生产环境出现问题时完全一样的状态.有以下原因:

    • 时间: 流程实例可能执行好几个月
    • 并发: 流程实例会和别的实例一起运行,问题可能只产生于并发执行的情况
    • 用户: 很多用户可以参与到流程实例中,流程实例会影响到出现问题的状态
  • 模拟器可以更好的暴露以上的问题:

    • 模拟过程是虚拟的,不会依赖真实环境
    • Activiti流程引擎本身是虚拟的,不需要创建虚拟流程引擎,作为模拟环境使用
    • 并发场景也是原生的
    • 用户行为都会记录日志,并可以从日志重现,根据需要进行预测和生成
  • 分析历史的最好办法是重现一次,真实环境很难实现重现,但是模拟器就可以实现重现

    历史的事件

  • 重现历史最重要的事情是记录影响状态的事件
  • 流程是由用户事件驱动的,可以使用两种事件源:

    • 流程实例: 只支持原始的Activiti-Crystalball项目
    • ActivitiEvent日志: 可以向引擎添加想要记录日志的ActivitiEventListener. 事件日志可以保存下来,用于后续的分析
  • ActivitiEventListener的一个基本实现: org.activiti.crystalball.simulator.delegate.event.impl.InMemoryRecordActivitiEventListener

     @Override
    public void onEvent(ActivitiEvent event) {
      Collection<SimulationEvent> simulationEvents = transform(event);
      store(simulationEvents);
    }
  • 事件会被保存,可以对历史进行重现

  • 回放的好处是可以一遍一遍播放,直到完全理解发生了什么
  • Crystalball模拟器是基于真实数据,真实用户行为

  • 示例: 理解回放工作的最好方法是一步一步解释

    • 基于JUnit的测试例子 :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>

      流程发布,可以用于真实和模拟的运行:

  • 记录事件

    // 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();

    startProcessInstanceByKey方法调用后,记录ActivitiEventType.ENTITY_CREATED

  • 开始模拟运行:

    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();

  • 其它示例在org.activiti.crystalball.simulator.delegate.event.PlaybackProcessStartTest中

    调试流程引擎

  • 回放限制执行所有模拟事件一次性
  • 调试器允许将流程事件自行拆分成更小的步骤,在步骤之间观察流程引擎的状态

    • SimpleSimulationRun实现了SimulationDebugger接口 .SimulationDebugger可以一步一步执行模拟事件,可以模拟特定时间的执行:

    • 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();

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

    • 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();
      }

  • 执行SimpleSimulationRunTest来观察流程引擎调试器的运行

  • 回放需要创建另一个流程引擎实例,模拟环境配置
  • 重播工作在真实的流程引擎之上,重播在运行的流程引擎中执行模拟事件:

    • 结论是重播是实时运行的,实时意味着会被立即执行**

      重播一个流程实例示例: ReplyRunTest

  • 第一部分 :初始化流程引擎,启动一个流程实例,完成流程实例的任务

    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());

    使用的流程引擎是基础的InMemoryStandaloneProcessEngine: 配置了InMemoryRecordActivitiEventListener(记录Activiti事件,并转换为模拟事件)和UserTaskExecutionListener(当创建新用户任务时,新任务会重播流程实例,把任务完成事件放到事件日历中)

  • 第二部分 :在原始流程相同的引擎引擎上启动模拟调试器

    • 重播事件处理器使用StartReplayProcessEventHandler替换StartProcessEventHandler
    • StartReplayProcessEventHandler获取流程实例Id来重播,在流程实例启动的初始位置处理
    • StartProcessEventHandler在开始阶段,会创建一个新流程实例,包含一个变量.变量名为 _replay.processInstanceId. 变量用来保存重播的流程实例Id
    • SimpleSimulationRun不同 ,ReplaySimulationRun:

      • 不会创建和关闭流程引擎实例
      • 不会修改模拟时间

        final SimulationDebugger simRun = new ReplaySimulationRun(processEngine,
        getReplayHandlers(processInstance.getId()));
  • 开始重播流程实例:

    • 一开始, 没有运行的流程实例
    • 只有一个已完成的,在历史中的流程实例
    • 在初始化后,会在事件日历中添加一个模拟事件-用来启动流程实例,重播已经完成的流程实例

       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());
  • 任务创建时,UserTaskExecutionListener会创建一个新模拟事件来结束用户任务:

    simRun.step();
    
    // userTask was completed - replay process was finished
    assertEquals(0, runtimeService.createProcessInstanceQuery().processDefinitionKey(USERTASK_PROCESS).count());
    assertEquals(0, taskService.createTaskQuery().taskDefinitionKey("userTask").count());
  • 模拟结束.这时可以继续启动另一个流程实例或者事件,然后关闭simRun和流程引擎:

    simRun.close();
    processEngine.close();
    ProcessEngines.destroy();

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK