Myles Skinner's Development Portfolio: Technical Writing

The following is an article that I wrote for PartnerPath's Confluence knowlege base. I present this as an example of my skills in technical writing and documentation.

Deal Registration—General Approval Workflow

Introduction to the State Machine

Different tenants have different ways of handling their deal registration workflows. Our smaller tenants tend to have simple, straightforward workflows; Enterprise tenants like Majaro often have complicated workflows. In our portal, we need to be able to model our deal registration workflow so that it works equally well for everybody, no matter how complicated their deal registration process is. We use a state machine to define the rules that govern the workflow for each of our tenants. In this document, I will outline the basic state machine configuration for simple workflows. In Figure 1 below, I illustrate a state machine flow chart for deals requiring three levels of administrative approval.

Figure 1: The basic Deal state machine

A state machine consists of three components:

Two-Dimensional Workflow

One of the questions we faced in our old system was how to model workflows that require multiple levels of approval. An even more complicated question is, "How do we model workflows for different tenants who require different numbers of approval levels?" Our previous solution was to model the workflow as one long line from beginning to end, with special rules determining how a deal should traverse the line, jumping forwards and backwards chaotically. However, this approach got messy, especially when different tenants needed competing and contradictory rules.

In order to make the workflow much easier to configure, and to make the supporting code much more elegant, I have separated the workflow into two distinct but interdependent state machines: the :workflow_status state machine that runs from left to right on Figure 1 above, and the :approval_status state machine that runs from top to bottom. Each has its own set of rules that define the events that trigger transitions from one state to another. The "state machine" defined in our Deal model is actually a combination of these two state machines working together.

Figure 2: Deal workflow status

Workflow Status: Left to Right

The basic workflow lifecycle of a normal deal is very simple: it starts out its life in :new status, progresses to :fully_approved, and eventually becomes :closed, shown in Figure 2 as arrows going from left to right. The basic states defined in the :workflow_status state machine work as follows:

Figure 3: Three-level approval workflow

Approval Status: Top to Bottom

Depending on the tenant, a deal might need to pass through multiple levels of approval. Figure 3 shows an example of an :approval_workflow with three approval levels. We are not limited to three levels—we can define as many levels as we need. Most basic tenants will only need one or two levels; Majaro currently has six levels.

A new deal starts with an approval status of :pending_level_one, and over the course of its life progresses from top to bottom until it reaches the :approvals_complete state. When approvals are complete, the :approval_status state machine sends a message back to the :workflow_status state machine to let it know that it is time to move forward.

There are two basic events in the normal :approval_status state machine:

Client Configuration: Defining the Approval Levels

Rather than set up a separate state machine for each tenant, we implement a single state machine with flexible rules. The code fragment in Figure 4 shows how we represent the :approve events diagrammed in Figure 3 in our Deal model:

Figure 4: Transitions defined for the :approve event

As the comment at the top of Figure 4 indicates, the state machine will examine the transitions we have defined in order, line-by-line, until it finds one that meets the conditions we have defined. When the state machine finds a match, it executes the transition. Because the state machine checks each transition line in order when deciding which line to execute, we can use the ordering to define a precedence order for our rules.

The code in Figure 4 should be easy enough to read—I tried to choose labels that were self-documenting. The main point I'd like to demonstrate is in lines 4-5. Line 4 says we transition from :pending_level_one on to :pending_level_two provided that the level_two_valid? check passes for a particular deal. If level two is not valid, then we fall through to line 5 and transition to :approvals_complete. In other words, if there's no level two, then we must be finished once we get past level one.

The if check on the boolean instance method level_two_valid? in line 4 is important because it allows us to define the approval workflow behaviour not only for individual tenants, but also for individual deals. Consider the sample level_two_valid? method outlined in Figure 5:

Figure 5: @deal.level_two_valid? boolean instance method

In Figure 5, level_two_valid? always returns true for BlogFish, so all deals in BlogFish will have two levels of approval. Majaro has much more complicated requirements where some deals have a level two and some don't, based on the evaluation of a number of arbitrary properties of an individual deal. For most other tenants, level_two_valid? returns false, so these tenants will only have a single level of approval on their deal registrations.

By combining the conditional transition rules in the state machine with per-tenant validations for each level of approval, we can allow every tenant's workflow to co-exist peacefully. The vast majority of our tenants will have a simple one-level workflow and the defaults we have defined will "just work" out of the box, but this state machine model can handle any degree of complexity. Majaro is by far our most complex tenant, with six different approval levels—each with its own set of routing rules—that may or may not apply to a particular deal depending on the values of several of its properties. Majaro's state machine is too complex to outline here; I explain the details of Majaro's implementation in a separate document.