In this tutorial, we’ll see how to initiate a springboot project with flowable.
We will build an API to make a pony says "hello <name>" or "Eeyup" if the pony is Big McIntosh
Dependencies
-
for springboot : web, data-jpa, test
-
for flowable : flowable-spring-boot-starter-basic
-
for database : h2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<flowable.version>6.2.0</flowable.version>
</properties>
<dependencies>
<!-- Springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Flowabe -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-basic</artifactId>
<version>${flowable.version}</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Test -->
<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>
|
Goals
We want to resolve those two tests :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
@RunWith(SpringRunner.class)
@SpringBootTest
public class PonyControllerTest {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
final PonyController ponyController = new PonyController(runtimeService, historyService);
this.mockMvc = MockMvcBuilders.standaloneSetup(ponyController)
.build();
}
@Test
public void sayHello() throws Exception {
// Init
Pony pony = new Pony("Daring Do");
pony = ponyRepository.save(pony);
// Do
this.mockMvc.perform(post("/ponies/{ponyId}/sayHello", pony.getId()))
// Validate
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Hello Daring Do"));
// Teardown
ponyRepository.delete(pony.getId());
}
@Test
public void sayEeyup() throws Exception {
// Init
Pony pony = new Pony("Big McIntosh");
pony = ponyRepository.save(pony);
// Do
this.mockMvc.perform(post("/ponies/{ponyId}/sayHello", pony.getId()))
// Validate
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Eeyup"));
// Teardown
ponyRepository.delete(pony.getId());
}
}
|
Domain model
We will create all required class for our domain.
All objects used on a workflow must implement Serializable.
We will use two models, an entity Pony with an id and a name and an object message with is content.
Pony.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Entity
public class Pony implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
private String name;
public Pony(String name){
this.name = name;
}
}
|
Message.java
1
2
3
|
public class Message implements Serializable {
private String message;
}
|
PonyRepository.java
1
|
public interface PonyRepository extends JpaRepository<Pony, Long> { }
|
Services
We will need three methods on PonyService :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Service
@Transactional
public class PonyService {
private PonyRepository ponyRepository;
public PonyService(final PonyRepository ponyRepository) {
this.ponyRepository = ponyRepository;
}
public Pony getOne(long id) throws FunctionalException {
return ponyRepository.findOne(id);
}
public Message sayEeyup(){
return new Message("Eeyup");
}
public Message sayHello(Pony pony){
return new Message("Hello " + pony.getName());
}
}
|
Workflow
Now we will build the workflow.
It will be pretty simple we will have :
-
a StartEvent as an entry point of the workflow
-
a ServiceTask to get the pony by calling PonyService#getOne
-
an ExclusiveGateway which will lead to :
-
an EndEvent to end the workflow
StartEvent
The StartEvent also called "None Start Event" is a default start event. It will be used when we will start the workflow through the API.
ServiceTask
ServiceTask has a three-way to call a java :
-
Java class where one JavaDelegate = 1 function
-
Expression where we can call beans
-
Delegate expression when we need to do for example field injection
In our case, we will use Expression.
With Expression we will be able to call a bean method with ${myBean.method(variable)}
We will use resultVariableName to store the return of the method to a workflow variable.
ExclusiveGateway
An ExclusiveGateway will work like a switch with break on each case.
Sequence flow condition will be evaluates one by one and the first one with an evaluation to true will be used.
If no condition is resolved as true, it will follow the default flow (which must have no condition).
EndEvent
An EndEvent (also called None End Event) will terminate the workflow as success.
The execution of the workflow will be stopped.
BPMN file
The .bpmn open on text editor will look like :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.zomzog.fr/blog" id="m1511380610321" name="">
<process id="sayHello" name="Say hello process" isExecutable="true" isClosed="false" processType="None">
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<serviceTask id="getPonyDetails" name="Get pony details" activiti:expression="${ponyService.getOne(ponyId)}" activiti:resultVariableName="pony"></serviceTask>
<serviceTask id="sayEeyup" name="Say Eeyup" activiti:expression="${ponyService.sayEeyup()}" activiti:resultVariableName="result"></serviceTask>
<serviceTask id="sayHello" name="Say Hello" activiti:expression="${ponyService.sayHello(pony)}" activiti:resultVariableName="result"></serviceTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="getPonyDetails"></sequenceFlow>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway" default="flow4"></exclusiveGateway>
<sequenceFlow id="flow2" sourceRef="getPonyDetails" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow3" name="Big McIntosh" sourceRef="exclusivegateway1" targetRef="sayEeyup">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${pony.name == 'Big McIntosh'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" name="Default" sourceRef="exclusivegateway1" targetRef="sayHello"></sequenceFlow>
<sequenceFlow id="flow5" sourceRef="sayEeyup" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="sayHello" targetRef="endevent1"></sequenceFlow>
</process>
</definitions>
|
All bpmn files on src/main/resources/processes are automatically deployed on server startup.
Rest
We now need rest endpoints for start the process.
We need to create the map of variables which will be used by the process.
We will start the process by using the flowable runtimeService.
And at the end, we will use the historyService to access to the ended process variables.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@RestController
@RequestMapping("/ponies")
public class PonyController {
private RuntimeService runtimeService;
private HistoryService historyService;
@PostMapping("/{ponyId}/sayHello")
public ResponseEntity<Message> sayHello(@PathVariable("ponyId") Long ponyId) {
Map<String, Object> variables = new HashMap<>();
variables.put("ponyId", ponyId);
final ProcessInstance process = runtimeService.startProcessInstanceByKey("sayHello", variables);
final HistoricVariableInstance hvi = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(process.getId())
.variableName("result").singleResult();
Message message = (Message) hvi.getValue();
return ResponseEntity.ok(message);
}
}
|