Whitepaper State Machines Simplified
Whitepaper State Machines Simplified
In this article, we’ll present a brief introduction to state machines and show how
the design pattern can be used to implement a business process. Then, we’ll
compare a process implemented as a state machine to a process implemented
using Temporal.
State machines simplified
In order to get full benefit from reading this article, The more you know about
Temporal – from Workflows, and Activities, to the Workers and Clients – the
more you’ll get from this article.
temporal.io
1
State machines: an overview
and an example
A state machine is a software design pattern used to build applications that
move between many possible states. The role of a state machine is to manage
state changes and make the current state of the system explicit.
Figure 1 below illustrates a state machine for a system that supports a document
publishing workflow. This use case is implemented in the demonstration code
that accompanies this article. At the conceptual level, the workflow accepts a
document and then copy edits and graphic edits the document simultaneously.
Once copy editing and graphic editing completes, the document is published.
Reducing the complexity of state machines with Temporal
State machines simplified
temporal.io
2
When a document is submitted to the workflow, a controller component, which is
responsible for changing the state of the system, triggers an Editable event. How
the event is triggered depends on the nature of the controller. The event could
be triggered manually by a user by clicking a “Submit” button on a web page.
Or the event could be triggered automatically via a message queue working
in conjunction with an application’s controller software. (Using a message
queue is the approach taken in the implementation of the state machine in the
demonstration code).
The controller receives the event and changes the current state of the system.
For example in this article’s demonstration code, when the controller gets an
EVENT_EDITABLE event, it will set the current state to Editable like so…
AbstractState.current = AbstractState.editable;
The thing to understand about the state reassignment statement shown above is
that AbstractState is an abstract class that defines a set of public variables that
Reducing the complexity of state machines with Temporal
describe each state possible in the demonstration use case. Each state variable
is of type AbstractState too as shown in Listing 1 below.
package pubstatemachine.state;
import pubstatemachine.message.AbstractMessage;
import pubstatemachine.queue.SimpleMessageQueue;
Listing 1: The abstract class named AbstractState defines the various states of the demonstration use case
Later on logic in the Controller class assigns each of the public variables an
actual implementation of a class that inherits from AbstractState. The example
below shows an excerpt from the Controller code that assigns an instance of
the Editable class to the member variable AbstractState.editable.
In the case shown in Figure 1, at the start of the workflow the current state
is transitioned to Editable. Part of the transition behavior in the Editable
state is to issue two commands, EditGraphic and CopyEdit. Eventually, this
puts the system in a current state of AwaitingEdits. As the name implies,
the state of AwaitingEdits, waits to receive two events, WaitforCopyEdit and
Reducing the complexity of state machines with Temporal
WaitForGraphicEdit. These two events indicate that both editing tasks have been
completed. WaitforCopyEdit and WaitForGraphicEdit can occur in any order and
thus represents a rule composed of multiple conditions that must be satisfied in
order for the workflow to progress. (It’s worth noting that the work required to
support this composed rule in this demonstration use case is trivial. However, in
a real-world production scenario, supporting a composed rule can be a daunting
undertaking).
Listing 1 below shows the code excerpt from the Controller class of the
demonstration project. Event emission and command execution are facilitated by
messages received from a message queue. Those messages are processed by a
switch statement in the state machines Controller class as shown in Listing 2:
temporal.io
4
while (true) {
try {
AbstractMessage msg = queue.getMessage();
System.out.println(“Controller received message: “ + msg.getMessageType());
switch (msg.getMessageType()) {
case EVENT_EDITABLE:
processEventEditable(msg);
break;
case EVENT_AWAIT_GRAPHIC_EDIT:
case EVENT_AWAIT_COPY_EDIT:
processEventAwaitEdits(msg);
break;
case EVENT_PUBLISHABLE:
processEventPublishable(msg);
break;
case EVENT_PUBLISHED:
System.out.println(“Document published”);
break;
case COMMAND_GRAPHIC_EDIT:
processCommandGraphicEdit(msg);
break;
case COMMAND_COPY_EDIT:
processCommandCopyEdit(msg);
break;
case COMMAND_PUBLISH:
processCommandPublish(msg);
break;
default:
Reducing the complexity of state machines with Temporal
Listing 2: The logic in the controller class of the state machine demonstration code that processes messages
from a message queue.
State machines simplified
temporal.io
5
An important point to keep in mind is that the switch statement is only routing
messages to processing methods. It is not managing the sequence in which
state changes occur. State change order is governed by logic distributed among
the various transitions within the state machine. In other words, there is no
central place in the code that says, “go from the Editable state to the CopyEdit
and GraphicEdit states and then onto the Publish state.” Sequence management
is conducted implicitly via message emission dictated by a given transition rule.
For simple event-driven state machines this is manageable, but as you’ll see,
when state machines manage many sequential state changes, their complexity
increases.
state in the document publishing use case waits for both WaitforCopyEdit and
WaitForGraphicEdit events in order for the workflow to move forward. This is
an example of a composable rule. Supporting a limited number of composable
rules within a state machine is manageable. However, as more composable
rules are added to a state machine, the complexity of the machine increases, as
does the effort required to maintain and upgrade it. State machines that support
consensus require additional complexity.
Another complex task when building a state machine is maintaining the order
of actions in a sequential business process. In other words, making sure that
Step 1 is followed by Step 2 and then by Step 3, etc. The way the demonstration
State machines simplified
application ensures that the order of steps in the document publication process
is to introduce a class named StateMonitor. The StateMonitor class keeps track
of all the state transformations that have occurred in the application, and makes
it so a particular command associated with a particular state executes in the
expected order. Listing 3 below shows an excerpt of the update() method for the
AwaitingEdits class. As the name implies, the AwaitingEdit class represents the
state in which the document publication process is waiting for edits to complete.
The update() method is the associated transition.
temporal.io
6
StateMonitor sm = StateMonitor.getStateMonitor(message.getDocument());
.
.
.
Listing 3: The state machine demonstration used a class named StateMonitor to keep track of the various
states that the document publication process has passed through.
The transition rule emits a COMMAND_PUBLISH event, which tells the system to
publish the document, when the document has moved through GraphicEdit and
CopyEdit states.
Another challenge with state machines is maintaining system state in the event
of a failure. Failures are commonplace, even with the simplest of business
processes. Some failures, such as timeout, can be remedied by a retry. Others
require the system to be reverted to its last known good state. Addressing failure
in a State Machine is difficult; in fact, it can be just as much, if not more work as
creating the State Machine’s happy path.
When all is said and done, using a state machine for an application that has
a limited number of states and has infrequent changes makes sense (like
State machines simplified
temporal.io
7
Temporal: a simpler way to
manage state and transitions
Temporal is an open-source durable execution platform that abstracts away
the complexity of building scalable distributed systems. Temporal is specifically
intended for programming workflows, particularly complicated workflows. As
such, it has many of the characteristics of a state machine, yet avoids a good
deal of the work that goes with programming a state machine. Additionally,
because it provides durability, Temporal ensures state is always saved and all
processes complete, even in the event of a failure.
At the conceptual level, Temporal includes some of the parts typically found in a
state machine. These parts are built into the framework. A Temporal Workflow
is an essential primitive of the framework that can be thought of as a controller.
A Temporal Workflow manages the sequence and execution of activities.
Temporal Activities can be thought of as command handlers.
temporal.io
8
As with the state machine example mentioned previously, a document is
submitted to a workflow for processing. Then, the document is graphic edited
and copy edited simultaneously. The document is published after editing
completes. The high level behavior of both the state machine and Temporal
Workflow is similar. However, the effort that goes into implementing the Temporal
Workflow is apparent and straightforward, and requires significantly less time.
As shown below in Listing 4, all the code that manages the Workflow is in one
place.
Promise.allOf(promisesList).get();
} catch (ActivityFailure e) {
throw e;
Reducing the complexity of state machines with Temporal
}
}
}
The interesting thing to notice about the Workflow code in Listing 4 above is
that the editing and publishing work is encapsulated within Temporal Activities
as shown at Lines 7, 8 and 13. Temporal Activities are the fundamental, atomic
‘steps’ that act as the ‘doers’ in a Temporal Workflow. This implementation
demonstrates that they may be executed concurrently using Promises while the
Workflow blocks to await the results of both steps.
State machines simplified
إEasy compensations. In a state machine, even under the best case when
a state machine is well structured, compensation behavior would need to
Reducing the complexity of state machines with Temporal
temporal.io
In conclusion
Temporal’s essential value proposition is that the framework allows developers
to focus on what they do best: creating effective, efficient Workflows that meet
the needs of the business. When it comes to implementing complex business
processes, Temporal provides a structured and straightforward approach to
Workflow development that is hard for a state machine to emulate.
Reducing the complexity of state machines with Temporal
State machines simplified
11
temporal.io
J O I N O U R C O M M U N I T Y S L ACK