We will construct a state machine and explore some of its features. We can use app-gen to generate a basic workflow engine and try to understand what got generated. But we will use chenile-samples for this article instead. Clone chenile-samples first.
A workflow service is a typical Chenile service that is generated using the Chenile workflow blue print. We will explain this blue print in depth in this article. issue is an example of a Chenile workflow blue print project.
Like any other service, it has issue-api and issue-service.
issue-api - defining the model object
Let us look at issue-api first. We want to define an API (Java interface) for managing issues. The Chenile workflow blue print has standardized this interface already. This exists already in workflow-api under Chenile. The interface in question is org.chenile.workflow.api.StateEntityService. So issue-api does not need to redefine this. Let us look at what workflow-api has already defined for this contract.
It merely needs to use it for Issue entity which we will define by extending AbstractStateEntity.
As we see above, it extends AbstractStateEntity and gives additional fields that are required for an issue such as assignee , assignComment etc. Thus the contract is defined by StateEntityService<Issue>
Issue States
Issue supports multiple states. We will show them here to understand the issue workflow better. Consider the diagram below:
We see multiple states and events such as “assign”, “resolve”, close” etc. We will pick the “assign” event and state the payload object that is required to trigger the event. In this case, we call it AssignIssuePayload which we define as follows:
Notice here that we are extending MinimalPayload which is already defined in workflow-api. Minimal Payload contains one comment as a String. Here we are capturing assignee as well as another field.
It is recommended that people use at least MinimalPayload as the payload type for all events. BHowever Chenile does not enforce this recommendation.
So this is all we need for issue-api. We need to define the workflow entity and any other payloads required for the workflow events. Let us move to issue-service.
issue-service - Implementing the issue contracts
We know that the StateEntityService<Issue> has already defined the Issue service contract. We now need to implement the contract. The good news is that this contract is already implemented as well. It is in workflow-service in Chenile. So we don’t need to implement it. We just need to instantiate it with the Issue State Machine. We will make that by defining a State Transition Diagram for Issue called states.xml.
You will find the state transition diagram for Issues under issue-service/src/main/resources/org/chenile/samples/issue/states.xml which is defined as follows:
Key features to note about this XML include the following:
The event-information section on the top is used to define the payload for each event. We will see how this information is used later.
Both entry and exit actions are defined. The entry action is used for persisting the issue into a database. It needs to be injected with an IssueStore which we will discuss later.
All states and events are defined.
Handlers are defined for all events.
Handler code
The handlers are responsible for handling events. They must comply with a special signature defined in Chenile STM. Let us look at the “assign” handler. We define it as follows:
Things to note above:
All handlers must implement STMTransitionAction for that workflow entity. (in this case Issue)
Handlers can assume that the payload can be casted to the correct payload defined in event-information. In this case it is AssignIssuePayload that we had defined in issue-api.
The handler code can mutate the state entity (Issue) but should not persist it. We will rely on the Entry action to persist it. The entry action will call an EntityStore which we will next implement for Issue.
Entity Store
We gave a trivial implementation of the Issue entity store using a hashmap. We should ideally use an ORM to do this.
Here is the code for that:
In this case, we just use the store method to generate an ID if applicable and store it in the hashmap. The store needs to be injected into the entry action. The entry action is again generic and defined in workflow-service.
Defining the controller
Next we need to define the issueService and register it in Chenile. We should also expose this using HTTP. We accomplish this by writing the following code:
Notice the following:
We are exposing only three operations via HTTP.
The create and retrieve methods are straightforward. However, the processById() method has a caveat. It has an event Payload which depends on the eventID (as defined in the event-information in the XML above). Now we need something to let Chenile know what is the payload type so that Chenile can use this information to serialize from JSON to the appropriate payload type.
We use a bodyType selector to accomplish this. We don’t need to write a new issueBodyTypeSelector. It is already there in workspace-service. We just need to make sure that it is injected with the correct state machine.
Instantiating the service, handler, store etc.
Now that we have the entire XML and Java code defined, let us see how to instantiate the State machine and all the handlers and store and body type selector. Please browse the code under issue-service/src/main/java/org/chenile/samples/issue/configuration.
The IssueConfiguration takes care of instantiating all the beans and the State machines and injects the relevant stuff. We would not repeat that code here. Instead please see the samples code.
This completes the work required to make a workflow service in compliance to the Chenile workflow blue print.
Test cases etc.
The test cases test this entire construction. We will let you read them for yourselves.