Introduction to Triple Crown
Aneshka Goyal
AWS Certified Solutions Architect | Software Development Engineer III at Egencia, An American Express Global Business Travel Company
Organizations are always trying to improve how they work, in order to increase efficiency and reduce errors. This requires analysis and continuous improvement of their working methods, which may include very structured workflows in predictable situations, as well as protocols to respond to dynamic situations where it is impossible to follow a fixed process. Recognizing these varied requirements, members of the Object Management Group(OMG), an international, open membership, not-for-profit technology standards consortium, have produced three successive, powerful and complementary standards, which together can model the range of working methods used across most organizations. This “triple crown” of process improvement standards offers support for process specification (BPMN), case management (CMMN) and decision modelling (DMN).
Thus all these three(mentioned in title) help in improving the working of an organisation, making it more smooth and easy to understand for stakeholders by providing support for process specification (BPMN), case management (CMMN) and decision modelling (DMN). Whenever we think of some ordered flow/ process or some complex scenarios like insurance claim settlement this triple crown helps to make it simpler to showcase using graphical annotations, which makes it easier to understand for the stakeholders and hence easier to suggest improvements in the process and reduce chances of errors.
In this blog we will be taking a look at each of the elements of triple crown and how they fit in and help solve business problems. We will take a simple example just to understand how these can be developed and deployed leveraging java frameworks. Going forward we will be leveraging Flowable in our working code samples for BPMN, CMMN and DMN.
Before we leverage Flowable let's put together basic understanding of what is it. Flowable is an open source workflow engine written in Java that provides easy integration with with much popular Spring framework. It is an actively maintained fork of the activity software used popularly for Business Process Modelling.
BPMN
Business Process Model and Notation(BPMN) is a flow chart method that models the steps of a planned business process from end to end. A key to Business Process Management, it visually depicts a detailed sequence of business activities and information flows needed to complete a process.
For example the business process shown in the image.
Since its pictorial its very easy for all the stakeholders to understand the process, how the events flow and information passed.
Business Process Modeling Notation was developed by the Business Process Management Initiative (BPMI) and has gone through a series of revisions. In 2005, that group merged with the?Object Management Group (OMG), which took over the initiative. In 2011, OMG released BPMN 2.0 and changed the method’s name to Business Process?Model and?Notation. These standards are necessary to avoid any one vendor tight coupling and be understood at a larger scale uniformly.
Let's understand some key elements that we might encounter while modelling a business process.
Events - Events are circular symbols that serve as a trigger: initiating a starting point, intermediate step, or end point of a particular process. Some popular event symbols are:?timer, message, start, terminate etc.
Activities - Activities are rounded rectangles, illustrating a specific task performed by a person or system. There are many activities process designers can create, such as activities that occur once, occur multiple times, or if a specific set of conditions are met. Some commonly known activities are:
Gateways - Gateways within a BPMN diagram are diamond-shaped symbols that map decision points. They can be “forks in the road,” that determine the direction a process shall turn next. Types include, parallel, inclusive, event based and exclusive gateways. As the name suggests exclusive gateways allow one choice out of n options while inclusive allows to join all, event based gateways await a particular event to happen and parallel gateways don't wait for a particular condition to be met but allow for parallel executions of a number of forks.
We discussed some elements but an important part is connecting these various elements together, thus BPMN 2.0 has supported connectors.
Swimlanes - Swimlanes are graphical containers that represent participants of a process. Pools and lanes are the two kinds of swimlanes. Pools can be disjoint entities or roles that participate in a business process and Lanes can be thought of sub partition in the pool for example Department can be a pool and Finance, HR etc can be the lanes of that pool.
BPMN 2.0 not just allows us to depict a business process graphically but we can even handle the processing. Next we will be depicting and processing one such process using flowable and Spring Boot.
Sample Scenario
Lets take a Process to Purchase books. Lets say we have a simple business process that allows any student to submit his name and based on the name we keep the info which standard's course books he/she wants to purchase. This is a very simple scenario where we have considered uniqueness on names to avoid any conflicts and keep the processing easy.
We initialised a project using spring intializr. It is a maven project and hence the pom looks something like this.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>flowable-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>flowable-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Here spring boot web dependency lets us create some Restful endpoints for our sample project. Apart from that, the notable dependencies are flowable spring boot starter, this is a bundled dependency that has the boiler plate flowable code in place to work with spring boot. This dependency is enough for us to work on BPMN, CMMN and DMN. Flowable also needs to read and write the process definitions, executions states and history into data base tables. Flowable does this for all three elements BPMN, CMMN and DMN. This helps in auditing purposes as well. It has pre defined schema in place and hence needs a SQL DB in order to be up and running. Possible DBs being h2, mysql, oracle, postgres, mssql, db2. For this sample application instead of going for in memory h2 DB we would be leveraging postgreSQL so that we are able to query some tables and see the process data stored in a persistent storage. Thus we have a postgreSQL dependency included.
We will also be using docker to run flowable locally so that we can leverage UI application to create our process/DMN/case definitions. For this we will pull the flowable/all-in-one image and run that on our system.
docker pull flowable/all-in-one
docker run -p 8090:8080 flowable/all-in-one
Next we need to setup the PostgreSQL server locally and we would be leveraging a tool like PGAdmin to access our DB.
Once these local setups are done we should be able to make changes to our spring application to be able to connect to our DB instance.
spring.datasource.url=jdbc:postgresql://localhost:5432/flowable-demo
spring.datasource.username=postgres
spring.datasource.password=
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto = create-drop
The above is how our application.properties looks like.
Next we will be creating our process definition, we can directly write the definition in xml or we can leverage the flowable UI to create a graphical BPMN 2.0 process definition and download the same as xml. For me, the second option works better as its easier, less error prone and better to understand.
Since we have our flowable docker image running, we should be able to access
https://localhost:8890/flowable-modeler/#/processes
post logging in with username as admin and password as test.
This gives us a UI to create/import a process definition. We will be leveraging the elements of BPMN 2.0 that we discussed above to create a process that looks like the following.
Here we have leveraged service tasks/activities, sequence connectors, exclusive gateways and start and end events. As shown in the diagram the flow starts by getting a students details, once we have information on which standard the student studies in we call that particular fulfilment function and our process completes.
Since we are leveraging service tasks we will be delegating the execution to a dedicated class instance, thus we need to specify such delegations in our BPMN process.
Other thing we need to specify is a condition on the sequence flow from the exclusive gateway such that on the match of a condition we can choose a single path to follow, since its exclusive gateway. We have provision for defining that as well, as shown below.
Once we we have our process definition ready we can download the xml and copy the xml as bmpn20.xml file in the src/main/resources/processes directory. We need to make sure that the file extension and location are correct for flowable to automatically pick it up as a process definition for executions. The xml for the BPMN process looks like below.
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:flowable="https://flowable.org/bpmn" xmlns:bpmndi="https://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="https://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="https://www.omg.org/spec/DD/20100524/DI" typeLanguage="https://www.w3.org/2001/XMLSchema" expressionLanguage="https://www.w3.org/1999/XPath" targetNamespace="https://www.flowable.org/processdef">
<process id="PurchaseBooksV1" name="PurchaseBooksV1" isExecutable="true">
<startEvent id="start" flowable:formFieldValidation="true"></startEvent>
<serviceTask id="fulfilRequestForCalssX" name="fulfilRequestForClassX" flowable:delegateExpression="${fulfilRequestForClassXTask}"></serviceTask>
<endEvent id="sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E"></endEvent>
<serviceTask id="fulfilRequestForClassXI" name="fulfilRequestForClassXI" flowable:delegateExpression="${fulfilRequestForClassXITask}"></serviceTask>
<serviceTask id="getStudentDetails" name="getStudentDetails" flowable:delegateExpression="${studentDetailsTask}"></serviceTask>
<exclusiveGateway id="sid-99F8AF8E-358E-4F41-9FB9-929CABDFAC59"></exclusiveGateway>
<sequenceFlow id="startToGetStudentDetails" sourceRef="start" targetRef="getStudentDetails"></sequenceFlow>
<sequenceFlow id="getStrudentDetailsToGateway" sourceRef="getStudentDetails" targetRef="sid-99F8AF8E-358E-4F41-9FB9-929CABDFAC59"></sequenceFlow>
<sequenceFlow id="gatewayToClassXI" name="XI" sourceRef="sid-99F8AF8E-358E-4F41-9FB9-929CABDFAC59" targetRef="fulfilRequestForClassXI">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${execution.getVariable("STUDENT_CLASS") == "XI"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="gatewayToClassX" name="X" sourceRef="sid-99F8AF8E-358E-4F41-9FB9-929CABDFAC59" targetRef="fulfilRequestForCalssX">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${execution.getVariable("STUDENT_CLASS") == "X"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="classXtoEnd" sourceRef="fulfilRequestForCalssX" targetRef="sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E"></sequenceFlow>
<sequenceFlow id="classXIToEnd" sourceRef="fulfilRequestForClassXI" targetRef="sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_PurchaseBooksV1">
<bpmndi:BPMNPlane bpmnElement="PurchaseBooksV1" id="BPMNPlane_PurchaseBooksV1">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="60.0" y="145.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fulfilRequestForCalssX" id="BPMNShape_fulfilRequestForCalssX">
<omgdc:Bounds height="80.0" width="100.0" x="435.0" y="75.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E" id="BPMNShape_sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E">
<omgdc:Bounds height="28.0" width="28.0" x="690.0" y="165.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fulfilRequestForClassXI" id="BPMNShape_fulfilRequestForClassXI">
<omgdc:Bounds height="80.0" width="100.0" x="435.0" y="210.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="getStudentDetails" id="BPMNShape_getStudentDetails">
<omgdc:Bounds height="80.0" width="100.0" x="180.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-99F8AF8E-358E-4F41-9FB9-929CABDFAC59" id="BPMNShape_sid-99F8AF8E-358E-4F41-9FB9-929CABDFAC59">
<omgdc:Bounds height="40.0" width="40.0" x="330.0" y="140.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="startToGetStudentDetails" id="BPMNEdge_startToGetStudentDetails">
<omgdi:waypoint x="89.94999837389872" y="160.0"></omgdi:waypoint>
<omgdi:waypoint x="179.99999999997436" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="getStrudentDetailsToGateway" id="BPMNEdge_getStrudentDetailsToGateway">
<omgdi:waypoint x="279.94999999999277" y="160.20726141078836"></omgdi:waypoint>
<omgdi:waypoint x="330.41666666666436" y="160.41666666666666"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="gatewayToClassX" id="BPMNEdge_gatewayToClassX">
<omgdi:waypoint x="353.3205920795673" y="143.3578182875459"></omgdi:waypoint>
<omgdi:waypoint x="435.0" y="115.3259827707224"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="classXIToEnd" id="BPMNEdge_classXIToEnd">
<omgdi:waypoint x="534.9499999999999" y="249.61963190184048"></omgdi:waypoint>
<omgdi:waypoint x="690.0457905200011" y="190.62643617396566"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="gatewayToClassXI" id="BPMNEdge_gatewayToClassXI">
<omgdi:waypoint x="353.036669829222" y="176.9132911392405"></omgdi:waypoint>
<omgdi:waypoint x="435.0" y="249.11904761904762"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="classXtoEnd" id="BPMNEdge_classXtoEnd">
<omgdi:waypoint x="534.95" y="115.35474683544305"></omgdi:waypoint>
<omgdi:waypoint x="690.0061946908362" y="173.25568211260173"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
To read this rather elaborate xml, we can focus on the process block as the other one is a simple placement on the diagram on UI.
The process block tells us about various events like start and end that we have, each element would need to have a unique id, either we can provide one or flowable generates for us. The rather long strings in id are auto generated.
The service tasks have delegate expressions and the sequence flows have source and destination references. Also the sequence flows from exclusive gateway have conditional execution constraints.
Next let's now take a look at our controller delegate and other classes that we need to have in our spring application.
@RestController
@RequestMapping("/v1/flowable-demo")
public class FlowableDemoController {
@Autowired
private final FlowableDemoService flowableDemoService;
private final ApplicationContext applicationContext;
public FlowableDemoController(FlowableDemoService flowableDemoService, ApplicationContext applicationContext) {
this.flowableDemoService = flowableDemoService;
this.applicationContext = applicationContext;
}
@GetMapping
public void getBooksForStudent (@RequestParam String studentName) {
flowableDemoService.getBooksForStudent(studentName);
}
}
Lets say we have a simple REST controller that exposes an endpoint for us to get books for a particular student. For simplicity we are not returning any response for our get call. As we can see the controller talks to the service layer once it gets a call.
The service code is as below.
@Service
public class FlowableDemoService {
private final RuntimeService runtimeService;
public FlowableDemoService(RuntimeService runtimeService) {
this.runtimeService = runtimeService;
}
public void getBooksForStudent (String name) {
Map<String, Object> context = new HashMap<>();
context.put("student_name", name);
runtimeService.startProcessInstanceByKey("PurchaseBooksV1", context);
}
}
Here we are leveraging the flowable engines runtime service to start a process giving its name (this is the name of the process definition file without extension that we saved). Note: We are also passing in some process context variable(name of the student that was passed as a param in get call) that will be leveraged by the process instance tasks (as we will just see).
We did not have to start flowable process engines, register process definitions since we are leveraging flowable-springboot-starter and that does all the boiler plate job for us given we have things placed at the right locations.
Next let's take a look at our first service task delegate i.e to get student details.
@Component("studentDetailsTask")
public class StudentDetailsTask implements JavaDelegate {
private final FlowableDemoRepo flowableDemoRepo;
public StudentDetailsTask(FlowableDemoRepo flowableDemoRepo) {
this.flowableDemoRepo = flowableDemoRepo;
}
@Override
public void execute(DelegateExecution delegateExecution) {
System.out.println(delegateExecution.getVariable("student_name"));
String standard = flowableDemoRepo.getStandardForStudent((String)delegateExecution.getVariable("student_name"));
delegateExecution.setVariable("STUDENT_CLASS", standard);
System.out.println(delegateExecution.getVariable("STUDENT_CLASS"));
System.out.println(delegateExecution.getVariables().size());
}
}
This is a simple Java class annotated as spring component. One thing to note is that is implements the JavaDelegate this is to make it discoverable as a service task delegate, once the respective task is executing, this class's execute method is invoked. In the service class while starting the process instance, we had passes student name in context. Here we use that name to resolve the student's standard details and fill it up in context to be leveraged ahead in the execution flow, primarily while making a choice as to which fulfilment task to be called.
Let's take a look at the repository class as well, it is a simple in memory repository with limited student records.
@Repository
public class FlowableDemoRepo {
private static Map<String, String> studentMap = null;
public FlowableDemoRepo() {
studentMap = new HashMap<>();
studentMap.put("abc","XI");
studentMap.put("cdf", "X");
}
public String getStandardForStudent (String name) {
return studentMap.get(name);
}
}
Let' also define the two fulfilment tasks. One for each standard we provide books for. First for XI standard.
@Component
public class FulfilRequestForClassXITask implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
System.out.println("Got Request for Books for " + delegateExecution.getVariable("student_name", String.class) + "studying in " + delegateExecution.getVariable("STUDENT_CLASS", String.class));
}
}
Second for X standard
@Component
public class FulfilRequestForClassXTask implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
System.out.println("Got Request for Books for " + delegateExecution.getVariable("student_name", String.class) + " studying in " + delegateExecution.getVariable("STUDENT_CLASS", String.class));
}
}
Note: Currently both these look similar in functionality that they simply print a statement. This is a very correct observation and we will be leveraging this to introduce DMNs going forward.
Let's start the application and make an api call for student name as abc, followed by student name as cdf. We can see the print statements specifically from the fulfilment tasks of respective standards.
We also have postgresql running and storing data for such executions. We can go ahead and query the table to get the most recent execution of process instance and all the elements that were a part of that execution.
Before we query the data base let's understand some basics about the schema/table nomenclature. The database table names associated with the Flowable open source code base start with?ACT_. Database tables specific to Flowable Work or Engage start with the?FLW_?prefix.
The second part of the table name (after the first?'_') is a two character identification specifying the specific use case of the table.
Since our case both our executions we completed we will be looking at the act_hi_prosinst which shall give us the process instance history. As we know the time of execution from out IDE logs, we can find the two specific instances and then query for their activity details.
select * from act_hi_procinst;
Let's get the history of activities for first process instance that corresponds to the student name abc, standard XI. This has all the details around the different activities and their execution start and end time.
select * from act_hi_actinst where proc_inst_id_='8b044963-339d-11ee-b90d-acde48001122';
All the static info around BPMN process definition is also preserved by flowable.
Note: If we do not use an end event in this case, the process definition adds an implicit end event and hence the data does not remain in act_ru_* tables.
Thus we were able to understand the basic elements of BPMN and also created a process using some of the several elements. We also looked at the execution history of our process.
Let's now look at the second in line, i.e DMN.
DMN
Decision Model and Notation is a modelling language and notation for the precise specification of business decisions and business rules. DMN is easily readable by the different types of people involved in decision management. These include: business people who specify the rules and monitor their application; business analysts. DMNs can be used alongside BPMN processes or CMMN cases(we will see this later). DMNs allow to model business decisions in the form of unambiguous tables.
Lets understand some basics about the decision modeling before we implement DMN in our Purchase book BPMN2.0 process that we defined above.
This is how a simple decision table looks like. We can see Column headers. One column is highlighted in blue that is our input column representing an input condition. Other column is the output column, based on the input value matched and the hit policy specified(in the top left as 'F') we get an output column value returned. Lets discuss more about the hit policy.
In the top left corner we can see a?hit policy.
There are 7 hit policies available;
single hit
multiple hit
(Prior to version 1.2 of the DMN spec the definition of the?count?collect hit policy stated that only distinct outputs were taken into account. We can still force this behaviour by selecting?Force DMN 1.1?when saving a decision table)
Note: If we have more that one input columns, a logical AND operation on input columns can be used to find the row that matches and then deliver the output as per our hit policy.
Since now we understand the basics of DMN and what are decision tables and some terms associated with it. Let's try to leverage the above decision table in our existing BPMN process and thus simply the process a bit from what we initially developed.
We would be using the same app we had, so we don't need any additional dependencies. Now in our existing BPMN process we have used an exclusive gateway, this lets us decide whether its a X or XI standard student and hence follow the respective sequence. Since we are making a decision, lets try to make that decision now using a decision table specified using DMN.
Our decision table looks like this.
Just like we did for BPMN, we would be exporting it as dmn file and placing it under the dmn directory (this is similar to the step where we created the processes directory to place the BPMN2.0.xml file).
<definitions xmlns="https://www.omg.org/spec/DMN/20180521/MODEL/" id="definition_4a0ea353-23b5-11ee-babe-0242ac110002" name="StandardDecision" namespace="https://www.flowable.org/dmn">
<decision id="StandardDecision" name="StandardDecision">
<decisionTable id="decisionTable_4a0ea353-23b5-11ee-babe-0242ac110002" hitPolicy="FIRST">
<input label="name">
<inputExpression id="inputExpression_3" typeRef="string">
<text>student_name</text>
</inputExpression>
<inputValues>
<text>"abc","cdf"</text>
</inputValues>
</input>
<output id="outputExpression_4" label="class" name="STUDENT_CLASS" typeRef="string">
<outputValues>
<text>"X","XI"</text>
</outputValues>
</output>
<rule>
<inputEntry id="inputEntry_3_1">
<text><![CDATA[== "abc"]]></text>
</inputEntry>
<outputEntry id="outputEntry_4_1">
<text><![CDATA["X"]]></text>
</outputEntry>
</rule>
<rule>
<inputEntry id="inputEntry_3_2">
<text><![CDATA[== "cdf"]]></text>
</inputEntry>
<outputEntry id="outputEntry_4_2">
<text><![CDATA["XI"]]></text>
</outputEntry>
</rule>
</decisionTable>
</decision>
</definitions>
The definitions can be understood with help of accompanying tags.
We will also simplify our BPMN process since we will remove the exclusive gateway and the forks in the sequence. This is how the simplified process looks like.
We are referencing the Standard decision table here that we just built. Also since we said the nature of work is similar in X and XI fulfilment task we have also reduced that complexity from our BPMN process diagram.
Let's take a look at the generated xml to find the change details.
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:flowable="https://flowable.org/bpmn" xmlns:bpmndi="https://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="https://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="https://www.omg.org/spec/DD/20100524/DI" typeLanguage="https://www.w3.org/2001/XMLSchema" expressionLanguage="https://www.w3.org/1999/XPath" targetNamespace="https://www.flowable.org/processdef">
<process id="PurchaseBooksV2" name="PurchaseBooksV2" isExecutable="true">
<documentation>null</documentation>
<startEvent id="start" flowable:formFieldValidation="true"></startEvent>
<endEvent id="sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E"></endEvent>
<serviceTask id="getStudentDetails" name="getStudentDetails" flowable:delegateExpression="${studentDetailsTask}"></serviceTask>
<serviceTask id="standardDecision" name="StandardDecision" flowable:type="dmn">
<extensionElements>
<flowable:field name="decisionTableReferenceKey">
<flowable:string><![CDATA[StandardDecision]]></flowable:string>
</flowable:field>
<flowable:field name="decisionTaskThrowErrorOnNoHits">
<flowable:string><![CDATA[false]]></flowable:string>
</flowable:field>
<flowable:field name="fallbackToDefaultTenant">
<flowable:string><![CDATA[false]]></flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
<serviceTask id="sid-DBDAB0C7-199B-4982-B29B-C5044A4A49CB" name="fulFilRequestTask" flowable:delegateExpression="${fulfilRequestTask}"></serviceTask>
<sequenceFlow id="fulfilRequestTaskToEnd" sourceRef="sid-DBDAB0C7-199B-4982-B29B-C5044A4A49CB" targetRef="sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E"></sequenceFlow>
<sequenceFlow id="startToGetStudentDetails" sourceRef="start" targetRef="getStudentDetails"></sequenceFlow>
<sequenceFlow id="getStrudentDetailsToDMN" sourceRef="getStudentDetails" targetRef="standardDecision"></sequenceFlow>
<sequenceFlow id="standardDecisionToFulfilRequestTask" sourceRef="standardDecision" targetRef="sid-DBDAB0C7-199B-4982-B29B-C5044A4A49CB"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_PurchaseBooksV2">
<bpmndi:BPMNPlane bpmnElement="PurchaseBooksV2" id="BPMNPlane_PurchaseBooksV2">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="60.0" y="145.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E" id="BPMNShape_sid-8DA83A8D-9CE3-459A-AE2B-34BAF0678E6E">
<omgdc:Bounds height="28.0" width="28.0" x="720.0" y="146.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="getStudentDetails" id="BPMNShape_getStudentDetails">
<omgdc:Bounds height="80.0" width="100.0" x="180.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="standardDecision" id="BPMNShape_standardDecision">
<omgdc:Bounds height="80.0" width="100.0" x="345.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-DBDAB0C7-199B-4982-B29B-C5044A4A49CB" id="BPMNShape_sid-DBDAB0C7-199B-4982-B29B-C5044A4A49CB">
<omgdc:Bounds height="80.0" width="100.0" x="510.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="startToGetStudentDetails" id="BPMNEdge_startToGetStudentDetails">
<omgdi:waypoint x="89.94999923927439" y="160.0"></omgdi:waypoint>
<omgdi:waypoint x="179.9999999999684" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="standardDecisionToFulfilRequestTask" id="BPMNEdge_standardDecisionToFulfilRequestTask">
<omgdi:waypoint x="444.95000000000005" y="160.0"></omgdi:waypoint>
<omgdi:waypoint x="510.0" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="fulfilRequestTaskToEnd" id="BPMNEdge_fulfilRequestTaskToEnd">
<omgdi:waypoint x="609.9499999999774" y="160.0"></omgdi:waypoint>
<omgdi:waypoint x="720.0" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="getStrudentDetailsToDMN" id="BPMNEdge_getStrudentDetailsToDMN">
<omgdi:waypoint x="279.9499999999776" y="160.0"></omgdi:waypoint>
<omgdi:waypoint x="344.99999999996464" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
The BPMN process definition above gives details around the service tasks and their delegates and also references the decision table by its id.
领英推荐
The changes in the StudentDetailsTask is as below.
@Component("studentDetailsTask")
public class StudentDetailsTask implements JavaDelegate {
private final FlowableDemoRepo flowableDemoRepo;
public StudentDetailsTask(FlowableDemoRepo flowableDemoRepo) {
this.flowableDemoRepo = flowableDemoRepo;
}
@Override
public void execute(DelegateExecution delegateExecution) {
System.out.println(delegateExecution.getVariable("student_name"));
String standard = flowableDemoRepo.getStandardForStudent((String)delegateExecution.getVariable("student_name"));
//delegateExecution.setVariable("STUDENT_CLASS", standard);
//System.out.println(delegateExecution.getVariable("STUDENT_CLASS"));
System.out.println(delegateExecution.getVariables().size());
}
}
We have commented the interaction with repository as this is no longer needed, since we have delegated the task of standard resolution to our decision table.
FulfilRequest is the service task delegate in place of 2 different service tasks that we had initially for each standard.
@Component
public class FulfilRequestTask implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
System.out.println("Got Request for Books for " + delegateExecution.getVariable("student_name", String.class) + " studying in " + delegateExecution.getVariable("STUDENT_CLASS", String.class));
}
}
Inorder to start the process instance we have a single line change in the FlowableDemoService code. This instructs to start the process instance for v2 version instead of the previous v1 version.
runtimeService.startProcessInstanceByKey("PurchaseBooksV2", context);
Lets run the application and hit our endpoint, first for abc and then for cdf and note the outcomes in each case.
This prints the lines as we expected, first student name, followed by the context map size and then the fulfilment task statement with right standard as resolved from our decision table. As expected the output is similar to BPMN process with exclusive gateway output, only difference is that the size of our process map is one since we no longer pass the standard info alongside name (resolution done by decision table).
Next as we did for BPMN process let's see the execution history from the postgresql tables related to DMN. The naming convention is similar to what we had for BPMN processes just that prefix would now be act_dmn since we are dealing with dmns now.
select * from act_dmn_hi_decision_execution;
The above query execution will give us details on the execution like start time, end time and other details along with an execution json.
Below is the image of our query execution alongside execution json that gives granular details around input, types, variables, hit policy, and output details as well (first for name as abc followed by cdf).
Thus we saw that with Decision table made using DMN it becomes easy to model the decision flows in easy to understand table, based on inputs and hit condition. This also makes the BPMN process somewhat linear, cleaner and easier to understand.
Next we would discuss CMMN, last but not the least of the triple crown and apply the above two to model a sample case.
CMMN
Case Management Model and Notion, is a graphical notation used for capturing work methods that are based on the handling of cases requiring various activities that may be performed in?an unpredictable order in response to evolving situations. Using an event-centred approach and the concept of a case file, CMMN expands the boundaries of what can be modelled with?BPMN, including less structured work efforts and those driven by knowledge workers. Using a combination of BPMN and CMMN allows users to cover a much broader spectrum of work methods. The Basic difference between BPMN and CMMN being that former focuses on process(how to achieve something) where the flow and steps are pretty clear while the latter tells what needs to be achieved, flow or steps or how to achieve is not very clear and hence depends on the knowledge workers(People) involved. Some examples of CMMN to create cases can be insurance claim settlement, loan applications, customer complaints, and patients in healthcare.
Before understanding the building blocks or elements of CMMN, lets take a look at the lifecycle of activation
With CMMN we’re always looking at what’s?available?and can be made?active, either automatically or started manually by a user.?CMMN is about making the right set of options available to a user, or automatically executed, at the right time and in the right circumstances.?The main states in a case element’s lifecycle are shown in the figure below.
Some basic elements of CMMN are :
Case Plan
Everything about a case is contained in a?Case Plan Model, which is depicted visually as a large dossier.?A case plan can represent a wide range of business situations, with typical case management scenarios. In the figure on the top, the outermost rectangle is the case model and it has a name as well.
Stage
A?stage?within a case plan model can be looked at like a phase, a step or a period in the lifecycle of a case. If we look at the end-to-end lifecycle of a case from a business perspective (the full use case it represents), then a?stage?is a phase within that lifecycle.
But stages might not only represent a business meaning when looking at the use case the case model is representing, they might also be used for more technical / modelling purposes to group certain elements together. Stages are well labelled and depicted in the diagram above.
Plan Fragment
A plan fragment is very similar to a stage, however, it has no implication during execution, it is only useful for a visual representation of elements belonging together, but not from an execution point of view.
If we add a?plan fragment?to the case model, name it?Global Tasks?and add two user tasks?Task C?and?Task D?to it. The plan fragment is represented as a dotted container.
Milestone
A milestone represents, as the name implies, an important step within the lifecycle of a case to be reached at some point. A milestone, from a CMMN element point of view, does not have an active state, it typically goes from?available?directly to?completed?state at the point where the milestone is reached as a milestone does not contain "work" to be done like a stage and only represents an important step being reached. The Case above has a milestone depicted as a rectangle with rounded corners (also labelled as a milestone)
Sentries
If we want to have an order in the stages, we can use a?sentry?to connect them and bring them into a particular order. These can also serve as conditional entry/ exit points for stage or case itself. The diamonds in the diagram are entry sentry, exit is similarly represented as a dark coloured diamond(shown below).
Events Listeners and Triggers
Another major elements are?Event listeners, again with a variety of flavours, including?Timer Event Listeners?and?User Event Listeners, represented as double-line circles.?As we might expect, these are used to trigger things when certain events occur.
Tasks
Then we have?Tasks?of different flavours that represent some actual piece of work.?These might be?Human Tasks,?Decision Tasks,?Process Tasks?or?Case Tasks?among others.?These are drawn as rectangles with rounded corners.?Tasks can be placed inside the case plan or within a stage. An important feature of tasks is that they can be defined as starting manually or automatically, as well as be used multiple times.?A case can have BPMN processes as tasks or even other cases. We can have repeating tasks as well. The diagram in reference shows the human tasks. Some tasks can be modelled as blocking or non-blocking.
Let's now extend the example we have used so far, to create a case. The main aim of this sample would be see the various elements of the case created using CMMN in action and witnesses the transition across lifecycle stages by querying our db tables.
Understanding Case
We will extend the same spring boot application for case modelling depiction. Here we will consider that our case has two stages, inventory updation and purchase stage. The inventory updation stage has two service tasks, one for whole sale and other for resale, we can think of it that any student who purchases books can specify if they want to buy new books or would want to save some cost by purchasing old books. The inventory updation stage is marked to be completed if any one of the two service tasks complete(based on our use case understanding). If we have not marked it for such optional completion, the stage would still be available and hence the case will also not complete.
Next we have the Purchase stage that has the same BPMN process with decision table modelled using the DMN that we discussed above. This stage has an entry criterion that the inventory update stage should be completed with student action variable setup as Purchase. With this we are also able to solve a case where a student just wants to purchase books and has no choice of inventory, had we kept it simply on completion of Inventory stage we would not be able to trigger purchase stage without completing inventory stage.
${vars:getOrDefault("STUDENT_ACTION","NOTHING") == "PURCHASE"}
Talking about entry sentries, we have them at task level for wholesale and resale and also at the Purchase stage level.
We also have a simple milestone with an entry sentry, this milestone moves from available to completed when stages complete as depicted by the entry sentry(we have modelled it as an OR condition).
The xml for our case definition looks like this
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="https://flowable.org/cmmn" xmlns:cmmndi="https://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="https://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="https://www.omg.org/spec/CMMN/20151109/DI" targetNamespace="https://www.flowable.org/casedef">
<case id="BookCaseV2" name="BookCaseV2" flowable:initiatorVariableName="initiator">
<casePlanModel id="casePlanModel" flowable:formFieldValidation="true">
<planItem id="planItem3" name="InventoryUpdation" definitionRef="InventoryUpdation"></planItem>
<planItem id="planItem5" name="Purchase" definitionRef="Purchase">
<entryCriterion id="sid-73978ECD-71BE-4615-A2CE-1755C3AFDA44" flowable:sentryRef="sentry3"></entryCriterion>
</planItem>
<planItem id="planItem6" name="Inventory Updated" definitionRef="sid-90A76B0E-C059-4447-BC51-81FDE30CE23D">
<entryCriterion id="sid-5A44C718-3092-4E92-B9DA-FBAEECFEBCE7" flowable:sentryRef="sentry4"></entryCriterion>
<entryCriterion id="sid-8598A958-0545-4B72-9ED9-41949C7D29AE" flowable:sentryRef="sentry5"></entryCriterion>
</planItem>
<sentry id="sentry3">
<planItemOnPart id="sentryOnPart1" sourceRef="planItem3">
<standardEvent>complete</standardEvent>
</planItemOnPart>
<ifPart>
<condition><![CDATA[${vars:getOrDefault("STUDENT_ACTION","NOTHING") == "PURCHASE"}]]></condition>
</ifPart>
</sentry>
<sentry id="sentry4" flowable:triggerMode="onEvent">
<planItemOnPart id="sentryOnPart2" sourceRef="planItem3">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<sentry id="sentry5" flowable:triggerMode="onEvent">
<planItemOnPart id="sentryOnPart3" sourceRef="planItem5">
<standardEvent>complete</standardEvent>
</planItemOnPart>
</sentry>
<stage id="InventoryUpdation" name="InventoryUpdation" autoComplete="true">
<planItem id="planItem1" name="WholeSalse" definitionRef="WholeSalse">
<entryCriterion id="sid-547A8E94-264F-41A4-8A28-FA48C3BB09F9" flowable:sentryRef="sentry1"></entryCriterion>
</planItem>
<planItem id="planItem2" name="Resale" definitionRef="Resale">
<entryCriterion id="sid-634D8446-7741-42A6-B688-084B5F2917A6" flowable:sentryRef="sentry2"></entryCriterion>
</planItem>
<sentry id="sentry1">
<ifPart>
<condition><![CDATA[${vars:getOrDefault("STUDENT_ACTION","NOTHING") == "WholeSale"}]]></condition>
</ifPart>
</sentry>
<sentry id="sentry2">
<ifPart>
<condition><![CDATA[${vars:getOrDefault("STUDENT_ACTION","NOTHING") == "Resale"}]]></condition>
</ifPart>
</sentry>
<task id="WholeSalse" name="WholeSalse" isBlocking="false" flowable:type="java" flowable:delegateExpression="${wholeSaleTask}"></task>
<task id="Resale" name="Resale" isBlocking="false" flowable:type="java" flowable:delegateExpression="${resaleTask}"></task>
</stage>
<stage id="Purchase" name="Purchase">
<planItem id="planItem4" name="PurchaseProcess" definitionRef="PurchaseProcess"></planItem>
<processTask id="PurchaseProcess" name="PurchaseProcess" flowable:fallbackToDefaultTenant="false">
<extensionElements>
<flowable:in source="student_name" target="student_name"></flowable:in>
<flowable:out source="STUDENT_ACTION" target="STUDENT_ACTION"></flowable:out>
</extensionElements>
<processRefExpression><![CDATA[PurchaseBooksV2]]></processRefExpression>
</processTask>
</stage>
<milestone id="sid-90A76B0E-C059-4447-BC51-81FDE30CE23D" name="Inventory Updated"></milestone>
</casePlanModel>
</case>
<cmmndi:CMMNDI>
<cmmndi:CMMNDiagram id="CMMNDiagram_BookCaseV2">
<cmmndi:CMMNShape id="CMMNShape_casePlanModel" cmmnElementRef="casePlanModel">
<dc:Bounds height="714.0" width="718.0" x="0.0" y="0.0"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_planItem3" cmmnElementRef="planItem3">
<dc:Bounds height="269.0" width="446.0" x="60.0" y="90.0"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_planItem1" cmmnElementRef="planItem1">
<dc:Bounds height="80.0" width="100.0" x="100.0" y="147.35163718060247"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_sid-547A8E94-264F-41A4-8A28-FA48C3BB09F9" cmmnElementRef="sid-547A8E94-264F-41A4-8A28-FA48C3BB09F9">
<dc:Bounds height="22.0" width="14.0" x="92.29354863951394" y="171.1509755026039"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_planItem2" cmmnElementRef="planItem2">
<dc:Bounds height="80.0" width="100.0" x="265.0" y="210.0"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_sid-634D8446-7741-42A6-B688-084B5F2917A6" cmmnElementRef="sid-634D8446-7741-42A6-B688-084B5F2917A6">
<dc:Bounds height="22.0" width="14.0" x="257.288330078125" y="239.0"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_planItem5" cmmnElementRef="planItem5">
<dc:Bounds height="170.0" width="370.0" x="105.0" y="435.0"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_sid-73978ECD-71BE-4615-A2CE-1755C3AFDA44" cmmnElementRef="sid-73978ECD-71BE-4615-A2CE-1755C3AFDA44">
<dc:Bounds height="22.0" width="14.0" x="178.77207369902666" y="423.72260575374906"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_planItem4" cmmnElementRef="planItem4">
<dc:Bounds height="80.0" width="100.0" x="160.0" y="475.0"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_planItem6" cmmnElementRef="planItem6">
<dc:Bounds height="54.0" width="146.0" x="570.0" y="255.0"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_sid-5A44C718-3092-4E92-B9DA-FBAEECFEBCE7" cmmnElementRef="sid-5A44C718-3092-4E92-B9DA-FBAEECFEBCE7">
<dc:Bounds height="22.0" width="14.0" x="562.8873349228465" y="269.9403961583021"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="CMMNShape_sid-8598A958-0545-4B72-9ED9-41949C7D29AE" cmmnElementRef="sid-8598A958-0545-4B72-9ED9-41949C7D29AE">
<dc:Bounds height="22.0" width="14.0" x="640.1465486610895" y="298.9892034623539"></dc:Bounds>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNShape>
<cmmndi:CMMNEdge id="CMMNEdge_sid-6A541ED8-804E-40BD-A483-047854FBA696" cmmnElementRef="planItem5" targetCMMNElementRef="sid-8598A958-0545-4B72-9ED9-41949C7D29AE">
<di:waypoint x="475.0" y="519.0"></di:waypoint>
<di:waypoint x="643.2723428030378" y="314.87248908607694"></di:waypoint>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNEdge>
<cmmndi:CMMNEdge id="CMMNEdge_sid-F7555CE7-086C-4668-B1B4-AE65111AAA85" cmmnElementRef="planItem3" targetCMMNElementRef="sid-73978ECD-71BE-4615-A2CE-1755C3AFDA44">
<di:waypoint x="392.6918918918919" y="359.0"></di:waypoint>
<di:waypoint x="191.09234247504995" y="432.141416704221"></di:waypoint>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNEdge>
<cmmndi:CMMNEdge id="CMMNEdge_sid-B1BA6199-B1EE-43C1-99E3-BCADD993A5FE" cmmnElementRef="planItem3" targetCMMNElementRef="sid-5A44C718-3092-4E92-B9DA-FBAEECFEBCE7">
<di:waypoint x="506.0" y="222.91764705882352"></di:waypoint>
<di:waypoint x="566.1624448376436" y="275.7901940085478"></di:waypoint>
<cmmndi:CMMNLabel></cmmndi:CMMNLabel>
</cmmndi:CMMNEdge>
</cmmndi:CMMNDiagram>
</cmmndi:CMMNDI>
</definitions>
Points to note
Since we understand the case definition we can now take a look at what has been modified in java code to support the case execution. The basic structure of the application would remain the same as this is an extension to what we developed so far.
In our controller we have modified the endpoint to take in an action query param as we want to dynamically start the case instance using the action and hence trigger the correct stage. Controller looks like below.
@RestController
@RequestMapping("/v1/flowable-demo")
public class FlowableDemoController {
@Autowired
private final FlowableDemoService flowableDemoService;
private final ApplicationContext applicationContext;
public FlowableDemoController(FlowableDemoService flowableDemoService, ApplicationContext applicationContext) {
this.flowableDemoService = flowableDemoService;
this.applicationContext = applicationContext;
}
@GetMapping
public void getBooksForStudent (@RequestParam String studentName, @RequestParam String action) {
flowableDemoService.getBooksForStudent(studentName, action);
}
}
Our service was responsible so far for starting a BPMN process instance, following the same structure, it will now be responsible to start the case instance we have with correct variables setup as we obtain from our controller endpoint.
@Service
public class FlowableDemoService {
private final CmmnRuntimeService runtimeService;
public FlowableDemoService(CmmnRuntimeService runtimeService) {
this.runtimeService = runtimeService;
}
public void getBooksForStudent (String name, String action) {
runtimeService.createCaseInstanceBuilder()
.caseDefinitionKey("BookCaseV2")
.variable("STUDENT_ACTION", action)
.variable("student_name", name)
.start();
}
Let's now take a look at the wholesale and resale delegation tasks.
Wholesale task, similar to fulfilment task in our BPMN process, but the difference is the interface we implement instead of Java delegate we have PlanItemJavaDelegate. Rest here we simply set the action variable to make it enter the next stage.
@Component("wholeSaleTask")
public class WholeSaleTask implements PlanItemJavaDelegate {
@Override
public void execute(DelegatePlanItemInstance delegatePlanItemInstance) {
System.out.println("Got Action Request as " + delegatePlanItemInstance.getVariable("STUDENT_ACTION", String.class));
delegatePlanItemInstance.setVariable("STUDENT_ACTION", "PURCHASE");
}
}
Resale task is depicted below, here to show some difference in case lifecycle we have deliberately not set up the variable to enter the next stage.
@Component
public class ResaleTask implements PlanItemJavaDelegate {
@Override
public void execute(DelegatePlanItemInstance delegatePlanItemInstance) {
System.out.println("Got Action Request as " + delegatePlanItemInstance.getVariable("STUDENT_ACTION", String.class));
delegatePlanItemInstance.setVariable("STUDENT_ACTION", "NOTHING");
}
}
Rest all the task delegations for PurchaseBooksV2 BPMN process remain as we discussed in the last section on DMN.
Now we can execute one by one all the actions possible and also query the associated CMMN tables to note the differences.
Action as Wholesale
We made the request for student name abc, and action as WholeSale. Thus we got the below console output. From our decision table above we are aware that the standard resolution for student abc is X.
The execution history for this case instance is obtained by querying act_cmmn tables, similar to the dmn tabled that we had.
select * from act_cmmn_hi_case_inst;
We will see that this gives us the case_instance_id and the state i.e completed for this case instance, since this case instance was completed we don't see the entry alive in the act_cmmn_ru_case_inst table(ru are runtime tables as discussed during the schema nomenclature in bpmn section). We will just take the id from here and then see the plan item details.
select * from act_cmmn_hi_plan_item_inst where case_inst_id_ = '6510164f-3e9b-11ee-b8a1-ce8164bf8a11';
This gives us the state of various plan items that we noted while describing case definition, along with their unique ids to query the process tables and link all the elements. Here we see resale is terminated due to the entry sentry no met thus caused its termination, because of auto-complete turned on for the inventory updation stage, with even one task completed the stage went into completion.
Next we will query the procinst table that we did while understanding BPMN and will use the highlighted id (above) as the callback id as this process was called from a case.
select * from act_hi_procinst where callback_id_ = '651a769b-3e9b-11ee-b8a1-ce8164bf8a11';
Next we are on same lines as discussed while understanding BPMN and DMN history table queries, we will use this proc instance id for checking the activities history and their associated details.
Action as Resale
Next we execute the endpoint again, just changing the action as resale, this time BPMN process will not be triggered as the entry sentry is not met. Highlighted is the output on terminal.
Lets now see the execution state results by querying the same tables as we did for wholesale.
First we check the history table and it has the state of case as active. As per the lifecycle it reached the active state post being available. Since this is active case instance we will find it in the ru (i.e runtime table as well).
select * from act_cmmn_ru_case_inst;
Let's now take a look at plan item table.
select * from act_cmmn_hi_plan_item_inst where case_inst_id_ = '7023a40b-3e9f-11ee-b8a1-ce8164bf8a11';
Output of the above query shows that since Purchase stage was in available state and was not triggered and hence did not get completed, our case instance did not complete as well(case will complete when all plan items complete). Wholesale was not activated hence it got terminated, because of auto-complete on inventory updation stage, with even one task completed it went into completion.
Action as Purchase
Now we will execute our last action as Purchase, this will directly trigger the Purchase stage and will not go through the InventoryUpdation stage as we defined in our case definition.
Highlighted is our output on terminal. As we can see it did not trigger any of the WholeSale or Resale tasks.
When we query the act_cmmn_hi_case_inst table we can see the case is completed this time, hence the ru table has no alive entry.
Let's now check the plan items history table to see the sates of each plan item
One point to note here is that both the wholesale and resale tasks were terminated as they were never activated due to attached sentry not meeting. Rest all the plan items were completed.
Point to note: Our milestone plan item is always completed since, it was in or condition between completion event from Purchase or InventoryUpdation Stage.
Summary
We understood the role of triple crown elements, we also got to see the building blocks for each member of the triple crown. Here we also explored when to use which member and how to use them in conjunction to deliver more refined solutions for businesses.
Sources of Knowledge
Director of Software Engineering at American Express Global Business Travel
1 年Nice article Aneshka Goyal . Few things were new for me.
VP of Engineering, Amex GBT Marketplace and Country Lead, Amex GBT India
1 年Really good one Aneshka Goyal !!