bpmn-elements icon indicating copy to clipboard operation
bpmn-elements copied to clipboard

Export internal Behaviour classes

Open sumbricht opened this issue 1 year ago • 5 comments

Hi

I'm using bpmn-engine to execute the whole process flow in my application, but the execution of any script expressions (e.g. the code of a ScriptTask or the condition of a SequenceFlow) needs to be executed by my application. Furthermore, I need a bit more control over how activities are generally executed.

As a proof of concept, I have therefore copied & pasted the implementation of ScriptTaskBehaviour and used my custom implementation in the engine, which is working well so far. Now I'm not a big fan of copying and pasting and would like to do this in a cleaner way by extending the default ScriptTaskBehaviour rather than copying it over.

For this to work, the package would however need to export the Behaviour classes, which it currently doesn't. Would you be open to changing this minor detail?

For reference, what I'm currently doing:

const engine = Engine({
	source,
	listener,
	elements: {
		ScriptTask: MyScriptTask,
	}
})

// -------------------------

import * as BpmnElements from 'bpmn-elements'
export function MyScriptTask(activityDefinition: any, context: any) {
	return new (BpmnElements as any).Activity(MyScriptTaskBehaviour, activityDefinition, context)
}

class MyScriptTaskBehaviour {
	loopCharacteristics
	constructor(private activity, private context) {
		this.loopCharacteristics = activity.behaviour.loopCharacteristics && new activity.behaviour.loopCharacteristics.Behaviour(this.activity, activity.behaviour.loopCharacteristics);
	}
	
	async execute(executeMessage) {
		const { content } = executeMessage
		const { id, type, broker, behaviour } = this.activity
		const { environment } = this.context
		
		if (this.loopCharacteristics && content.isRootScope) {
			return this.loopCharacteristics.execute(executeMessage);
		}
		
		try {
			await myCustomFunctionProvidedByMyApplication()
			broker.publish('execution', 'execute.completed', { ...content })
		} catch(err) {
			// handle error according to https://github.com/paed01/bpmn-engine/issues/182
		}
	}
}

and what I'd like to do instead:

const engine = Engine({
	source,
	listener,
	elements: {
		ScriptTask: MyScriptTask,
	}
})

// -------------------------

import { Activity, ScriptTaskBehaviour } from 'bpmn-elements' // Activity is already exported but not included in the .d.ts type declarations
export function MyScriptTask(activityDefinition: any, context: any) {
	return new Activity(MyScriptTaskBehaviour, activityDefinition, context)
}

class MyScriptTaskBehaviour extends ScriptTaskBehaviour {
	async execute(executeMessage) {
            // same code as in the previous example
	}
}

PS: I know that there's little code reuse in the above example as ScriptTaskBehaviour doesn't contain much more than the execute method, but I'll also need to override the behaviours of a few other similar cases where it's not as straight-forward (e.g. StartEventBehaviour).

Thanks for your support and the great work you've already put into this!

sumbricht avatar Jan 29 '24 13:01 sumbricht

I see your point. I'll attempt to achieve this in an elegant way.

paed01 avatar Jan 30 '24 05:01 paed01

I have published the new version as rc, npm i bpmn-elements@rc.

Struggled with the type definitions so I don't dare to make it latest yet.

Exposed the behaviors categorised by type of activity, e.g:

import { ScriptTaskBehaviour } from 'bpmn-elements/tasks';
import { EndEventBehaviour } from 'bpmn-elements/events';
import { ParallelGatewayBehaviour } from 'bpmn-elements/gateways';

paed01 avatar Feb 03 '24 13:02 paed01

Thank you very much for these changes :-), this now works much better for my use case. Special thanks for creating all these TS declarations that truly help understand the internals of this library and use it much more easily. The only remaining place I currently have to monkey-patch something is the Activity; as this is exported as interface and not as class, I'm using this slightly nasty hack:

import * as BpmnElements from 'bpmn-elements'
import { Activity, IActivityBehaviour } from 'bpmn-elements/types'

// a bit of hacky type manipulation...
const Activity = (BpmnElements as any).Activity as new (Behaviour: IActivityBehaviour, activityDef: any, context: any) => Activity

export function MyScriptTask(activityDefinition: any, context: any) {
  // ... so that I don't have any hacks here
  return new Activity(MyScriptTaskBehaviour, activityDefinition, context)
}

A quick win could be to turn Activity from interface into a class and add a constructor (ideally with better type annotations for activityDef/context):

declare class Activity extends Element<Activity> {
  constructor(Behaviour: IActivityBehaviour, activityDef: any, context: any)
  ...
}

So much for my inputs :-). Again, I'm super happy and thankful for your changes!

sumbricht avatar Feb 07 '24 10:02 sumbricht

The new version should now expose Activity as a class - npm i bpmn-elements@latest. Feel free to try it out.

paed01 avatar Feb 14 '24 10:02 paed01

Any luck with the type definitions?

paed01 avatar Mar 26 '24 14:03 paed01