Process & Domain Specification
First we define our Business Processes and -Domain.
Process
Here we use the BPMN Specification with some extensions from Camunda. You can use Camunda's tools to create them, see Camunda Modeler.
To get started we will take the Invoice Example. You find a version for a version for Camunda 7 and one for Camunda 8 on Github - see Invoice Example
Nothing interesting here, just a standard Camunda/ BPMN Model. Let's define the Domain for this process.
Domain
Whenever we interact with the Process, der are Business Objects involved. But also a Service that we integrate defines its Domain.
So implementing a Process is more or less working with Domain Objects, mostly mapping them to similar Domain Object, defined by Services.
To describe the Domain Model, we use basic Scala constructs, like Case Classes and Enumerations.
The following BPMN Inputs / -Outputs do we describe:
- Process
- UserTask
- ReceiveSignal / ReceiveMessage (no Outputs)
- DMN
Here is the UserTask 'Approve Invoice' from our Invoice Process.
-
Import:
import camundala.domain.*
-
Input:
@description("Received Invoice that need approval.") case class InvoiceReceipt( creditor: String = "Great Pizza for Everyone Inc.", amount: Double = 300.0, invoiceCategory: InvoiceCategory = InvoiceCategory.`Travel Expenses`, invoiceNumber: String = "I-12345", invoiceDocument: FileRefInOut = FileRefInOut( "invoice.pdf", "processes/invoice.pdf", Some("application/pdf") ) ) enum InvoiceCategory : case `Travel Expenses`, Misc, `Software License Costs`
-
Output:
@description("""Every Invoice has to be accepted by the Boss.""") case class ApproveInvoice( @description("If true, the Boss accepted the Invoice") approved: Boolean = true )
You see here the following elements:
@description("my descr")
Description of a class or a field > used then in the API Documentation.-
creditor: String = "Great Pizza for Everyone Inc."
Define each field with a name, a type and an example. You can use:- simple types, like String, Boolean etc.
- enumerations - like
InvoiceCategory
- objects - just other Case Classes
invoiceDocument: FileRefInOut
in Camunda 8 only JSONs are allowed - so you need a File representation.enum InvoiceCategory..
this is how you define an enumeration.`Travel Expenses`,
if you have names with spaces you need to use Back-Ticks.
Checkout the Domain Description of InvoiceReceipt.
There are 2 things you have to care for that is not purely your domain.
Case Class
You describe your domain model with case classes, as they are easy to create and there is support for documentation and JSON marshalling.
Simple Enum
If an attribute of a Case Class is an enumeration you can use the Scala enum
.
enum InvoiceCategory :
case `Travel Expenses`, Misc, `Software License Costs`
Hierarchy Enum
If you have inputs or outputs that can differ, you can use the Scala enum
as well. It just looks a bit different:
enum GetCodesOut:
case KeyValues(
codesResult: Option[Map[String, String]] = None,
poBox: Option[Map[String, String]] = Some(defaultPoBoxFr)
)
case ManualKeys(
codesResult: Option[Seq[Map[String, String]]] = None,
poBox: Option[Seq[Map[String, Json]]] = Some(
Seq(
Map("key" -> 1.asJson, "name" -> "Postfach".asJson),
...
)
)
)
end GetCodesOut
Generic Objects
Sometimes your domain is huge are you will start fast.
To have still at least an example and correct test data, you can use a generic datatype, like Json
.
This looks like this:
case class MyDomainClass(
... // some specific attributes
someJson: Json = toJson("""{"value": 12}""")
)
You can create examples with toJson("""{"value": 12}""")
.
This will be a Json Variable in the Simulations and you can use them everywhere in your domain model.
That this works you need this import: import sttp.tapir.json.circe.*
- see JSON marshalling.
Tip: Start with Jsons and replace them with proper classes as soon you start documenting (mostly when the process is stable).
Documentation
The closer the documentation is to your code, that you work with, the higher is the chance that you will spot mistakes.
So we use @description("my descr")
from Tapir.
These descriptions will then automatically taken into account when the API documentation is generated.
Be aware that the description of Input- and Output Objects are not taken into the doc by Open API.
So document them in the process description if needed.
JSON marshalling
We need this to get to and from JSON. There is an automatic way, but it turned out that it made compiling slow.
Sorry for this technical noise 😥.
-
Case Classes and Enumerations:
given ApiSchema[InvoiceReceipt] = deriveApiSchema given InOutCodec[InvoiceReceipt] = deriveInOutCodec
-
Simple Enumerations:
enum InvoiceCategory: case `Travel Expenses`, Misc, `Software License Costs` object InvoiceCategory: given ApiSchema[InvoiceCategory] = deriveEnumApiSchema given InOutCodec[InvoiceCategory] = deriveEnumInOutCodec