Coder Social home page Coder Social logo

peterfromthehill / lib-bpmn-engine Goto Github PK

View Code? Open in Web Editor NEW

This project forked from nitram509/lib-bpmn-engine

0.0 0.0 0.0 575 KB

A BPMN engine, meant to be embedded in Go applications with minimal hurdles, and a pleasant developer experience using it. This approach can increase transparency for non-developers.

Home Page: https://nitram509-lib-bpmn-engine.readthedocs-hosted.com/

License: MIT License

Go 100.00%

lib-bpmn-engine's Introduction

lib-bpmn-engine

Motivation

A BPMN engine, meant to be embedded in Go applications with minimum hurdles, and a pleasant developer experience using it. This approach can increase transparency of code/implementation for non-developers.

This library is meant to be embedded in your application and should not introduce more runtime-dependencies. Hence, there's no database support built nor planned. Also, the engine is not agnostic to any high availability approaches, like multiple instances or similar.

Philosophies around BPMN

The BPMN specification in its core is a set of graphical symbols (rectangles, arrows, etc.) and a standard definition about how to read/interpret them. With this foundation, it's an excellent opportunity to enrich transparency or communication or discussions about implementation details. So BPMN has a great potential to support me as a developer to not write documentation into a wiki but rather expose the business process via well known symbols/graphics.

There's a conceptual similarity in usage between BPMN and OpenAPI/Swagger. As developers, on the one hand side we often use OpenAPI/Swagger to document our endpoints, HTTP methods, and purpose of the (HTTP) interface, our services offer. Hence, we enable others to use and integrate them. With BPMN on the other hand it can be conceptual similar, when it comes to share internal behaviour of our services. I see even larger similarity, when it comes to the question: How do I maintain the documentation? Again, on the one hand side with OpenAPI/Swagger, we tend to either use reflection and code generators or we follow the API spec first approach. The later one is addressed by this library in the BPMN context: Business Process spec first approach

Roadmap

v0.1.0

progress milestone v0.1.0

For the first release I would like to have service tasks and events fully supported.

v0.2.0

progress milestone v0.2.0

With basic element support, I would like to add visualization/monitoring capabilities. If the idea of using Zeebe's exporter protocol is not too complex, that would be ideal. If not, a simple console logger might do the job as well.

v0.3.0

progress milestone v0.3.0

With basic element and visualization support, I would like to add expression language support as well as support for correlation keys

Build status

test action status codecov Documentation Status

Project status

  • very early stage
  • contributors welcome

Documentation

WiP... https://nitram509-lib-bpmn-engine.readthedocs-hosted.com/

GoDoc: https://pkg.go.dev/github.com/nitram509/lib-bpmn-engine/pkg/bpmn_engine

Requires Go v1.16+

BPMN Modelling

All these examples are build with Camunda Modeler Community Edition. I would like to send a big "thank you", to Camunda for providing such tool.

Usage Examples

Hello World

Assuming this simple 'Hello World' BPMN example should just print "hello world".
hello_world.png

Then a simple (and verbose) code to execute this looks like this

package main

import (
	"fmt"
	"github.com/nitram509/lib-bpmn-engine/pkg/bpmn_engine"
)

func main() {
	// create a new named engine
	bpmnEngine := bpmn_engine.New("a name")
	// basic example loading a BPMN from file,
	process, err := bpmnEngine.LoadFromFile("simple_task.bpmn")
	if err != nil {
		panic("file \"simple_task.bpmn\" can't be read.")
	}
	// register a handler for a service task by defined task type
	bpmnEngine.AddTaskHandler("hello-world", printContextHandler)
	// setup some variables
	variables := map[string]string{}
	variables["foo"] = "bar"
	// and execute the process
	bpmnEngine.CreateAndRunInstance(process.ProcessKey, variables)
}

func printContextHandler(job bpmn_engine.ActivatedJob) {
	println("< Hello World >")
	println(fmt.Sprintf("ElementId                = %s", job.ElementId))
	println(fmt.Sprintf("BpmnProcessId            = %s", job.BpmnProcessId))
	println(fmt.Sprintf("ProcessDefinitionKey     = %d", job.ProcessDefinitionKey))
	println(fmt.Sprintf("ProcessDefinitionVersion = %d", job.ProcessDefinitionVersion))
	println(fmt.Sprintf("CreatedAt                = %s", job.CreatedAt))
	println(fmt.Sprintf("Variable 'foo'           = %s", job.GetVariable("foo")))
	job.Complete() // don't forget this one, or job.Fail("foobar")
}

A microservice API example

The following example snippet shows how a microservice could use BPMN engine to process orders and provides status feedback to clients.

For this example, we leverage messages and timers, to orchestrate some tasks. hello_world.png

For this microservice, we first define some simple API.

package main

import "net/http"

func initHttpRoutes() {
	http.HandleFunc("/api/order", handleOrder)                                        // POST new or GET existing Order
	http.HandleFunc("/api/receive-payment", handleReceivePayment)                     // webhook for the payment system
	http.HandleFunc("/show-process.html", handleShowProcess)                          // shows the BPMN diagram
	http.HandleFunc("/index.html", handleIndex)                                       // the index page
	http.HandleFunc("/", handleIndex)                                                 // the index page
	http.HandleFunc("/ordering-items-workflow.bpmn", handleOrderingItemsWorkflowBpmn) // the BPMN file, for documentation purpose
}

Then we initialize the BPMN engine and register a trivial handler, which just prints on STDOUT.

package main

import (
	"fmt"
	"github.com/nitram509/lib-bpmn-engine/pkg/bpmn_engine"
	"time"
)

func initBpmnEngine() {
	bpmnEngine = bpmn_engine.New("Ordering-Microservice")
	process, _ = bpmnEngine.LoadFromBytes(OrderingItemsWorkflowBpmn)
	bpmnEngine.AddTaskHandler("validate-order", printHandler)
	bpmnEngine.AddTaskHandler("send-bill", printHandler)
	bpmnEngine.AddTaskHandler("send-friendly-reminder", printHandler)
	bpmnEngine.AddTaskHandler("update-accounting", updateAccountingHandler)
	bpmnEngine.AddTaskHandler("package-and-deliver", printHandler)
	bpmnEngine.AddTaskHandler("send-cancellation", printHandler)
}

func printHandler(job bpmn_engine.ActivatedJob) {
	// do important stuff here
	println(fmt.Sprintf("%s >>> Executing job '%s", time.Now(), job.ElementId))
	job.Complete()
}

func updateAccountingHandler(job bpmn_engine.ActivatedJob) {
	println(fmt.Sprintf("%s >>> Executing job '%s", time.Now(), job.ElementId))
	println(fmt.Sprintf("%s >>> update ledger revenue account with amount=%s", time.Now(), job.GetVariable("amount")))
	job.Complete()
}

Since the /api/order endpoint can be requested with the GET or POST method, we need to make the handler smart enough to either create an order process instance or respond a status

package main

import (
	_ "embed"
	"fmt"
	"net/http"
	"strconv"
)

func handleOrder(writer http.ResponseWriter, request *http.Request) {
	if request.Method == "POST" {
		createNewOrder(writer, request)
	} else if request.Method == "GET" {
		showOrderStatus(writer, request)
	}
}

func createNewOrder(writer http.ResponseWriter, request *http.Request) {
	instance, _ := bpmnEngine.CreateAndRunInstance(process.ProcessKey, nil)
	redirectUrl := fmt.Sprintf("/show-process.html?orderId=%d", instance.GetInstanceKey())
	http.Redirect(writer, request, redirectUrl, http.StatusFound)
}

func showOrderStatus(writer http.ResponseWriter, request *http.Request) {
	orderIdStr := request.URL.Query()["orderId"][0]
	orderId, _ := strconv.ParseInt(orderIdStr, 10, 64)
	instance := bpmnEngine.FindProcessInstanceById(orderId)
	if instance != nil {
		// we re-use this GET request to ensure we catch up the timers - ideally the service uses internal timers instead
		bpmnEngine.RunOrContinueInstance(instance.GetInstanceKey())
		bytes, _ := prepareJsonResponse(orderIdStr, instance.GetState(), instance.GetCreatedAt())
		writer.Header().Set("Content-Type", "application/json")
		writer.Write(bytes)
		return
	}
	http.NotFound(writer, request)
}

Also, for the incoming payments, our microservice provides an endpoint so that we get informed by external payment service. This handler sends a message to the process instance and continues.

package main

import (
	_ "embed"
	"net/http"
	"strconv"
)

func handleReceivePayment(writer http.ResponseWriter, request *http.Request) {
	orderIdStr := request.FormValue("orderId")
	amount := request.FormValue("amount")
	if len(orderIdStr) > 0 && len(amount) > 0 {
		orderId, _ := strconv.ParseInt(orderIdStr, 10, 64)
		processInstance := bpmnEngine.FindProcessInstanceById(orderId)
		if processInstance != nil {
			processInstance.SetVariable("amount", amount)
			bpmnEngine.PublishEventForInstance(processInstance.GetInstanceKey(), "payment-received")
			bpmnEngine.RunOrContinueInstance(processInstance.GetInstanceKey())
			http.Redirect(writer, request, "/", http.StatusFound)
			return
		}
	}
	writer.WriteHeader(400)
	writer.Write([]byte("Bad request: the request must contain form data with 'orderId' and 'amount', and the order must exist"))
}

To get the snippet compile, see the other sources in the examples/ordering_microservice/ folder.

Supported BPMN elements

  • Start Event
  • End Event
  • Service Task
    • Get & Set variables from/to context (of the instance)
  • Forks
    • controlled and uncontrolled forks are supported
    • Parallel Gateway supported
  • Joins
    • uncontrolled and exclusive joins are supported
    • parallel joins are supported
  • Message Intermediate Catch Event
    • at the moment, just matching/correlation by name supported
    • TODO: introduce correlation key
  • Timer Intermediate Catch Event

lib-bpmn-engine's People

Contributors

nitram509 avatar actions-user avatar stoovon avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.