API Documentation

The focus is to describe or better define how someone must interact with your processes.

This is done with ReDoc which is an Open API documentation tool. So there are some quirks, but I think you get used to them fast.

Why

To have accurate and not outdated documentation is a challenge. The closest you can get, is when your running- or tested code is the documentation.

And this is what we do here! Together with the Simulations and the Workers you get the most accurate documentation possible without having too much work.

Getting Started

The documentation uses the BPMNs you created - in this context I refer to the Bpmn DSL.

Let's start with a basic example:

// put your api in a package of your project
package camundala.examples.invoice.api

// define an object that extends from a common Api Creator
object ApiProjectCreator extends DefaultApiCreator:
  // technical name of the project
  val projectName = "invoice-example"
  // readable name of the project
  lazy val title = "Invoice Example Process API"
  lazy val projectDescr = "This is the API Documentation for the Invoice Example Process."
  // version of your project
  lazy val version = "1.0"

  // the documentation
  document (
    api(`Invoice Receipt`)(
      InvoiceAssignApproverDMN,
      ApproveInvoiceUT,
      PrepareBankTransferUT
    ),
    api(`Review Invoice`)(
      AssignReviewerUT,
      ReviewInvoiceUT
    ),
    group("DMNs", "All the DMNs")(
      InvoiceAssignApproverDMN
    )
  )

document

This is the entry point for your documentation. There is one documentation for a project. A document consists of one or more apis and/or groups.

api

Usually there is an api for each process with its interactions, like DMN Decisions or User Tasks.

group

If you have no process to organize your Process Interactions, you can use a group. This is especially useful if you have only Workers (a shared Service project) or DMN Decisions. It is also possible to add a description to the group.

Create the Documentation

Document

Naming

The DSL uses the InOut.id as the references throughout the documentation. For the api it uses the Process.processName also as the Tag (grouping in the document structure).

Example:

api_apiStructure

Strange Stuff

As we use a REST API documentation tool, there are some strange things to get used to.

As there are not really services behind*, we just use the HTTP method HEAD.

path

We create a unique path, like endpointType / tag / nameOrId. This is required as Open API skips Apis with identical paths.

Responses 200

The output variables are described in the 200 (ok) response, as a status is required by Open API.

See Postman Open API for additional information.

There is an idea to provide a Domain Driven Gateway, so this will be more natural.

Apis

Each Api defines its inputs and outputs, as well as if needed additional information.

Based on the BPMN objects

Implicit Apis

All Apis that have no 'children', are created automatically from their BPMN objects.

So api is optional (example AssignReviewerUT is equal to api(AssignReviewerUT)). You can do whatever you prefer.

Process Apis

A process Api must be specific, if you want to group all its interactions in the processes tag.

...
api(PROCESS)(
  INTERACTIONS
)
...

Example:

...
api(`Invoice Receipt`)(
  InvoiceAssignApproverDMN,
  ApproveInvoiceUT,
  PrepareBankTransferUT
)
...

This will create the following structure in the doc:

api_apiStructure

Input-/ Output-Variables

The input- and output variables are taken from the domain model of the BPMN object.

See Bpmn DSL for more information.

This creates this input description documentation:

api_inputs

And it creates this output description documentation:

api_outputs

Examples

By default, we create an example for the input- and one for the output variables. You find them on the right side of your Api documentation.

If you want to add more examples, you can do this the following ways:

input

Just add another input object and give it a name:

private lazy val InvoiceAssignApproverDMN =
  bpmn.InvoiceAssignApproverDMN
    .withInExample(budget)
    .withInExample(`day-to-day expense`)
    .withInExample(exceptional)

val budget = SelectApproverGroup()
val `day-to-day expense` = SelectApproverGroup(125, InvoiceCategory.Misc)
val exceptional = SelectApproverGroup(12345, InvoiceCategory.Misc)

Here the name of the example is taken from the variable name automatically.

In the documentation you can now select the different examples.

api_inputExamples

output

Same with output objects:

  private lazy val ReviewInvoiceUT =
    bpmn.ReviewInvoiceUT
      .withOutExample("Invoice clarified", InvoiceReviewed())
      .withOutExample("Invoice NOT clarified", InvoiceReviewed(false))

Here the name of the example is given explicitly.

In the documentation you can now select the different examples.

api_outputExamples

in- and output

You can also add input- and output examples in one step:

private lazy val InvoiceAssignApproverDMN =
    bpmn.InvoiceAssignApproverDMN
      .withExample(
        "budget",
        bpmn.InvoiceAssignApproverDMN
          .withIn(SelectApproverGroup())
          .withOut(CollectEntries(ApproverGroup.management))
      )
      .withExample(
        "day-to-day expense",
        bpmn.InvoiceAssignApproverDMN
          .withIn(SelectApproverGroup(125, InvoiceCategory.Misc))
          .withOut(
            CollectEntries(ApproverGroup.accounting, ApproverGroup.sales)
          )
      )

Generic Service Process

This is now DEPRECATED and replaced by Service Workers. See Service Worker.

Groups

You can organize your Apis within Groups. This is especially useful if you have a lot of processes or dmns.

...
group(NAME_OF_GROUP)(
  APIs
)
...

Example:

...
group("User Tasks")(
  ApproveInvoiceUT,
  PrepareBankTransferUT,
  AssignReviewerUT,
  ReviewInvoiceUT
)
...

This will create the following structure in the doc:

api_groupStructure

Be aware that document only supports two levels. This is because we use Tags to structure the APIs.

Correct 1-2 Levels:

    api(`Review Invoice`)(
      AssignReviewerUT,
      ReviewInvoiceUT
    ),
    OtherExternalTask,
    group("DMNs")(
      InvoiceAssignApproverDMN
    ),
    group("Helper Processes")(
      MyServiceProcess
    ),

Wrong > 2 Levels:

    api(`Review Invoice`)(
       api(MyServiceProcess)(
         SelectServiceUT
       )
    )
    group("Cool Processes")(
      api(MyServiceProcess)(
        SelectServiceUT
      )
    )

Postman Open API

Next to the Documentation, it creates also a YAML that you can import into Postman. With a few manual adjustments (due to restrictions in Open API) you have test client for your processes.

Import into Postman:

Manual adjustments in most request:

That it is not possible to have the API Documentation as the postman Open API is not nice.

However as we can describe a UserTask as one API, it takes three requests to actually handle a UserTask.

In the future we may provide a REST API that will work for both.

This would also allow a painless transition to Camunda 8.