Every company works with data that have some structure. Let's take invoices for example: as a bare minimum they have an ID number and a date. The rest of the data is where it gets more complicated: the bill-to data and the list of purchased items are further data structures and they depend on each other. Such data structures are reflected as custom complex data types, called Records.
You create them in dedicated data type definitions in a visual editor which makes the process easy and intuitive: individual data structures, like invoices, items, people, etc. are represented by Records, which are similar to classes in OOP.
This chapter provides only a brief introduction into data types and data type modeling. Note that to create both sophisticated and efficient data models, you will need more than being able to work with LSPS: make sure to analyze your data model properly before you design it to prevent performance and maintenance issues in the future. More detailed information on data types is available in the Data Type Model section of the GO-BPMN Modeling Guide.
We will create a Record called Invoice and another Record called BillTo. To include data about the Invoice in the BillTo record, we will add an invoice field to the BillTo.
Let's do that:
First you need to create a data type definition in your Module. This is a file that will contain the data type model with the Records:
Right-click the Module and go to New > Data Type Definition. Provide the name and click Finish.
The name of the file is not important since the server does not care for file name: you can have your data model in multiple data type definitions and they will be still considered one data model: what "separates" your data models is a module (the namespace is defined by the name of the module), not files.
You can see the definition file in your Module in the GO-BPMN Explorer and it should open in the data-model editor: the white empty space of the canvas in the editor is actually a diagram, which is a visualization unit: it displays the content or part of the content of the data type definition. The actual content of the definition file is in the Outline view. We will return to diagrams later.
In the diagram, create the Invoice Records with the fields number of the type Integer and dueDate of the type Date.
Create another record, the BillTo record, with the fields name
and surname
of the type String.
To quicken the modeling process, insert a new Field with the Insert key and move to the field type with the tabulator key. You can copy and paste the Records as well as their Fields.
To add the bill-to data to the Invoice, create the billTo Field of the type BillTo in the Invoice.
Now you can create instance of the Invoice record with the data about the invoice stored in its fields, for example, in a process on a Flow assignment: the expression will be executed when a token is passed from the Start Event to the Flow. We store the Record instance in the global variable invoice
. The data of the global variable can be accessed from anywhere within the module with the expression invoice.billTo.name
.
You have probably spotted a problem in the data model: What if the same person places multiple orders? You will end up creating multiple invoices with the same Person data again and again. If the data of the person change, you will need to update them in every single Invoice instance. We can solve this by associating the two Records with a relationship: the Invoice record will be only related to a BillTo record and so multiple Invoices can be related to the same BillTo person:
Note that we have named both relationship ends so we can navigate from the Invoice to BillTo and vice versa (that means, we can now write expressions like myInvoiceInstance.billTo
and myBillTo.invoices
to access the related record instance).
Also, we have set the multiplicity on the invoices end to Set: this allows us to associate multiple invoices with a single BillTo. Here is an example:
currentInvoice_1 := new Invoice( number > 1, dueDate > now() ); currentInvoice_2 := new Invoice( number > 1, dueDate > now() ); //john is a global variable of the type BillTo: john := new BillTo( name > "John", surname > "Doe", invoices > {currentInvoice_1, currentInvoice_2 } )
Let's rethink our BillTo Record: you could charge a company or a person; a company will not need a surname and a person will not need headquarters data. However, they are still considered the charged parties: to retain this structure, let's create child Records that will allow us to distinguish the charged parties.
Create the child Records NaturalPerson and LegalPerson of the BillTo Record.
With a bit of luck you have now a model similar to this one:
And there we have another problem: we do not want to create a BillTo record ever again: we want to charge always a natural or legal person. BillTo must be abstract: in the Properties of the Record select the Abstract flag.
Now, the assignment of an invoice to John is no longer valid: John must become a NaturalPerson.
john := new NaturalPerson ( name > "John", surname > "Doe", invoices > {currentInvoice_1, currentInvoice_2} )
Records can create hierarchies: For further details on inheritance, refer to the official documentation
You now have a data type model with a little bit of complexity. But all the data you create gets trashed right after you model instance finishes. How can you persist the data so it remains available forever (or at least as long as your database is available)? Just flag the Records as shared: shared records are reflected in the underlying database instantaneously.
Let's persist the Records and take a look at what is going on in the database: Mark all Record as Shared in their properties.
We got a bunch of errors saying Shared record <NAME> must specify at least one primary key
: since we are creating database tables for individual records, primary keys are required. Now we do not want to create the key by hand so set it to be generated automatically:
Note that the parent BillTo Record has additional database property O-R inheritance mapping. Its default setting is Each record to own table. Let's take a look at what that means:
Open a database client, such as SQuirreL, and check the schema of the Record tables.
For the PDS Embedded Server, connect to //localhost/h2/h2;MVCC=TRUE;LOCK_TIMEOUT=60000
; for jdbc the URL will be jdbc:h2:tcp://localhost/h2/h2;MVCC=TRUE;LOCK_TIMEOUT=60000
. Both the name and password are lsps
.
You will see that the tables for individual Records reflect the model: BillTo table contains only the id column; NaturalPerson contains id, surname, firstname, etc.
Let's test the other inheritance mapping:
The BILL_TO and INVOICE table now contain the entire hierarchy schema along with the TYPE_ID column which hold the record type of the entity, such as invoice-types' NaturalPerson: there are no tables for the NaturalPerson or LegalPerson records.
Until now we have created all elements of our data type model in the default Main Diagram. When we inserted an element into the canvas of the diagram, the element was displayed in the diagram and created in the definition file.
Note that diagrams do not represent a namespace: they are merely a presentation tool. If you create two Records with the same name in two diagrams, this will result in a name clash.
The content of the definition file is displayed in the Outline view.
For more information, refer to the Diagram chapter of the GO-BPMN Modeling Guide.
To display element from another data type definition file, possibly from a definition file in an imported Module, use the Record Import element, which is available in the palette of the editor.