Overview

Introduction

WES (workflow engine service) is a workflow engine that can be expose some useful api endpoints for clients and servers and reduced coding for your application. More features of WES :

  1. Easy to use and easy to learn
  2. common api endpoints for clients
  3. microservice architecture
  4. Fast and powerful
  5. written on node.js, typescript
  6. fields validation
  7. provide interfaces in python3
  8. using mongodb and docker
  9. cli support

Contributing

We welcome contributions of all kinds.

You can contribute to this book by opening an issue or forking and sending a pull request to the main Workflow-engine-service repository. Knowing what people use this book for the most helps direct our attention to making those sections the best that they can be. We also want the reference to be as normative as possible, so if you see anything that is wrong, please also file an issue.

Installation

developer mode

you must install node.js 12 or later. also you need to run an instance of mongo. for run mongo db, you can use docker:

sudo docker run -p 27017:27017 --name mongo -d mongo

you can access monogo cli with docker exec -it mongo mongosh --quiet

then you need to install npm dependencies of project by npm i on project root directory. finally you can run dev server with npm run dev and you can see on http://localhost:8082

production mode :

first go to cli/ directory and then run command dat p i and Done!

for install dat command, see Command Line Tool

you can create a configs.prod.json file and set your custom configs

Concepts

In this section you will learn how to:

  • deploy workflow schema
  • create processes
  • execute actions

Workflow

a workflow just a json schema that once deployed on workflow engine and NOT CHANGED forever until reset mongo db.

so you can just deploy your workflow once. if you can upgrade your workflow schema, you can deploy workflow with different version

sample workflow schema:

{
    "workflow_name": "register_user",
    "version": 1,
    "create_access_roles": [
        "_all_"
    ],
    "auto_delete_after_end": true,
    "start_state": "enter_info",
    "end_state": "finish",
    "fields": [
        {
            "name": "firstname"
        },
        {
            "name": "lastname",
            "type": "string",
            "meta": {
                "title": "نام خانوادگی",
                "help": "نام خود را وارد کنید",
                "class": "col-sm col"
            }
        },
        {
            "name": "phone",
            "type": "number"
        },
        {
            "name": "email",
            "validation": [
                {
                    "builtin_check": "email",
                    "error": "email not valid"
                }
            ]
        },
        {
            "name": "avatar",
            "type": "file",
            "validation": [
                {
                    "builtin_check": "file_type",
                    "builtin_params": {
                        "types": [
                            "image/png",
                            "image/jpg"
                        ]
                    },
                    "error": "avatar image not valid"
                },
                {
                    "builtin_check": "file_size",
                    "builtin_params": {
                        "max": 10000
                    },
                    "error": "avatar image size not valid"
                }
            ]
        }
    ],
    "states": [
        {
            "name": "enter_info",
            "meta": {
                "title": "ورود اطلاعات"
            },
            "access_roles": [
                "_all_"
            ],
            "actions": [
                {
                    "access_roles": [
                        "manager"
                    ],
                    "required_fields": [
                        "firstname",
                        "lastname",
                        "phone"
                    ],
                    "optional_fields": [
                        "email"
                    ],
                    "name": "approve",
                    "type": "hook_url",
                    "url": "http://127.0.0.1/django/api/v1/register",
                    "method": "post",
                    "message_required": "false",
                    "meta": {
                        "color": "green",
                        "title": "تایید اولیه"
                    }
                },
                {
                    "name": "reject",
                    "type": "redis",
                    "channel": "register",
                    "message_required": "true",
                    "response_channel": "resp_register",
                    "set_fields": {
                        "is_done": false
                    },
                    "meta": {
                        "color": "red",
                        "isCircleButton": true
                    }
                },
                {
                    "name": "edit",
                    "type": "local",
                    "next_state": "finish"
                }
            ]
        },
        {
            "name": "finish"
        }
    ]
}


Schema details (20220727.2 edition)

main schema

nametyperequiredDescription
workflow_namestringYESname of workflow
versionnumberNOdefault is 1
create_access_rolesstring[]NOuser roles that access to create process from this workflow (default: ['_all_'])
read_access_rolesstring[]NOuser roles that access to read process from this workflow (default: ['_all_'])
process_init_checkWorkflowProcessOnInitNOcheck when a process wants to start
auto_delete_after_endbooleanNOdelete process after enter to end state
start_statestringYESstart state of process
end_statestringYESend state of process
fieldsWorkflowFieldNOworkflow fields
statesWorkflowStateYESworkflow states

WorkflowProcessOnInit schema

nametyperequiredDescription
typestringYEScan be local or api
local_checkstringNOcheck in local mode by workflow engine. options: just_one_user_running_process: every user can only create one running process
api_urlstringNOapi url can only response boolean or a error string like 'you can not create new process'

WorkflowField schema

nametyperequiredDescription
namestringYESfield name
typestringNO'string' or 'number' or 'boolean' or 'file' (default: string)
metaobjectNOany data useful for client
validationWorkflowFieldValidation[]NOyou can set more validations on field

WorkflowState schema

nametyperequiredDescription
namestringYESstate name
access_rolestring[]NOroles to view this state (default: ['_all_'])
metaobjectNOany data useful for client
actionsWorkflowStateAction[]NOactions of state
eventsWorkflowStateEvent[]NOyou can define events on state

WorkflowStateAction schema

for more information about state actions, continue from here

nametyperequiredDescription
namestringYESaction name
access_rolestring[]NOroles to execute this action (default: ['_all_'])
required_fieldsstring[]NOfields must get from client
optional_fieldsstring[]NOfields that client can send
send_fieldsstring[]NOfields that must be send to app server 1
typestringNO'hook_url' or 'redis' or 'local'
alias_namestringNOname of an alias 2
metaobjectNOany data useful for client
message_requiredbooleanNOclient must send a message or not
set_fieldsobjectNOfields that can set hardcoded
urlstringNOcan be a url like http://sample.com/hook. used for 'hook_url' type
methodstringNOcan be a request method like 'get' or 'post'. used for 'hook_url' type
headersstring[]NOheaders that can be set on hook request. used for 'hook_url' type
channelstringNOpublish channel name. used for 'redis' type
response_channelstringNOsubscribe channel name. used for 'redis' type
redis_instancestringNOa redis instance name. used for 'redis' type. (default is first instance defined on configs)
next_statestringNOnext state to be go. used for 'local' type

2 added in 20220727.1

1 added in 20220727.2

Process

A process is an instance of a deployed workflow. you can create new process like this:

curl -X 'POST' \
  'http://localhost:8082/api/v1/workflow/create' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "sample_workflow",
  "version": 1
}'

if you do not specify workflow version, in default create process from with latest version of that workflow

Schema details (20220727.2 edition)

main schema

nametyperequiredDescription
_idstringYESid of process
workflow_namestringNOworkflow name
workflow_versionnumberNOdefault is 1
current_statestringYEScurrent state of process
field_valuesWorkflowProcessField[]NOprocess fields that filled
historyWorkflowProcessHistoryModel[]YESall history of process change states
workflowDeployedWorkflowModelYESworkflow reference
created_atnumberYEScreate process at date (timestamp)
created_bynumberYESwho create process
updated_atnumberYESupdate process at date (timestamp)

WorkflowProcessField schema

nametyperequiredDescription
namestringYESname of field
valueanyYESvalue of field
metaobjectNOmeta data of field

Access Roles

every user that call one of endpoint api, must be authorized. and every user can have some roles. (just a label)

for example, a user can be manager and normal_user and supporter and these are just labels that save for every user.

but if you want all users access to read a process or execute an action, how? we have a reserved access role for this. this is _all_. in default of all access roles on every where is _all_.

State Action

every state of workflow, has some actions or not! you can see details of state action schema on here

every action can be one of these types for execute:

you can use, alias for hook_url, redis types by alias_name property.

hook_url

when your server must be determine that next state is which or you can manage action execute by your self app server.

for this you need to set a endpoint url as url, and your accept request method like get or post (default is post method)

and you can set custom headers on hook request that raised by workflow engine like a authenticate token:

"headers": {
    "auth_token": "workflow_engine_token"
}

redis

when you communicate with workflow engine by redis, you can use this way.

you must to set a channel name that subscribe on it in your app server. and set a response channel name that publish on it your response (like next state) in your app server.

in default, workflow service use first redis instance that defined in configs, but you can use specify redis instance by redis_instance parameter.

channel and response_channel not be same!

local

if your action, can be execute locally, you can do this. you can just set your next_state name.

for example for reject actions, you can do this way.

State Action Response

when you use hook_url or redis types for state action, your server receive an object that contains:

nametypeDescription
state_namestringstate name
state_action_namestringstate action name
workflow_namestringworkflow name
workflow_versionnumberworkflow version
process_idnumbercurrent process id
user_idnumberuser that call this action
messagestringmessage that user send on call this action
required_fieldsWorkflowProcessField[]all required fields that user send them
optional_fieldsWorkflowProcessField[]all optional fields that user send them
send_fieldsWorkflowProcessField[]all fields that before set

in hook_url type, if no response or error response receive, then failed action.

when you set state action as hook_url or redis, you must response to complete action:

string response

you can send just next state as string

object response

nametyperequiredDescription
state_namestringYESthe state that go on, if null, then failed state
response_messagestringNOmessage of responsible server
fieldsobjectNOupdate some fields of process

after a timeout (defined in server configs), if no receive any response, failed action.

Command Line Tool

we use dat-tool tool for integrating a cli for project.

Install & Use

  • install node 12 or higher version
  • install DAT by npm install -g dat-tool
  • go to cli folder
  • type npm install to install node types
  • then, type dat m , if you want to develop script
  • And for use cli script, type dat play or just dat p

the new command

just support python3 language for now

for generate new workflow interfaces. the new command is used like this

dat p n -n sample_workflow

most used parameters:

name (n)

workflow name that need to create

language (l)

language to generate interface files

programming languages to support:

  • python3 (default)

version (v)

version of workflow (default : 1)

output (o)

directory path for generate interface files

overwrite (ow)

force to overwrite workflow interfaces, if exist

the install command

install workflow engine as dockerize. the install command is used like this

dat p i

most used parameters:

skip-remove-docker-cache (s1)

skip to remove docker unused images, containers

skip-build-image (s2)

skip to build workflow image

Configure

for customize default configure, if you in development mode, must edit configs.dev.json and if in production mode, you need to edit configs.prod.json file in project root

configs.prod.json file not in git tracking and you should create it for self.

configs json file includes some namespaces that some is required to set (for development mode) and some is optional.

after update configs.prod.json file (in production mode), you should to reset workflow engine by command dat p i (install command)

config namespaces

redis config namespace

you can define some redis server connections used for redis type of state action

if you not use any redis server, redis server in docker compose not be loaded!

sample code:

"redis": {
    "redis1": {
        "host": "127.0.0.1",
        "port": 6379
    }
},

for define redis connection, you declare a name for it (like redis1) and define redis properties on it.

properties

nametyperequiredDescription
hoststringYeshost name of redis server
portnumberYES-

mongo config namespace

you must define main database connection for workflow engine

sample code:

"mongo": {
    "host": "127.0.0.1",
    "port": 27017,
    "name": "workflow_db",
    "timezone": "Asia/Tehran"
},

properties

nametyperequiredDescription
hoststringYeshost name of mongo server
portnumberYES-
namestringYESdatabase name
usernamestringNO-
passwordstringNO-

server config namespace

you must define server configs like port or disable document services

sample code:

"server": {
    "port": 8082,
    "host": "0.0.0.0",
    "logs_path": "./logs",
    "uploads_path": "./uploads",
    "debug_mode": false,
    "swagger_disabled": true,
    "wiki_disabled": true
},

properties

nametyperequiredDescription
portnumberYES-
hoststringNOhost name of main server (default: localhost)
logs_pathstringNOlocal logs folder path for store app logs (default: ./logs)
uploads_pathstringNOuploads folder path for store users upload files (default: ./uploads)
debug_modebooleanNOactivate debug logs
wiki_base_urlstringNOwiki service base url (default: /wiki)
swagger_base_urlstringNOswagger service base url (default: /api-docs)
swagger_disabledbooleanNOis swagger service disabled?
worker_timeoutnumberNOtime for wait a worker to get response (default: 30s)
max_worker_runningnumberNOmax workers to async running (default: 10)

admin_users config namespace

you must at least one admin user to connect workflow service. set admin users on workflow engine as hard coded

you can set custom role for your admin users

sample code:

"admin_users": [
    {
        "username": "admin",
        "secretkey": "admin",
        "roles": [
            "admin"
        ]
    }
],

properties

nametyperequiredDescription
usernamestringYES-
secretkeystringYESrecommend that admin secret key be very hard
rolesstring[]NOyou can set custom roles for your admin user (default: [_admin_])

auth_user config namespace

you must define set authenticate configs

sample code:

"auth_user": {
    "type": "directly",
    "lifetime": 86400,
    "param_name": "access_token"
},

properties

nametyperequiredDescription
typestringYEScan be 'api_based' or 'directly' or 'dual'
lifetimenumberYESin seconds
urlstringNOfor api_based type
methodstringNOfor api_based type (can be get or post or put)
param_namestringNOfor api_based type (default: access_token)
header_namestringNOfor authenticate user request (default: authentication)

docker config namespace

you can define docker configs

this namespace used just for production mode

sample code:

"docker": {
    "mongo_image": "mongo",
    "redis_image": "redis"
}

properties

nametyperequiredDescription
mongo_imagestringNOdocker image used for mongo service (you can use it with tag)
redis_imagestringNOdocker image used for redis service

alias config namespace

you can define some alias in different types like hook_url. and then use it on every where that accept this type with alias_name property.

sample code:

"alias": {
    "app_hook": {
        "type": "hook_url",
        "url": "http://app.com/hook",
        "headers": {
            "auth_token": "436gbg45b4by54c45c4"
        }
    },
    "app_redis": {
        "type": "redis",
        "channel": "action",
        "response_channel": "res_action"
    }
},

properties

nametyperequiredDescription
typestringYestype of alias

types

every type of alias has different properties

hook_url
nametyperequiredDescription
urlstringYEScan be a url like http://sample.com/hook. used for 'hook_url' type
methodstringNOcan be a request method like 'get' or 'post'. used for 'hook_url' type
headersstring[]NOheaders that can be set on hook request. used for 'hook_url' type
redis
nametyperequiredDescription
channelstringYESpublish channel name. used for 'redis' type
response_channelstringYESsubscribe channel name. used for 'redis' type
redis_instancestringNOa redis instance name. used for 'redis' type. (default is first instance defined on configs)