In enterprise organizations it is bound to occur that different teams are building similar software independently of each other. For example, multiple teams write code which interacts with various services and performs some business logic in a similar manner. The complexity of creating software is usually not in creating components which interacts with external software itself (e.g., connecting with a RESTful API via a service client class). The complexity resides in the means of combination of these various components. I.e. the modeling of the business process (the flow of work through an organization).
A Petri net is a mathematical modeling language which is particularly suited to model business processes. In the foreword of Modeling Business Processes: A Petri Net-Oriented Approach by Aalst & Stahl (2011) it is stated that:
Petri nets provide the foundation of the graphical notation and the basic primitives for modeling concurrency, communication, and synchronization. The extension of these basic primitives with data (color) ... makes it possible to model and analyze complex artifacts. (p. iv).
To facilitate flexibility in the face of changing requirements, to encapsulate the business rules in one central place, and to possibly tackle the problem of re-usability described above, a Petri net can be used to model a business process. Below I will show an example of using a Petri net using a domain specific language (DSL) which we have started creating at work. From this DSL which describes a business process an event-driven Petri net representation can be generated. All that is left of complex business logic in an application is the need to send the right events (with data) to the Petri net representation.
In this article I will elaborate on Petri nets, the DSL created and an event-driven Petri net representation.
In 1939 Carl Adam Petri was only 13 years old when he invented the Petri net with the goal of describing a chemical process. A Petri net is a directed bipartite graph consisting of places and transitions, where every place only connects with transitions and every transition only connects with places.
Below a visualization of a Petri net is provided. Transitions are visualized as rectangles and places as circles.
(Source: Wikipedia)
In the places tokens can exist (the dots that appear in the image above). Once all in-adjacent places towards a transition are filled, the transition can fire and consume the tokens, and thereby produce new tokens in the out-adjacent places out of the transition. Parallelism is supported (e.g., multiple places next to each other coming out of a transition or multiple places leading to one transition).
There are various extensions to the Petri net. A colored Petri net is a special type of Petri net where the tokens can also have data. A colored Petri net is particularly suited for modeling business processes.
Especially in Europe much research has been done on Petri nets and certain properties can be proven about them. It is possible to mathematically reason about reachability ("can we reach a certain state?"), liveness ("can a transition fire?") and boundedness ("how many tokens can there be?"). Reasoning about such properties is useful for example for:
By using Petri Net Markup Language (PNML) to describe the Petri net analysis can be done via existing tools.
When a chef cooks a recipe she combines various ingredients. When certain events happen, or when ingredients are ready, a chef knows what action(s) to execute next. E.g., when a chef receives an event WaterBoiling she knows that she can start cooking the pasta. And once the chef has a knife and tomatoes she will start cutting them. If the chef is skilled, this results in pasta sauce when finished.
In the DSL below a similar recipe can be provided to a chef – only the chef is an execution engine written in Scala. Ingredients do not consist of edible substances but of software components. Actions to be executed are not certain kitchen skills, but instead methods on these software components. The preconditions for these actions are data or events that are provided.
The chef is all that is needed to handle flows through our business process.
In the current setup the recipe is represented as a Java map where the keys are the interfaces of the components and the values a representation of the actions. The representation of the action has properties to specify which methods are needed and which preconditions they have. These preconditions are specified as events or data.
Once the chef received the recipe it can start cooking a Petri net topology. The chef "cooks" by using reflection to get all the information out of the recipe and the ingredients. This topology is shared with various states of the Petri net (a state describes in what place which data/tokens are).
The chef can listen for events that are happening. Once it processes an event it knows to which Petri net state it belongs by looking at the process identifier, and it will perform the action specified in the recipe and update the state accordingly with the results.
The Petri net is modeled in-memory via the Kagera library which is maintained by one of my team members at work (who happens to be really enthusiastic about Petri nets). In the future it will be possible to store the state on disk using event sourcing – with the added benefit that all historical flows can be replayed and analyzed.
The chef has the following abilities:
Below we see the interface of the chef:
class Chef(recipe: Map[Class[_], Seq[Action]],
ingredientImpls: Map[Class[_], AnyRef],
events: Set[Class[_]]) {
/* Cook the recipe for a specific process id */
def cook(processId: UUID): Unit = /* (...) */
/* Get all accumulated state from the Petri net marking */
def getAccumulatedState(processId: UUID): Map[String, Optional[Any]] = /* (...) */
/* Tell the chef an event has happened for a specific process id */
def tellEventHappened(processId: UUID, event: AnyRef): Unit = /* (...) */
/* Get the GraphViz (graph visualization software) implementation of the recipe */
def getVisualRecipe: String = /* (...) */
/* Get the PNML (Petri Net Modelling Language) representation of the recipe */
def getPnmlRecipe: String = /* (...) */
}
When getVisualRecipe
is called the following GraphViz visualization of the recipe describing a simple cooking process is returned:
Here events are modeled as blue diamonds, red circles represent data (places in Petri net terminology), and grey rectangles are the actions on the ingredients (transitions in Petri net terminology).
In the code of your application only the user-facing parts need to be created. Here the chef can be notified by events via the tellEventHappened
method.
For example in a Java API, an instance of the Chef
is provided in the classes providing API endpoints. This can look roughly as follows:
@Autowired
private JChef chef;
@POST
@Path("pastaSauce")
public Response waterBoiling(UUID processId, Sauce sauce) {
chef.tellEventHappened(processId, new PastaSauceReady(sauce));
return Response.ok().build();
}
Once the API is called on the pastaSauce resource there is no complex if-then-else business logic. The chef is just told whom it is that is calling our API, that it is the PastaSauceReady
event that has happened and what sauce
data was provided. All logic on what to do with this information is provided by the recipe and encapsulated in the chef.
By using the modeling language of Petri nets and by creating a DSL it is possible to facilitate reuse, flexibility and encapsulation of complexity in the modeling of a business process. Changing the business logic has become as easy as changing the recipe. Business analysts can read or view the recipe and see if it adheres to their ideas. The chef should even be able to tell if it can cook a specific recipe. In theory, it is possible to share the ingredients and the chef with other teams and let them create new recipes reusing the components. And where in classically build APIs it can be difficult to trace where things went wrong, because of the properties of Petri nets it is possible to thoroughly analyze flows taken through the application.
If you want to read more: