BPMN allow us design complex computer readable business workflow diagram. Beside standard CRUD api, SimpleApp Generator allow flexible workflow by design bpmn
document and optionally bind to specific jsonschema
as easy access entry point.
At the moment, SimpleApp Generator using bpmn-server to process bpmn document, and map the business event to specific document listener
in run time.
bpmn-server is young and may change frequently in future and cause some break of compatibility, but it look promising and allow very tight integration with our backend (nestjs). There is BPMN Compatibility table
We recomend you to install vscode extension Camunda Modeler
from Miranium
.
Use BPMN in SimpleApp Generator involve below steps:
- Write definitions and generate codes
- Modify generated source code
- Ensure config.json declare bpmn folder,
{
... # others props
"bpmnFolder": "./workflows/bpmn", # empty will not generate workflow codes
... # others props
}
- Create a sample business process definitions file:
project1/workflows/bpmn/suspendcustomer.bpmn
- (optional) Define alternate api entry point
additionalApis
at existing jsonschema to start the bpmn process.
# example customer.json
{
"type": "object",
"x-simpleapp-config": {
"documentType": "cust",
"documentName": "customer",
"uniqueKey": "customerNo",
"documentTitle": "customerName",
"generateDocumentNumber": true,
"isolationType": "org",
"pageType": "crud",
"additionalApis":[
{
"action":"suspend",
"entryPoint":":id/suspend",
"requiredRole":["Customer_update"],
"method":"patch",
"workflowSetting":{
"bpmn":"suspendcustomer"
},
"description":"Suspend specific customer (set active=false)"
}
]
},
"properties": {
"_id": { "type": "string" },
"created": { "type": "string" },
"updated": { "type": "string" },
"createdBy": { "type": "string" },
"updatedBy": { "type": "string" },
"tenantId": { "type": "integer", "default": 1 },
"orgId": { "type": "integer", "default": 1 },
"branchId": { "type": "integer", "default": 1 },
"customerNo": {
"type": "string",
"format": "documentno",
"examples": ["S0001"]
},
"customerName": {
"type": "string",
"examples": ["Customer 1"],
"minLength": 3
},
"customerStatus": {
"type": "string",
"enum": ["unknown", "potential", "suspended", "existing"],
"examples": ["unknown"]
},
"email": {
"type": "string",
"oneOf": [{ "format": "email" }, { "enum": [""] }]
},
"tel": {
"type": "string",
"oneOf": [{ "format": "tel" }, { "enum": [""] }]
},
"active": { "type": "boolean", "default": true, "examples": [true] },
"description": { "type": "string", "format": "text" },
"docNoFormat": {
"type": "object",
"x-foreignkey": "docnoformat",
"properties": {
"_id": { "type": "string" },
"label": { "type": "string" }
}
}
}
}
- Generate (or regenerate) backend codes:
sh build.sh updatebackend
# observe generated code at below path:
# backend/src/simpleapp/services/cust.service.ts
# backend/src/simpleapp/workflows/bpmn/suspendcustomer.bpmn.ts
# backend/src/simpleapp/workflows/listeners/suspendcustomer.listener.ts
- restart backend service
- regenerate frontend codes (ensure backend regenerated and service restart):
sh build.sh updatefrontend
- restart frontend service
After source code generated, it add files into frontend and backend:
./src/simpleapp/workflows/bpmn/suspendcustomer.bpmn
: backend will use the bpmn to spawn workflow process./src/simpleapp/workflows/listeners/suspendcustomer.listener.ts
: this file will provide sample code of listen commonusertask
andservicetask
../src/simpleapp/services/cust.service.ts
: Ifcustomer.json
added additionalApis, after regenerate backendcustomer.service.ts
auto created new rest apirunSuspend
, which will route the execution to generate new workflow processsuspendcustomer
.
./simpleapp/workflows/bpmn/suspendcustomer.bpmn
: keep here for reference, frontend can render bpmn ui with this file./simpleapp/generate/clients/CustomerClient.ts
: added api entry pointrunSuspend
.
./src/simpleapp/workflows/listeners/suspendcustomer.listener.ts
provide sample listener code of below task:
Sample code listen on user task wait
& invoke
event. you can add more (support start,wait,invoke,end,assign
)
start
: task begin, seldom usewait
: workflow suspend and waiting user input. You may send notification to users base onprops:UserTaskData
. To trigger workflow continue we shall call apiworkflow/invoke-task/:taskId
invoked
: during user invoked process, you will obtain input data from actor. The workflow will go toend
then move to next taskend
: when current task completed, before move to next process (seldom use)assign
: during trigger when current task properties like assignee, candidateUsers, duedata changed. workflow status remain unchange.
Sample code listen on start
only, it support start
and end
. Event characteristic for start
& end
same with User task
- Remove
* --remove-this-line-to-prevent-override--
from top comment in./src/simpleapp/workflows/listeners/suspendcustomer.listener.ts
- Put in suspend customer source code by modify the file.
//plenty of import here
@Injectable()
export class SuspendcustomerListenerService extends SimpleAppListenerService {
logger = new Logger();
constructor() {
super();
}
@OnEvent('suspendcustomer.hello1.start')
async watch_hello1_start(props: ServiceTaskData) {
console.log('Running listener suspendcustomer.hello1.start', props);
}
}
to
//plenty of import here
import { CustomerService } from 'src/simpleapp/services/cust.service'; //import customer service class
@Injectable()
export class SuspendcustomerListenerService extends SimpleAppListenerService {
logger = new Logger();
constructor(private custService:CustomerService) { //inject custService object
super();
}
@OnEvent('suspendcustomer.hello1.start')
async watch_hello1_start(props: ServiceTaskData) {
const appuser = this.prepareAppUser(props.data) //obtain user info, and perform data isolation
const id = props.data._id
//find existing record and suspend it
const newdata = await this.custService.findById(appuser,id)
newdata.active=false
await this.custService.findIdThenUpdate(appuser,props.data._id,newdata) //save into database
}
}
type | status | description |
---|