-
Notifications
You must be signed in to change notification settings - Fork 0
The first example illustrates how a business process model could (currently) be interpreted in Go. No modeller is used to create the elements.
Note
A prerequisite for using such methodologies is, of course, the ability for visualization a model in your own head.
Three modules are imported and three packages from the models. To start with gobpmn at all, the core package of gobpmnModels must first be imported. The counter is not mandatory, but illustrates how elements are counted. The automated hash distribution via a reflected dependency injection is a method for responding algorithmically to this problem. For this purpose, the module for outputting the model definition has a method for the hidden initialization of so-called standard elements in the first dimension of a business process model.
import (
gobpmn_builder "github.com/deemount/gobpmnBuilder"
gobpmn_counter "github.com/deemount/gobpmnCounter"
gobpmn_hash "github.com/deemount/gobpmnHash"
"github.com/deemount/gobpmnModels/pkg/core"
"github.com/deemount/gobpmnModels/pkg/events/elements"
"github.com/deemount/gobpmnModels/pkg/process"
)
Three global variables are declared with the respective types from the modules.
The types are
- Builder
- Injection
- Quantities
The builder utilises the dependent elements for the definitions and structures and creates a file in BPMN, JSON or XML from them
The injection distributes the hash values to the respective elements in the structures, which can then be appended using a suffix.
The quantity information contains the quantity of counted elements from the structures. The quantity information can then be inserted into the respective setter using a field.
var (
build gobpmn_builder.Builder
count gobpmn_counter.Quantities
hash gobpmn_hash.Injection
)
The centrepiece of the process is, of course, the body. And this is exactly where the idea of gobpmn begins.
First, an interface with the name Proxy is created, in which the build method signature and replay type of this method are written. This serves to visibly decouple the initialization from the build. This makes the structure as a whole much clearer.
Note
The name Proxy is freely chosen here and can of course also be named differently.
type Proxy interface {
Build() ExampleProcess
}
The following structure with the freely chosen name ExampleProcess contains the rough structure of the model. It is particularly important to use the correct words and the order of the fields.
An important and unavoidable rule in gobpmn for creating a process from a structure is the first field of the type core.DefinitionsRepository. This field is fixed and must be either Def or Definitions. This field also provides a reference to the set of methods that are required to create a BPMN process from the root element.
The following anonymous fields are embedded, whereby Pool as a stand-alone word and the suffix Process as a word in the appendix are decisive for the creation of the elements. A pool can, for example, carry one or more processes, but the process within is clearly labelled with its prefix.
The last field in this example then describes the structure of the respective participant of the process, which is described here with the freely chosen name Tenant.
type ExampleProcess struct {
Def core.DefinitionsRepository
Pool
Tenant
}
The pool contains two fields, whereby the order of the fields and the typing is also decisive here. At the same time, the name of the field is also compared with the process to which the field can be assigned as an attribute or element.
A distinction is made between two reflected types in the typing. One of type bool and one of type struct. The bool type reflects a configuration option. In this example, the process is labelled as Executable.
The following field refers to a process to be created, which can then carry the other fields, such as the start event, in its dimension. Attention must also be paid to the prefix when naming the process. The prefix describes the process name in CamelCase notation
type Pool struct {
TenantIsExecutable bool
TenantProcess gobpmn_hash.Injection
}
The tenant as such describes the individual process with all its elements within a pool. In this example, only one StartEvent is set to illustrate this. Here, the prefix from the pool becomes an object and is firmly anchored in a structure.
type Tenant struct {
TenantStartEvent gobpmn_hash.Injection
}
These structures now describe the process that can now be written.
Overview of the overall structure with interface
type (
Proxy interface {
Build() ExampleProcess
}
ExampleProcess struct {
Def core.DefinitionsRepository
Pool
Tenant
}
Pool struct {
TenantIsExecutable bool
TenantProcess gobpmn_hash.Injection
}
Tenant struct {
TenantStartEvent gobpmn_hash.Injection
}
)
The initialization takes place in four steps, which are written in New().
- count the captured elements by their prefixes and suffixes in this structure.
- attach a field with the name Suffix to each captured element and insert a hash value.
- declare a variable of type DefinitionsRepository and set the declarations for a BPMN document.
- pass the declared definition and the quantities to a method of the builder, which can then set the collaboration, the process(es) or the diagram in the first dimension of the BPMN document
The return value p refers to the interface with the single method Build()
func New() Proxy {
c := count.In(ExampleProcess{})
p := hash.Inject(ExampleProcess{}).(ExampleProcess)
p.Def = core.NewDefinitions()
p.Def.SetDefaultAttributes()
build.Defaults(p.Def, c)
return p
}
So far, I have used the following solutions to form the elements in a BPMN document in the design of the software or the process structure in Go:
-
all elements should, if possible, be constructed in a single method that can be clearly identified by name. This method is called Build() and has the Value Receiver of the created process structure as its return value.
-
the method with the so-called name Call() with the return value of the DefinitionRepository is then finally the lock of the initialized and evaluated elements from the process to the builder. This method is a must in this design, as otherwise the structure of the document cannot be interpreted by the builder.
func (p ExampleProcess) Build() ExampleProcess {
p.setTenantProcessArgs()
p.tenantProcess().SetStartEvent(1)
p.setTenantStartEventID()
return p
}
func (p ExampleProcess) Call() core.DefinitionsRepository {
return p.Def
}
Important
Build and Call are of course also freely chosen names. What is important in the constellation is the correct return value and, if the name is different, to implement the information correctly during execution.
As with the models in the respective packages, the local methods are divided into so-called setters and getters. Each setter method describes the element and its specific attributes or configuration options. The getter methods, on the other hand, open the element for the use of its own attributes and/or the reference to other elements within its own structure in the nth dimension. Due to the strong typing in the models themselves, getters must be created as the types declared in the models do not provide any further methods.
The hash values resulting from the creation and distribution can also be assigned here. The assignment can be inserted automatically using the suffix that is added to each element from the injection. The same principle then applies to the IsExecutable configuration option, which then has the value true instead of false, for example.
With the getter methods, care must be taken to ensure that they return a real pointer to the element and not a defined type. In addition, care must be taken to set the index for the process correctly.
func (p *ExampleProcess) setTenantProcessArgs() {
p.tenantProcess().SetID("process", p.TenantProcess.Suffix)
p.tenantProcess().SetIsExecutable(p.TenantIsExecutable)
}
func (p *ExampleProcess) setTenantStartEventID() {
p.tenantStartEvent().SetID("startevent", p.TenantStartEvent.Suffix)
}
func (p ExampleProcess) tenantProcess() *process.Process {
return p.Def.GetProcess(0)
}
func (p ExampleProcess) tenantStartEvent() *elements.StartEvent {
return p.tenantProcess().GetStartEvent(0)
}
During execution, a variable is first declared that contains the type DefinitionsRepository. The three golabeled methods from the process structure created in Go are then called via method chaining.
Before the structure can be interpreted, an instance is defined on the builder, which then, for example, creates the name of the file numerically and with a prefix.
The process structure is then passed as a reference to the builder in a method provided for this purpose, which then generates the respective documents in the Build() method.
Note
The documents are stored in a specified directory with the name files/bpmn and files/json in the same package as this example.
func main() {
exampleProcess := New().Build().Call()
builder, err := gobpmn_builder.New()
if err != nil {
panic(err)
}
builder.SetDefinitionsByArg(exampleProcess)
builder.Build()
}
The result is two files in their respective directories assigned to the types. All elements have their assigned hash values and are arranged validly in the tree structure. In the JSON output, it should be emphasized that the collaboration element appears here as a property with the value zero. This is because this element in the package in the gobpmnModels has a different tag value than the xml notation
BPMN
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_2a5508c8" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_95a7780e" isExecutable="true">
<bpmn:startEvent id="StartEvent_a3d87cfe"></bpmn:startEvent>
</bpmn:process>
</bpmn:definitions>
JSON
{
"id": "Definitions_2a5508c8",
"collaboration": null,
"process": [
{
"id": "Process_95a7780e",
"startEvent": [
{
"id": "StartEvent_a3d87cfe"
}
],
"isExecutable": true
}
]
}
Creating a BPMN document plus an additional JSON copy in the Go language without a modeler is a feasible experiment in its initial stages. The smaller the model, the more readable it is, even without a visualization using implemented diagrams. A new document with new hash values is therefore created with each execution. Especially for projects in the Go context, this idea can be an excellent implementation for further executions, even with a modeler.