Replies: 25 comments 5 replies
-
Hi
Sounds great
Please go ahead by providing more description and design information
Thanks
…Sent from my iPhone
On Sep 21, 2023, at 6:44 AM, Ks Tan ***@***.***> wrote:
I know that current backend for prototyping and not design for production use.
Since bpmn-server is pure backend engine I recommend to create new backend using nestjs. it offer benefit which included:
1. More robust design
2 bigger ecosystem and able to use lot of nestjs plug-in like sso authentication, swagger-ui, middleware
2. We have better way to manage complex workflow requirement
So for bpmn project like kogito they are using quarkus which can allow developer use plenty of quarkus plugin
If you agree we may work on it
—
Reply to this email directly, view it on GitHub<#118>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AQL5NQZ3I7MOPO6TBU7TCIDX3QK5ZANCNFSM6AAAAAA5BKO7NE>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
We may start from easier way like:
One comfortable we may create cli executor and user install it via "npm install -g bpmn-server-cli" Then setup and maintain the project using
|
Beta Was this translation helpful? Give feedback.
-
Great
Please go ahead with a separate branch
I will be done with a full release in couple of days
Thanks
…Sent from my iPhone
On Sep 21, 2023, at 6:15 PM, Ks Tan ***@***.***> wrote:
We may start from easier way like:
1. Create an kickstart git repo like "bpmn-nest-template"
2. Inside content latest nestjs project and appropriate route/service controller
3. Put some bpmn sample, delegates inside
4. Write read me for how to kick start:
* add more process definition
* change configuration via .env
* try api via swagger
* turn on api key or sso jwt token (like keycloak)
* how to build for production
* and etc
One comfortable we may create cli executor and user install it via "npm install -g bpmn-server-cli"
Then setup and maintain the project using
* bpmn-server-cli init my-proj
* bpm-server-cli start/stop
* bpmn-server-cli add-deligate uploads3 emailuser push-notification
* etc
—
Reply to this email directly, view it on GitHub<#118 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AC2XVKDC56NNLKV5TZ4CCD3X3S37HANCNFSM6AAAAAA5BKO7NE>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
I tried to create a loopback extension to use with the loopback backend framework |
Beta Was this translation helpful? Give feedback.
-
Sounds good, I will spend sometime check loop back is it good for me, |
Beta Was this translation helpful? Give feedback.
-
Can we think of Bpmn-server as a microservice that can be used in either framework? |
Beta Was this translation helpful? Give feedback.
-
IMHO bpmn act as purely backend microservices, it doesn't effect much as long as it comply openapi api standard. All languages and framework can tan to openapi easily. I not recommend bpmn server embed as library to support 2 or more framework cause it add burden to development for testing and documentation. Let's pick one and stick with it until perfect, than decide again. In typescript I only have experience in nuxt and nestjs, never try loopback framework so I can't give comment about loopback good or bad yet. Nest has build plenty of features I think suitable for industry grade application, bpmn-server adopt by industry most of the time so I feel we park inside good for us |
Beta Was this translation helpful? Give feedback.
-
Hi I am hoping that only the bpmn-client will be ported to lb4 with is minimal code while keeping bpmn-server as pure nodeJS Thanks, Let us keep the discussion going |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Thanks very much for the explanation, that looks great, the only challenge
I found is how to update bpmn-server?
Is there a way to use npm instead of copying the code into the library
folder?
Please let me know if you need any changes from my end.
Thanks
…On Fri, Sep 29, 2023 at 4:17 AM ingpconci ***@***.***> wrote:
Hi
I use the loopback as a client for bpmn-server, because I want to use the
loopback API framework to control authentication, filtering and connecting
to the relational database PostgreSQL.
I imported the library, created the server instance and used it in the
controllers.
I don't modify the code of bpmn-server and I change only the 4
customization files for the library
[image: image]
<https://user-images.githubusercontent.com/8488830/271501524-d3391dbc-077c-40e6-929c-bc952b5efcae.png>
The main effort is focused on defining a flexible system user task
assignment; to facilitate the acceptance of BPMN workflow, I use this
approach based on association between Lane in diagram and Use Role
[image: image]
<https://user-images.githubusercontent.com/8488830/271503879-75e83f02-dc30-4399-b692-c44f1d430cf2.png>
In the extension, I put the controller API endpoints to support the Role
management.
In the picture there is the Html interface.
[image: image]
<https://user-images.githubusercontent.com/8488830/271505081-3d894bfe-bd67-4569-8e53-876209e76ed4.png>
I would like to put in the loopback bpmn-server extension an API endpoint
that returns the information about the User Task Form.
At the moment, I define the BPMN process with camunda modeler and use the
camunda form definition.
[image: image]
<https://user-images.githubusercontent.com/8488830/271508462-7bd58c18-5a20-497c-9c04-949dc0d08d7f.png>
<bpmn:process id="Process_07ii97u" isExecutable="true">
<bpmn:extensionElements> <zeebe:userTaskForm id="userTaskForm_08ui781">{
"components": [ { "text": "<h2>(1.1) New nonconformity</h2>",
"label": "Text view", ….. }</zeebe:userTaskForm>
In my Html App, I have developed a form engine that uses this information
in the model definition to build the Form, but in the future I would like
to facilitate the construction of the form at server side. The function
must provide a form representation that permit to build a non Html form
(e.g. a standalone C++ app form engine)
[image: image]
<https://user-images.githubusercontent.com/8488830/271510032-234f3c78-4340-439d-bb09-ad7c11844421.png>
—
Reply to this email directly, view it on GitHub
<#118 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AQL5NQZQI3TIHAYQ2QGLRLLX4Z7YXANCNFSM6AAAAAA5BKO7NE>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
After view back your project seems u did extension for loopback only. Previously misunderstand and thought you host whole loopback application in repo. @bpmn-ts you may need to decide is loopback a good framework and qualify as firstclass citizen for bpmn or not. if yes we may update documentation according that approach. Else, i will spend times to do in nestjs. @ingpconci can you make your effort become npm package so it more portable? I'm excited actually because your may solve a big issue which can make bpmn-server use in production |
Beta Was this translation helpful? Give feedback.
-
I clone the project locally
Then compile and pack
After that I insert in the package.json
|
Beta Was this translation helpful? Give feedback.
-
To use in the main loopback application, I import these libraries into the application.ts
and insert the initialization code
Remember that the extension uses a multitenant authorization extension
The loopback4-tenant-table-filter permit to have a unique DB but different isolated organization When I start the process I set this instance variable in the process with the tenantId of the user
The bpmn server engine use this information for all operation in the DB in the custom datastore that uses the
|
Beta Was this translation helpful? Give feedback.
-
I believe the full App (Web UI, Server) can use any framework. but I would rather have bpmn-server not rely on any framework, because that would be restrictive. In other words I like that bpmn-server to remain on pure nodeJS and only offer ExpressJS for demo purposes only? So the question is what features in bpmn-server can be enhanced using these frameworks? Any thoughts? |
Beta Was this translation helpful? Give feedback.
-
I don't think it is good ideal to maintain as independent express due to below reason
Current project quality, completeness, reliability yet to achieve perfect. In this situation flexible is kind of no-no and drag the progress. Why don't Make everything static at the time being until near perfect, then decide again in future? |
Beta Was this translation helpful? Give feedback.
-
Anyhow, it is my personal perspective, which I believe people wish to find robust bpmn framework rather than bpmn library. I'm ok with using someone bpmn framework, as long as reliable and suitable |
Beta Was this translation helpful? Give feedback.
-
Not sure I understand your recommendation, can you please clarify what do you recommend, keep in mind Express is only used for demo purposes |
Beta Was this translation helpful? Give feedback.
-
Technically most of time the users want something prepackaged for production use, included me. For production package, it need:
Some of above requirement will need the project to settle down best practice of implement bpmn, in this case flexibility may not a good ideal. We may rather put that as advance topic like how to use others framework (if it is necessary) |
Beta Was this translation helpful? Give feedback.
-
When I chose the bpmn-server library, I checked other libraries and full frameworks like Camunda Products.
I agree that a reference framework could benefit the development of the bpmn-server library. I am also open to discussing the possibility of making my main loopback application service, along with the HTML angular client, open source. This could be useful for managing and interacting with the bpmn server. |
Beta Was this translation helpful? Give feedback.
-
@ingpconci Thanks very much for you comments and contribution. I definitely appreciate any additional contribution to enhance the Client UI. As for the server, would a package like Memcached help solve the scalability issue? Thanks again |
Beta Was this translation helpful? Give feedback.
-
@ingpconci , I also noticed from your code of support mySQL is using an older version of the code, and I don't see you saving tokens into the instance. I can support mySQL as follows:
|
Beta Was this translation helpful? Give feedback.
-
@ralphhanna , I updated the bpmn-server on my installation with the last version, and I see that there is a new Class InstanceLocker. I need to change the DefaultConfiguration.ts and add a method to permit a custom locker? During the first test , I've found a bug in the Definition.ts:107:34.
|
Beta Was this translation helpful? Give feedback.
-
The intent is instance_locker be private to the dataStore |
Beta Was this translation helpful? Give feedback.
-
Nestjs using
Below is the /**
* This file execute workflow bpmn-server command
* This file was automatically generated by simpleapp generator.
* DONT CHANGE THIS FILE !!
* MODIFICATION OVERRIDE BY GENERATEOR
* last change 2023-10-28
* Author: Ks Tan
*/
import { WorkflowConfig } from './workflow.config';
import {
BadRequestException,
Injectable,
InternalServerErrorException,
Logger,
NotFoundException,
} from '@nestjs/common';
import {
WorkflowSettingApiSchema,
WorkflowDataApiSchema,
WorkflowProcess,
WorkflowTask,
UserTaskActors,
UserTaskType,
ServiceTaskData,
UserTaskData,
UserTaskEventType,
ServiceTaskEventType,
} from './workflow.type';
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
import {
BPMNServer,
Logger as bpmnlogger,
BPMN_TYPE,
EXECUTION_EVENT,
} from 'bpmn-server';
// import { configuration } from './configuration';
import { UserContext } from '../commons/user.context';
//import * as formschemas from '../../workflow/formschema';
export {
WorkflowSettingApiSchema,
WorkflowDataApiSchema,
} from './workflow.type';
@Injectable()
export class WorkflowService {
protected bpmnServer: BPMNServer;
private readonly logger = new Logger();
constructor(
private workflowconfig: WorkflowConfig,
private eventEmitter: EventEmitter2,
) {
this.bpmnServer = new BPMNServer(this.workflowconfig.getConfig());
this.bpmnServer.listener.on(
'all',
async (event) => await this.applyEventListener(event),
);
}
/**
* get all workflow userTask for specific uid (uuid)
* @param appuser
* @returns array of usertask
*/
async getActorUserTask(appuser: UserContext, uid: string) {
const tmp: any[] = await this.bpmnServer.dataStore.findItems({
'items.status': 'wait',
'data.tenantId': appuser.getTenantId(),
//filter uid properties base on function
$or: [
{ 'items.assignee': appuser.getUname() },
{ 'items.candidateUsers': appuser.getUname() },
{ 'items.candidateGroups': appuser.getGroup() },
],
});
const result: UserTaskType[] = tmp.map((proc) => {
const subitem: UserTaskType = {
id: proc.instanceId,
taskId: proc.id,
elementId: proc.elementId,
name: proc.name,
processName: proc.processName,
assignee: proc.assignee,
candidateUsers: proc.candidateUsers,
candidateGroups: proc.candidateGroups,
data: proc.data,
vars: proc.vars,
startedAt: proc.startedAt,
followUpDate: proc.followUpDate,
dueDate: proc.dueDate,
priority: proc.priority,
type: proc.type,
userId: proc.userId,
};
return subitem;
});
return result;
// throw new BadRequestException(`getActorUserTask is not supported yet`)
}
/**
* get all workflow userTask for my uid (uuid)
* @param appuser
* @returns array of usertask
*/
async getMyUserTask(appuser: UserContext) {
return await this.getActorUserTask(appuser, appuser.getUid());
}
/**
* !! waiting founder
* deveploper method
* create new process definitions
*/
async newWorkflowDefinitions(
appuser: UserContext,
workflowName: string,
xml: string,
) {
throw new BadRequestException(
`newWorkflowDefinitions "${workflowName}" is not supported yet`,
);
}
async getWorkflowDefinitions(appuser: UserContext) {
throw new BadRequestException(
`getWorkflowDefinitions is not supported yet`,
);
}
async readWorkflowDefinition(appuser: UserContext, workflowname: string) {
throw new BadRequestException(
`readWorkflowDefinition is not supported yet`,
);
}
async deleteWorkflowDefinition(appuser: UserContext, workflowname: string) {
throw new BadRequestException(
`deleteWorkflowDefinition is not supported yet`,
);
}
async updateWorkflowDefinition(
appuser: UserContext,
workflowname: string,
xml: string,
) {
throw new BadRequestException(
`updateWorkflowDefinition is not supported yet`,
);
}
/**
* admin workflow service, obtain all active workflows and it's pending task from all tenant
* status: done
* @param appuser
* @returns array of workflow process and it's waiting task
*/
async getAllActiveProcesses(appuser: UserContext) {
const alldata = await this.bpmnServer.dataStore.findInstances(
{ status: 'running' },
'summary',
);
const result = this.workflowProcessArrayMaping(alldata);
return result;
}
/**
* delete a workflow process from database
* @param appuser
* @param id
* @returns
*/
async deleteWorkflowById(appuser: UserContext, id: string) {
if (await this.getWorkflowById(appuser, id)) {
const result = await this.bpmnServer.dataStore.deleteInstances({
id: id,
});
return {
status: 'ok',
deletedCount: result['deletedCount'],
};
}
}
/**
* invoke workflow process to next step
* @param appuser
* @param id
* @param taskId
* @param data
* @returns
*/
async invokeWorkflowUserTask(
appuser: UserContext,
taskId: string,
data: any,
) {
try {
const response = await this.bpmnServer.engine.invoke(
{ 'items.id': taskId, 'items.status': 'wait' },
data,
appuser.getUid(),
{ appuser: appuser },
);
return {
parentKeys: Object.keys(response),
parentId: response.id,
data: response.instance.data,
// parentId:response,
input: response.item.input,
vars: response.item.vars,
id: response.item.id,
status: response.item.status,
};
} catch (e) {
throw new NotFoundException(e);
}
}
/**
* !! waiting founder
* use in adhoc situation override bpmn assignee/candidateUsers/Groups with data
* status: draft
* @param appuser
* @param id
* @param taskId
* @param data
*/
async updateWorkflowUserTask(
appuser: UserContext,
id: string,
taskId: string,
assignmentdata: UserTaskActors,
) {
try {
const result = await this.bpmnServer.dataStore.findInstance(
{ id: id },
{},
);
const usertask = result.items.find((item) => item.id == taskId);
if (usertask) {
const result = await this.bpmnServer.engine.assign(
{ id: id, 'items.id': taskId },
{ newData: 1 }, //some data want to put in
assignmentdata,
appuser.getUid(),
{}, //options
);
return 'OK'; //Object.keys(result)
} else {
throw new NotFoundException(`user task id:${taskId} not found`);
}
} catch (e) {
throw new NotFoundException(e);
}
// throw new BadRequestException(`updateWorkflowUserTask is not supported yet`)
}
/**
*
* @param appuser search all workflow in current tenant
* @param filter
* @returns
*/
async searchRunningWorkflowProcesses(appuser: UserContext, filter: any) {
// filter['data.tenantId']= appuser.getTenantId() //tmp disabled waiting
filter['status'] = 'running';
return await this.searchWorkflowProcesses(appuser, filter);
}
/**
*
* @param appuser search all workflow in current tenant
* @param filter
* @returns
*/
async searchWorkflowProcesses(appuser: UserContext, filter: any) {
filter['data.tenantId'] = appuser.getTenantId(); //tmp disabled waiting
const alldata = await this.bpmnServer.dataStore.findInstances(
filter,
'summary',
);
const result = this.workflowProcessArrayMaping(alldata);
return result;
}
async getPendingProcess(appuser: UserContext) {
const tmp: any[] = await this.bpmnServer.dataStore.findItems({
'items.status': 'wait',
});
const result = tmp.map((proc) => ({
id: proc.instanceId,
assignee: proc.assignee,
candidateUsers: proc.candidateUsers,
candidateGroups: proc.candidateGroups,
data: proc.data,
processName: proc.processName,
startedAt: proc.startedAt,
type: proc.type,
taskName: proc.name,
userId: proc.userId,
}));
return result;
}
/**
* initialise new workflow process, also listening event 'workflow.start'
* status: done
* @param appuser
* @param workflowName
* @param data
* @returns workflowsummary
*/
@OnEvent('workflow.start')
async startWorkflow(appuser: UserContext, workflowName: string, data?: any) {
try {
// console.log('startWorkflow started: ', workflowName);
data.tenantId = appuser.getTenantId();
data.orgId = appuser.getOrgId();
data.branchId = appuser.getBranchId();
const result = await this.bpmnServer.engine.start(
workflowName,
data,
undefined,
appuser.getUid(),
{
appuser: appuser,
},
);
// console.log("startWorkflowstartWorkflowstartWorkflow",result)
return {
id: result.id,
name: result.name,
status: result.status,
userId: result.userName,
startedAt: result.item.startedAt,
};
} catch (e) {
throw new BadRequestException(e, JSON.stringify(data));
}
}
/**
* read single process full property
* @param appuser
* @param id
* @returns
*/
async getWorkflowById(appuser: UserContext, id: string) {
try {
const data = await this.bpmnServer.dataStore.findInstance({ id: id }, {}); // .findInstances({id:id},'full')
const result = this.workflowProcessMaping(data);
return result;
} catch (e) {
throw new NotFoundException(e);
}
}
async getPendingWorkflows(appuser: UserContext) {
const tmp: any[] = await this.bpmnServer.dataStore.findItems({
'items.status': 'wait',
});
const result = tmp.map((proc) => ({
id: proc.instanceId,
assignee: proc.assignee,
candidateUsers: proc.candidateUsers,
candidateGroups: proc.candidateGroups,
data: proc.data,
processName: proc.processName,
startedAt: proc.startedAt,
type: proc.type,
taskName: proc.name,
userId: proc.userId,
}));
return result;
}
/**
* obtain specific bpmn task camunda:formKey
* @param appuser
* @param processName
* @param elementId
* @returns properties
*/
async getWorkflowUserTaskFormKey(
appuser,
processName: string,
elementId: string,
) {
try {
let definition = await this.bpmnServer.definitions.load(processName);
let node = definition.getNodeById(elementId);
if (node) {
const formKey = node.def.formKey;
let schema: string = node.def.formKey;
if (schema) {
return {
processName: processName,
elementId: elementId,
schema: schema,
};
} else {
throw new InternalServerErrorException(
`Cannot resolve schema from invalid formKey '${schema}', example: 'jsonschema://SimpleApproveReject' or 'custom://your-custom-key'`,
);
}
} else {
throw new NotFoundException(
`Element ${elementId} not found in process ${processName}`,
);
}
} catch (e) {
throw new InternalServerErrorException(e);
}
}
// helper
workflowProcessArrayMaping(alldata: any[]) {
const result = alldata.map((data) => this.workflowProcessMaping(data));
return result;
}
workflowProcessMaping(data: any) {
const result: WorkflowProcess = {
id: data.id,
name: data.name,
status: data.status,
startedAt: data.startedAt,
items: data.items
.filter((item) => item.type != 'bpmn:SequenceFlow')
.map((item) => ({
taskId: item.id,
name: item.name,
status: item.status,
assignee: item.assignee,
candidateUsers: item.candidateUsers,
candidateGroups: item.candidateGroups,
type: item.type,
})),
};
return result;
}
/**
* listen all events in bpmn-server engine, inclduded task and execution instances
* @param event bpmn-server event instance
*/
async applyEventListener(event) {
if (event.context.item) {
const workflowName: string = event.context.instance.name;
const eventType: EXECUTION_EVENT = event.event;
const elementType: BPMN_TYPE = event.context.item.element.type;
const elementId: string = event.context.item.element.id;
const elementName: string = event.context.item.element.name;
const elementProps = event.context.item.element.def;
// const usertaskinput = event.context.item.input ?? null;
const data = event.context.instance.data;
const vars = {};
Object.assign(vars, event.context.item.input);
event.context.item.input = {};
const options = event.context.options;
switch (elementType) {
case BPMN_TYPE.UserTask:
let usertaskeventtype: UserTaskEventType;
if (eventType == EXECUTION_EVENT.node_start)
usertaskeventtype = 'start';
else if (eventType == EXECUTION_EVENT.node_assign)
usertaskeventtype = 'assign';
else if (eventType == EXECUTION_EVENT.node_end)
usertaskeventtype = 'end';
else if (eventType == EXECUTION_EVENT.transform_input)
usertaskeventtype = 'invoked';
else if (eventType == EXECUTION_EVENT.node_wait)
usertaskeventtype = 'wait';
// console.log("event-------=================================",eventType,usertaskeventtype)
if (usertaskeventtype) {
const props: UserTaskData = {
workflowName: workflowName,
eventType: usertaskeventtype,
elementType: 'bpmn:UserTask',
elementId: elementId,
elementName: elementName,
elementProps: {
startedAt: event.context.item.startedAt,
assignee: event.context.item.assignee,
candidateGroups: event.context.item.candidateGroups,
candidateUsers: event.context.item.candidateUsers,
dueDate: event.context.item.dueDate,
followUpDate: event.context.item.followUpDate,
priority: event.context.item.priority,
formKey: elementProps.formKey,
},
data: data,
vars: vars,
options: options,
};
this.eventEmitter.emit(
`${workflowName}.${elementId}.${usertaskeventtype}`,
props,
);
}
break;
case BPMN_TYPE.ServiceTask:
let servicetaskeventtype: ServiceTaskEventType;
if (eventType == EXECUTION_EVENT.node_start)
servicetaskeventtype = 'start';
else if (eventType == EXECUTION_EVENT.node_end)
servicetaskeventtype = 'end';
if (servicetaskeventtype) {
const props: ServiceTaskData = {
workflowName: workflowName,
eventType: servicetaskeventtype,
elementType: 'bpmn:ServiceTask',
elementId: elementId,
elementName: elementName,
data: data,
vars: vars,
options: options,
};
this.eventEmitter.emit(
`${workflowName}.${elementId}.${servicetaskeventtype}`,
props,
);
}
break;
default: //do nothing at the moment
break;
}
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Inside is empty, i'm don't know what way you wish to do:
the purpose of previous message is to share with you implement |
Beta Was this translation helpful? Give feedback.
-
I know that current backend for prototyping and not design for production use.
Since bpmn-server is pure backend engine I recommend to create new backend using nestjs. it offer benefit which included:
Others bpmn project like kogito, they did similar and are they use quarkus which can allow developer use plenty of quarkus plugin.
I use it sometimes but I can't stay with them cause it is too rigit
If you agree we may work on it
Beta Was this translation helpful? Give feedback.
All reactions