The goal is to design a simple engine in such a way that it can plugged and played for Rails application. Once it's stable enough, it will be ported to another platform.
- postgres 9.4 or above because it requires jsonb datatype.
Please download http://camunda.org/bpmn/tool/ to create the BPMN model.
If you want to define gateway, in modeler, click on exclusive gateway node and go to Properties
> Extensions
> add key/value to define your desired flows.
You can define service hook by add the attribute of Properties
> Listeners
> Execution Listeners
or Task Listeners
. Currently, we only support Class
method of invoking
You can inject data at each node by referring to its ID. See example below:
To setup, you need to migrate the schema
bundle exec rake process_engine:install:migrations
This is the definition of a process residing in ProcessEngine::ProcessDefinition
To get all definitions
pds = ProcessEngine::ProcessDefinition.all
This is the instance of a process definition residing in ProcessEngine::ProcessInstance
To get all process instances
pd = ProcessEngine::ProcessDefinition.last
pds = pd.process_instances
This is the node of a process definition residing in ProcessEngine::ProcessDefinitionNode
To get all nodes
pd = ProcessEngine::ProcessDefinition.last
pdns = pd.process_definition_nodes
This is the instance of a process instance and process definition node residing in ProcessEngine::ProcessTask
. This is the place where we can query tasks of a user/assignee.
Each node can have properties to define branching and other metas. For example:
To inject data at branching node, add implementation to app/node_data_injection/
.
# file: /app/process_hooks/appointment_hook.rb
class AppointmentHook
class << self
# [required] to distinguish the namespace
def process_definition_slug
"tuansing"
end
# [required] to tell what to implement
def implemented_node_ids
[:process_appointment, :handover_gateway]
end
# [required] to forward the implementation
def forward_implementation_method(node_id, source_object)
# source_object could be process_instance_object (when it's movable node)
# source_object could be task_process_object (when it's task node)
send("implement_#{node_id}", source_object)
end
def implement_process_appointment(process_instance_object)
# { assignee: "random_assignee", candidate_users: ["chea", "lim"], candidate_groups: ["sale", "marketing"] } # return this node option here
{ assignee: "reamream"}
end
def implement_handover_gateway(process_instance_object)
task = process_instance_object.last_process_task_by_state_name("process_appointment")
value = task.data["handover_gateway_choice"] || ["cancel", "submit"].sample
{ exclusive_gateway_choice_value: value } # return this node option here
end
end
end
To load this class, we need to include in initializer:
# file: /config/initializers/data_injection_loader.rb
ProcessEngine::NodeDataInjection.injected_classes = [AppointmentHook]
For development you can inject this in application.rb
# file: /config/application.rb
#### blah blah
module AppNameHere
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.to_prepare do
ProcessEngine::NodeDataInjection.injected_classes = [AppointmentHook]
end
end
end
For each User Task
type, you can service hooks for executionListener
and taskListener
. Hooks are planted in process task for starting and ending of the process task.
listener | modeler events | supported events | modeler types | supported types |
---|---|---|---|---|
executionListener | start, end | start, end | class, delegate expression, expression, script | class |
taskListener | create, assignment, complete | create, complete | class, delegate expression, expression, script | class |
You can just define in your modeler and implement it anywhere. e.g.
# file at /app/process_engine_hooks/sample_process_task_hook.rb
class SampleProcessTaskHook
# need to define 'self.execute' so it can be executed
def self.execute(process_task)
puts 'this is a service hook'
end
end
All queries should be done through ProcessEngine::ProcessQuery
The signature is
# available options: :finisher, :verified_state
def task_complete(task_id, options = {})
# ...
end
finisher
: The person who complete this taskverified_state
: The state of the task to make sure that call the right corresponding task. Silence if not includeddata
: Hash value to save into process task's state
The signature is
# available options: :assignee, :candidate_user, :all_candidate_users, :any_candidate_users, :all_candidate_groups, :any_candidate_groups, :user, :user_or_groups, :status
def task_get_all(options = {})
# ...
end
assignee
: primary assigneecandidate_user
: single person in candidate usersall_candidate_users
: all people must be in candidate usersany_candidate_users
: anyone in candidate usersall_candidate_groups
: all groups must be in candidate groupsany_candidate_groups
: any group in candidate groupsuser
: single person who is assignee OR in candidate usersuser_or_groups
: single person who is assignee OR in candidate users OR any group in candidate groups. e.g.options[:user_or_groups] = ['assignee_name', ['group1', 'group2']]
status
: status of tasks (pending
orfinished
)
The signature is
def task_accessible?(task_id, user, groups = [])
# ...
# return boolean
end
This is to check if a user which has user name as user
or belongs to group array groups
has access to a task id task_id
The signature is
# available options:
def process_instance_start(process_defintion_slug, creator)
# ...
# return instance of ProcessEngine::ProcessInstance
end
You can easily use ruby ready-api in ProcessEngine::ProcessTask#consumer_task
which corresponds to ProcessEngine::Consumer::ConsumerTask
. The methods include
external_ref
: defined inExtensions
asexternal_ref
in modeler.
You can include built-in json api helper to make faster standard api usage.
class Api::V2::PeApisController < Api::ApiController
# to include built-in api method implementation
include ProcessEngine::Api::Base
# allowed methods
include_apis :process_instance, :process_definitions, :process_instance_start, :process_tasks
# filter for any above method to authenticate/cut cross logic
before_action :before_action_process_instance , only: [:process_instance]
# process_instance requires params[:process_instance_id]
# process_instance_start requires params[:process_definition_id]
private
def before_action_process_instance
render json: { status: "dummy that you need to modify here" }
end
# required by process_tasks typed Hash
# check more options in process query section
def process_task_options
{
user: "user_123"
}
end
# required by process_instance_start typed String
# check more options in process query section
def process_instance_start_creator
"user_123"
end
end
script/task hookexternal ref data- process_task chaining
- closing branching besides complex gateway
create gem