Split sample docs

- For sm-examples.adoc split every chapter into its own
  file to easy further reactive docs changes.
- Relates #742
This commit is contained in:
Janne Valkealahti
2019-05-14 23:10:35 +01:00
parent c213f62aef
commit 6208b3eefe
19 changed files with 2242 additions and 2099 deletions

View File

@@ -0,0 +1,295 @@
[[statemachine-examples-cdplayer]]
== CD Player
CD Player is a sample which resembles a use case that many people have
used in the real world. CD Player itself is a really simple entity that allows a
user to open a deck, insert or change a disk, and then drive the player's
functionality by pressing various buttons (`eject`, `play`,
`stop`, `pause`, `rewind`, and `backward`).
How many of us have really given thought to what it will take to
make code that interacts with hardware to drive a CD Player. Yes, the
concept of a player is simple, but, if you look behind the scenes,
things actually get a bit convoluted.
You have probably noticed that, if your deck is open and you press play, the
deck closes and a song starts to play (if a CD was inserted).
In a sense, when the deck is open, you first need to close
it and then try to start playing (again, if a CD is actually inserted). Hopefully,
you have now realized that a simple CD Player is so simple.
Sure, you can wrap all this with a simple class that has a few boolean variables
and probably a few nested if-else clauses. That will do the job, but what
about if you need to make all this behavior much more complex? Do you
really want to keep adding more flags and if-else clauses?
The following image shows the state machine for our simple CD player:
image::images/statechart3.png[width=500]
The rest of this section goes through how this sample and its state machine is designed and
how those two interacts with each other. The following three configuration sections
are used within an `EnumStateMachineConfigurerAdapter`.
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetAA]
----
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetAB]
----
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetAC]
----
====
In the preceding configuration:
* We used `EnumStateMachineConfigurerAdapter` to configure states and
transitions.
* The `CLOSED` and `OPEN` states are defined as substates of `IDLE`, and
the `PLAYING` and `PAUSED` states are defined as substates of `BUSY`.
* With the `CLOSED` state, we added an entry action as a bean called
`closedEntryAction`.
* In the transitions we mostly map events to expected state
transitions, such as `EJECT` closing and opening a deck and `PLAY`, `STOP`,
and `PAUSE` doing their natural transitions. For other transitions, we did the following:
** For source state `PLAYING`, we added a timer trigger, which is
needed to automatically track elapsed time within a playing track and
to have a facility for making the decision about when to switch the to next track.
** For the `PLAY` event, if the source state is `IDLE` and the target state is
`BUSY`, we defined an action called `playAction` and a guard called `playGuard`.
** For the `LOAD` event and the `OPEN` state, we defined an internal
transition with an action called `loadAction`, which tracks inserting a disc with
extended-state variables.
** The `PLAYING` state defines three internal transitions. One is
triggered by a timer that runs an action called `playingAction`, which updates
the extended state variables. The other two transitions use `trackAction`
with different events (`BACK` and `FORWARD`, respectively) to handle
when the user wants to go back or forward in tracks.
This machine has only have six states, which are defined by the following enumeration:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetB]
----
====
Events represent the buttons the user can
press and whether the user loads a disc into the player.
The following enumeration defines the events:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetC]
----
====
The `cdPlayer` and `library` beans are used to drive the application.
The following listing shows the definition of these two beans:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetD]
----
====
We define extended state variable keys as simple enumerations,
as the following listing shows:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetE]
----
====
We wanted to make this sample type safe, so we define our own
annotation (`@StatesOnTransition`), which has a mandatory meta
annotation (`@OnTransition`).
The following listing defines the `@StatesOnTransition` annotation:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetF]
----
====
`ClosedEntryAction` is an entry action for the `CLOSED` state, to
send a `PLAY` event to the state machine if a disc is present.
The following listing defines `ClosedEntryAction`:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetG]
----
====
`LoadAction` update an extended state variable if event
headers contain information about a disc to load.
The following listing defines `LoadAction`:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetH]
----
====
`PlayAction` resets the player's elapsed time, which is kept as
an extended state variable.
The following listing defines `PlayAction`:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetI]
----
====
`PlayGuard` guards the transition from `IDLE` to `BUSY` with the
`PLAY` event if the `CD` extended state variable does not indicate that a
disc has been loaded.
The following listing defines `PlayGuard`:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetJ]
----
====
`PlayingAction` updates an extended state variable called `ELAPSEDTIME`, which
the player can use to read and update its LCD status display. `PlayingAction` also handles
track shifting when the user goe back or forward in tracks.
The following example defines `PlayingAction`:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetK]
----
====
`TrackAction` handles track shift actions when the user goes back or forward
in tracks. If a track is the last on a disc, playing is stopped and the `STOP`
event is sent to a state machine.
The following example defines `TrackAction`:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetL]
----
====
One other important aspect of state machines is that they have their
own responsibilities (mostly around handling states) and that all application
level logic should be kept outside. This means that applications need
to have a ways to interact with a state machine. Also, note
that we annotated `CdPlayer` with `@WithStateMachine`, which instructs a
state machine to find methods from your POJO, which are then called
with various transitions.
The following example shows how it updates its LCD status display:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/CdPlayer.java[tags=snippetA]
----
====
In the preceding example, we use the `@OnTransition` annotation to hook a callback
when a transition happens with a target state of `BUSY`.
The following listing shows how our state machine handles whether the player is closed:
====
[source,java,indent=0]
----
include::samples/demo/cdplayer/CdPlayer.java[tags=snippetB]
----
====
`@OnTransition` (which we used in the preceding examples) can only be
used with strings that are matched from enumerations. `@StatesOnTransition`
lets you create your own type-safe annotations that use real enumerations.
The following example shows how this state machine actually works.
====
[source,text]
----
sm>sm start
Entry state IDLE
Entry state CLOSED
State machine started
sm>cd lcd
No CD
sm>cd library
0: Greatest Hits
0: Bohemian Rhapsody 05:56
1: Another One Bites the Dust 03:36
1: Greatest Hits II
0: A Kind of Magic 04:22
1: Under Pressure 04:08
sm>cd eject
Exit state CLOSED
Entry state OPEN
sm>cd load 0
Loading cd Greatest Hits
sm>cd play
Exit state OPEN
Entry state CLOSED
Exit state CLOSED
Exit state IDLE
Entry state BUSY
Entry state PLAYING
sm>cd lcd
Greatest Hits Bohemian Rhapsody 00:03
sm>cd forward
sm>cd lcd
Greatest Hits Another One Bites the Dust 00:04
sm>cd stop
Exit state PLAYING
Exit state BUSY
Entry state IDLE
Entry state CLOSED
sm>cd lcd
Greatest Hits
----
====
In the preceding run:
* The state machine is started, which causes the machine to be initialized.
* The CD player's LCD screen status is printed.
* The CD library is printed.
* The CD player's deck is opened.
* The CD with index 0 is loaded into a deck.
* Play causes the deck to get closed and immediate play, because a disc
was inserted.
* We print the LCD status and request the next track.
* We stop playing.

View File

@@ -0,0 +1,80 @@
[[statemachine-examples-datajpa]]
== JPA Configuration
The JPA configuration example shows how you can use state machine concepts
with a machine configuration kept in a database. This sample uses
an embedded H2 database with an H2 Console (to ease playing with the
database).
This sample uses `spring-statemachine-autoconfigure` (which, by default,
auto-configures the repositories and entity classes needed for JPA).
Thus, you need only `@SpringBootApplication`.
The following example shows the `Application` class with the `@SpringBootApplication` annotation:
====
[source,java,indent=0]
----
include::samples/demo/datajpa/Application.java[tags=snippetA]
----
====
The following example shows how to create a `RepositoryStateMachineModelFactory`:
====
[source,java,indent=0]
----
include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetB]
----
====
You can use the following command to run the sample:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-datajpa-{revnumber}.jar
----
====
Accessing the application at `http://localhost:8080` brings up a newly
constructed machine for each request. You can then choose to send
events to a machine. The possible events and machine configuration are
updated from a database with every request.
The following image shows the UI and the initial events that are created when
this state machine starts:
image::images/sm-datajpa-1.png[scaledwidth="100%"]
To access the embedded console, you can use the JDBC URL (which is `jdbc:h2:mem:testdb`, if it is
not already set).
The following image shows the H2 console:
image::images/sm-datajpa-2.png[scaledwidth="100%"]
From the console, you can see the database tables and modify
them as you wish.
The following image shows the result of a simple query in the UI:
image::images/sm-datajpa-3.png[scaledwidth="100%"]
Now that you have gotten this far, you have probably wondered how those default
states and transitions got populated into the database. Spring Data
has a nice trick to auto-populate repositories, and we
used this feature through `Jackson2RepositoryPopulatorFactoryBean`.
The following example shows how we create such a bean:
====
[source,java,indent=0]
----
include::samples/demo/datajpa/StateMachineConfig.java[tags=snippetA]
----
====
The following listing shows the source of the data with which we populate the database:
====
[source,json,indent=0]
----
include::samples/data.json[]
----
====

View File

@@ -0,0 +1,101 @@
[[statemachine-examples-datajpamultipersist]]
== Data Multi Persist
The data multi ersist sample is an extension of two other samples:
<<statemachine-examples-datajpa>> and <<statemachine-examples-datapersist>>.
We still keep machine configuration in a database and persist into a
database. However, this time, we also have a machine that contains two orthogonal
regions, to show how those are persisted independently. This sample
also uses an embedded H2 database with an H2 Console (to ease playing
with the database).
This sample uses `spring-statemachine-autoconfigure` (which, by default,
auto-configures the repositories and entity classes needed for JPA).
Thus, you need only `@SpringBootApplication`.
The following example shows the `Application` class with the `@SpringBootApplication` annotation:
====
[source,java,indent=0]
----
include::samples/demo/datajpamultipersist/Application.java[tags=snippetA]
----
====
As in the other data-driven samples, we again create a `StateMachineRuntimePersister`,
as the following listing shows:
====
[source,java,indent=0]
----
include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetA]
----
====
A `StateMachineService` bean makes it easier to work with a machines.
The following listing shows how to create such a bean:
====
[source,java,indent=0]
----
include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetB]
----
====
We use JSON data to import the configuration.
The following example creates a bean to do so:
====
[source,java,indent=0]
----
include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetC]
----
====
The following listing shows how we get a `RepositoryStateMachineModelFactory`:
====
[source,java,indent=0]
----
include::samples/demo/datajpamultipersist/StateMachineConfig.java[tags=snippetD]
----
====
You can run the sample by using the following command:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-datajpamultipersist-{revnumber}.jar
----
====
Accessing the application at `http://localhost:8080` brings up a newly
constructed machine for each request and lets you send
events to a machine. The possible events and the state machine configuration are
updated from a database for each request. We also print out
all state machine contexts and the current root machine,
as the following image shows:
image::images/sm-datajpamultipersist-1.png[scaledwidth="100%"]
The state machine named `datajpamultipersist1` is a simple "`flat`" machine where states `S1`,
`S2` and `S3` are transitioned by events `E1`, `E2`, and `E3` (respectively).
However, the state machine named `datajpamultipersist2` contains two
regions (`R1` and `R2`) directly under the root level. That is why this
root level machine really does not have a state. We need
that root level machine to host those regions.
Regions `R1` and `R2` in the `datajpamultipersist2` state machine contains states
`S10`, `S11`, and `S12` and `S20`, `S21`, and `S22` (respectively). Events
`E10`, `E11`, and `E12` are used for region `R1` and events `E20`, `E21`,
and event `E22` is used for region `R2`. The following images shows what happens when we
send events `E10` and `E20` to the
`datajpamultipersist2` state machine:
image::images/sm-datajpamultipersist-2.png[scaledwidth="100%"]
Regions have their own contexts with their own IDs, and the actual
ID is postfixed with `#` and the region ID. As the following image shows,
different regions in a database have different contexts:
image::images/sm-datajpamultipersist-3.png[scaledwidth="100%"]

View File

@@ -0,0 +1,138 @@
[[statemachine-examples-datapersist]]
== Data Persist
The data persist sample shows how you can state machine concepts
with a persisting machine in an external repository. This sample uses
an embedded H2 database with an H2 Console (to ease playing with the
database). Optionally, you can also enable Redis or MongoDB.
This sample uses `spring-statemachine-autoconfigure` (which, by default,
auto-configures the repositories and entity classes needed for JPA).
Thus, you need only `@SpringBootApplication`.
The following example shows the `Application` class with the `@SpringBootApplication` annotation:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/Application.java[tags=snippetA]
----
====
The `StateMachineRuntimePersister` interface works on the runtime
level of a `StateMachine`. Its implementation,
`JpaPersistingStateMachineInterceptor`, is meant to be used with a
JPA.
The following listing creates a `StateMachineRuntimePersister` bean:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetA]
----
====
The following example shows how you can use a very similar configuration
to create a bean for MongoDB:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetB]
----
====
The following example shows how you can use a very similar configuration
to create a bean for Redis:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetC]
----
====
You can configure `StateMachine` to use runtime persistence by using the
`withPersistence` configuration method.
The following listing shows how to do so:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetD]
----
====
This sample also uses `DefaultStateMachineService`, which makes it
easier to work with multiple machines.
The following listing shows how to create an instance of `DefaultStateMachineService`:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetE]
----
====
The following listing shows the logic that drives the `StateMachineService` in this sample:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineController.java[tags=snippetA]
----
====
You can use the following command to run the sample:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar
----
====
[NOTE]
====
By default, the `jpa` profile is enabled in `application.yml`. If you want to try
other backends, enable either the `mongo` profile or the `redis` profile.
The following commands specify which profile to use (`jpa` is the default,
but we included it for the sake of completeness):
=====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=jpa
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=mongo
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=redis
----
=====
====
Accessing the application at http://localhost:8080 brings up a newly
constructed state machine for each request, and you can choose to send
events to a machine. The possible events and machine configuration are
updated from a database with every request.
The state machines in this sample have a simple configuration with states 'S1'
to 'S6' and events 'E1' to 'E6' to transition the state machine between those
states. You can use two state machine identifiers (`datajpapersist1` and
`datajpapersist2`) to request a particular state machine.
The following image shows the UI that lets you pick a machine and an event and that shows
what happens when you do:
image::images/sm-datajpapersist-1.png[scaledwidth="100%"]
The sample defaults to using machine 'datajpapersist1' and goes to its
initial state 'S1'.
The following image shows the result of using those defaults:
image::images/sm-datajpapersist-2.png[scaledwidth="100%"]
If you send events `E1` and `E2` to the `datajpapersist1` state machine, its
state is persisted as 'S3'.
The following image shows the result of doing so:
image::images/sm-datajpapersist-3.png[scaledwidth="100%"]
If you then request state machine `datajpapersist1` but send no events,
the state machine is restored back to its persisted state, `S3`.

View File

@@ -0,0 +1,138 @@
[[statemachine-examples-datapersist]]
== Data Persist
The data persist sample shows how you can state machine concepts
with a persisting machine in an external repository. This sample uses
an embedded H2 database with an H2 Console (to ease playing with the
database). Optionally, you can also enable Redis or MongoDB.
This sample uses `spring-statemachine-autoconfigure` (which, by default,
auto-configures the repositories and entity classes needed for JPA).
Thus, you need only `@SpringBootApplication`.
The following example shows the `Application` class with the `@SpringBootApplication` annotation:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/Application.java[tags=snippetA]
----
====
The `StateMachineRuntimePersister` interface works on the runtime
level of a `StateMachine`. Its implementation,
`JpaPersistingStateMachineInterceptor`, is meant to be used with a
JPA.
The following listing creates a `StateMachineRuntimePersister` bean:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetA]
----
====
The following example shows how you can use a very similar configuration
to create a bean for MongoDB:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetB]
----
====
The following example shows how you can use a very similar configuration
to create a bean for Redis:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetC]
----
====
You can configure `StateMachine` to use runtime persistence by using the
`withPersistence` configuration method.
The following listing shows how to do so:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetD]
----
====
This sample also uses `DefaultStateMachineService`, which makes it
easier to work with multiple machines.
The following listing shows how to create an instance of `DefaultStateMachineService`:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineConfig.java[tags=snippetE]
----
====
The following listing shows the logic that drives the `StateMachineService` in this sample:
====
[source,java,indent=0]
----
include::samples/demo/datapersist/StateMachineController.java[tags=snippetA]
----
====
You can use the following command to run the sample:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar
----
====
[NOTE]
====
By default, the `jpa` profile is enabled in `application.yml`. If you want to try
other backends, enable either the `mongo` profile or the `redis` profile.
The following commands specify which profile to use (`jpa` is the default,
but we included it for the sake of completeness):
=====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=jpa
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=mongo
# java -jar spring-statemachine-samples-datapersist-{revnumber}.jar --spring.profiles.active=redis
----
=====
====
Accessing the application at http://localhost:8080 brings up a newly
constructed state machine for each request, and you can choose to send
events to a machine. The possible events and machine configuration are
updated from a database with every request.
The state machines in this sample have a simple configuration with states 'S1'
to 'S6' and events 'E1' to 'E6' to transition the state machine between those
states. You can use two state machine identifiers (`datajpapersist1` and
`datajpapersist2`) to request a particular state machine.
The following image shows the UI that lets you pick a machine and an event and that shows
what happens when you do:
image::images/sm-datajpapersist-1.png[scaledwidth="100%"]
The sample defaults to using machine 'datajpapersist1' and goes to its
initial state 'S1'.
The following image shows the result of using those defaults:
image::images/sm-datajpapersist-2.png[scaledwidth="100%"]
If you send events `E1` and `E2` to the `datajpapersist1` state machine, its
state is persisted as 'S3'.
The following image shows the result of doing so:
image::images/sm-datajpapersist-3.png[scaledwidth="100%"]
If you then request state machine `datajpapersist1` but send no events,
the state machine is restored back to its persisted state, `S3`.

View File

@@ -0,0 +1,55 @@
[[statemachine-examples-deploy]]
== Deploy
The deploy example shows how you can use state machine concepts with
UML modeling to provide a generic error handling state. This state
machine is a relatively complex example of how you can use various features
to provide a centralized error handling concept.
The following image shows the deploy state machine:
image::images/model-deployer.png[width=500]
NOTE: The preceding state chart was designed by using the Eclipse Papyrus Plugin
(see<<sm-papyrus>>) and imported into Spring StateMachine through the resulting UML
model file. Actions and guards defined in a model are resolved
from a Spring Application Context.
In this state machine scenario, we have two different behaviors
(`DEPLOY` and `UNDEPLOY`) that user tries to execute.
In the preceding state chart:
* In the `DEPLOY` state, the `INSTALL` and `START` states are entered
conditionally. We enter `START` directly if a product is already
installed and have no need to try to `START` if install fails.
* In the `UNDEPLOY` state, we enter `STOP` conditionally if the application is
already running.
* Conditional choices for `DEPLOY` and `UNDEPLOY` are done through a
choice pseudostate within those states, and the choices are selected
by guards.
* We use exit point pseudostates to have a more controlled exit from the
`DEPLOY` and `UNDEPLOY` states.
* After exiting from `DEPLOY` and `UNDEPLOY`, we go through a junction
pseudostate to choose whether to go through an `ERROR` state
(if an error was added into an extended state).
* Finally, we go back to the `READY` state to process new requests.
Now we can get to the actual demo. Run the boot based sample application
by running the following command:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-deploy-{revnumber}.jar
----
====
In a browser, you can see something like the following image:
image::images/sm-deploy-1.png[width=500]
IMPORTANT: As we do not have real install, start, or stop functionality, we
simulate failures by checking the existence of particular message headers.
Now you can start to send events to a machine and choose various
message headers to drive functionality.

View File

@@ -0,0 +1,217 @@
[[statemachine-examples-eventservice]]
== Event Service
The event service example shows how you can use state machine concepts as
a processing engine for events. This sample evolved from a question:
Can I use Spring Statemachine as a microservice to feed events to
different state machine instances? In fact, Spring Statemachine can feed
events to potentially millions of different state machine instances.
This example uses a `Redis` instance to persist state machine
instances.
Obviously, a million state machine instances in a JVM would be
a bad idea, due to memory constraints. This leads to
other features of Spring Statemachine that let you persist a
`StateMachineContext` and re-use existing instances.
For this example, we assume that a shopping application
sends different types of `PageView` events to a separate
microservice which then tracks user behavior by using a state
machine. The following image shows the state model, which has a few states
that represent a user navigating a product items list, adding and removing
items from a cart, going to a payment page, and initiating a payment
operation:
image::images/statechart14.png[width=500]
An actual shopping application would send these events into
this service by (for example) using a rest call. More about this
later.
NOTE: Remember that the focus here is to have an application that exposes a
`REST` API that the user can use to send events that can be processed by a
state machine for each request.
The following state machine configuration models what we have in a
state chart. Various actions update the state machine's `Extended
State` to track the number of entries into various states and also how
many times the internal transitions for `ADD` and `DEL` are called and whether
`PAY` has been executed:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetC]
----
====
Do not focus on `stateMachineTarget` or
`@Scope` for now, as we explain those later in this section.
We set up a `RedisConnectionFactory` that defaults to
localhost and default port. We use `StateMachinePersist` with a
`RepositoryStateMachinePersist` implementation. Finally, we create a
`RedisStateMachinePersister` that uses a previously
created `StateMachinePersist` bean.
These are then used in a `Controller` that handles `REST` calls,
as the following listing shows:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetD]
----
====
We create a bean named `stateMachineTarget`.
State machine instantiation is a relatively
expensive operation, so it is better to try to pool instances instead
of instantiating a new instance for every request. To do so, we first
create a `poolTargetSource` that wraps `stateMachineTarget` and pools
it with a max size of three. When then proxy this `poolTargetSource` with
`ProxyFactoryBean` by using a `request` scope. Effectively, this means
that every `REST` request gets a pooled state machine instance from
a bean factory. Later, we show how these instances are used.
The following listing shows how we create the `ProxyFactoryBean`
and set the target source:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetA]
----
====
The following listing shows we set the maximum size and set the target bean name:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineConfig.java[tags=snippetB]
----
====
Now we can get into actual demo. You need to have a Redis server running on
localhost with default settings. Then you need to run the Boot-based sample
application by running the following command:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-eventservice-{revnumber}.jar
----
====
In a browser, you see something like the following:
image::images/sm-eventservice-1.png[width=500]
In this UI, you can use three users: `joe`, `bob`, and `dave`.
Clicking a button shows the current state and the extended state. Enabling a
radio button before clicking a button sends a particular event for that
user. This arrangement lets you play with the UI.
In our `StateMachineController`, we autowire `StateMachine` and
`StateMachinePersister`. `StateMachine` is `request` scoped, so you
get a new instance for each request, while `StateMachinePersist` is a normal
singleton bean.
The following listing autowires `StateMachine` and
`StateMachinePersist`:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineController.java[tags=snippetA]
----
====
In the following listing, `feedAndGetState` is used with a UI to do same things that an
actual `REST` api might do:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineController.java[tags=snippetB]
----
====
In the following listing, `feedPageview` is a `REST` method that accepts a post with
JSON content.
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineController.java[tags=snippetC]
----
====
In the following listing, `feedMachine` sends an event into a `StateMachine` and persists
its state by using a `StateMachinePersister`:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineController.java[tags=snippetD]
----
====
The following listing shows a `resetStateMachineFromStore` that is used to restore a state machine
for a particular user:
====
[source,java,indent=0]
----
include::samples/demo/eventservice/StateMachineController.java[tags=snippetE]
----
====
As you would usually send an event by using a UI, you can do the same by using `REST` calls,
as the following curl command shows:
====
[source,text]
----
# curl http://localhost:8080/feed -H "Content-Type: application/json" --data '{"user":"joe","id":"VIEW_I"}'
----
====
At this point, you should have content in Redis with a key of
`testprefix:joe`, as the following example shows:
====
[source,text]
----
$ ./redis-cli
127.0.0.1:6379> KEYS *
1) "testprefix:joe"
----
====
The next three images show when state for `joe` has been changed from
`HOME` to `ITEMS` and when the `ADD` action has been executed.
The following image the `ADD` event being sent:
image::images/sm-eventservice-2.png[width=500]
Now your are still on the `ITEMS` state, and the internal transition caused
the `COUNT` extended state variable to increase to `1`, as the following image shows:
image::images/sm-eventservice-3.png[width=500]
Now you can run the following `curl` rest call a few times (or do it through the UI) and
see the `COUNT` variable increase with every call:
====
[source,text]
----
# curl http://localhost:8080/feed -H "Content-Type: application/json" # --data '{"user":"joe","id":"ADD"}'
----
====
The following image shows the result of these operations:
image::images/sm-eventservice-4.png[width=500]

View File

@@ -0,0 +1,137 @@
[[statemachine-examples-monitoring]]
== Monitoring
The monitoring sample shows how you can use state machine concepts to
monitor state machine transitions and actions.
The following listing configures the state machine that we use for this sample:
====
[source,java,indent=0]
----
include::samples/demo/monitoring/StateMachineConfig.java[tags=snippetA]
----
====
You can use the following command to run the sample:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-monitoring-{revnumber}.jar
----
====
The following image shows the state machine's initial state:
image::images/sm-monitoring-1.png[scaledwidth="100%"]
The following image shows the state of the state machine after we have
performed some actions:
image::images/sm-monitoring-2.png[scaledwidth="100%"]
You can view metrics from Spring Boot by running the following two `curl`
commands (shown with their output):
====
[source,json]
----
# curl http://localhost:8080/actuator/metrics/ssm.transition.duration
{
"name":"ssm.transition.duration",
"measurements":[
{
"statistic":"COUNT",
"value":3.0
},
{
"statistic":"TOTAL_TIME",
"value":0.007
},
{
"statistic":"MAX",
"value":0.004
}
],
"availableTags":[
{
"tag":"transitionName",
"values":[
"INITIAL_S1",
"EXTERNAL_S1_S2"
]
}
]
}
----
[source,json]
----
# curl http://localhost:8080/actuator/metrics/ssm.transition.transit
{
"name":"ssm.transition.transit",
"measurements":[
{
"statistic":"COUNT",
"value":3.0
}
],
"availableTags":[
{
"tag":"transitionName",
"values":[
"EXTERNAL_S1_S2",
"INITIAL_S1"
]
}
]
}
----
====
You can also view tracing from Spring Boot by running the following `curl`
command (shown with its output):
====
[source,json]
----
# curl http://localhost:8080/actuator/statemachinetrace
[
{
"timestamp":"2018-02-11T06:44:12.723+0000",
"info":{
"duration":2,
"machine":null,
"transition":"EXTERNAL_S1_S2"
}
},
{
"timestamp":"2018-02-11T06:44:12.720+0000",
"info":{
"duration":0,
"machine":null,
"action":"demo.monitoring.StateMachineConfig$Config$$Lambda$576/1499688007@22b47b2f"
}
},
{
"timestamp":"2018-02-11T06:44:12.714+0000",
"info":{
"duration":1,
"machine":null,
"transition":"INITIAL_S1"
}
},
{
"timestamp":"2018-02-11T06:44:09.689+0000",
"info":{
"duration":4,
"machine":null,
"transition":"INITIAL_S1"
}
}
]
----
====

View File

@@ -0,0 +1,52 @@
[[statemachine-examples-ordershipping]]
== Order Shipping
The order shipping example shows how you can use state machine concepts
to build a simple order processing system.
The following image shows a state chart that drives this order shipping sample.
image::images/sm-ordershipping-1.png[scaledwidth="100%"]
In the preceding state chart:
* The state machine enters the `WAIT_NEW_ORDER` (default) state.
* The event `PLACE_ORDER` transitions into the `RECEIVE_ORDER` state and the entry
action (`entryReceiveOrder`) is executed.
* If the order is `OK`, the state machine goes into two regions, one handling order
production and one handling user-level payment. Otherwise, the state machine goes
into `CUSTOMER_ERROR`, which is a final state.
* The state machine loops in a lower region to remind the user to pay
until `RECEIVE_PAYMENT` is sent successfully to indicate correct
payment.
* Both regions go into waiting states (`WAIT_PRODUCT` and
`WAIT_ORDER`), where they are joined before the parent orthogonal state
(`HANDLE_ORDER`) is exited.
* Finally, the state machine goes through `SHIP_ORDER` to its final state
(`ORDER_SHIPPED`).
The following command runs the sample:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-ordershipping-{revnumber}.jar
----
====
In a browser, you can see something similar to the following image. You can start by choosing
a customer and an order to create a state machine.
image::images/sm-ordershipping-2.png[scaledwidth="100%"]
The state machine for a particular order is now created and you can start to play
with placing an order and sending a payment. Other settings (such as
`makeProdPlan`, `produce`, and `payment`) let you control how the state
machine works.
The following image shows the state machine waiting for an order:
image::images/sm-ordershipping-3.png[scaledwidth="100%"]
Finally, you can see what machine does by refreshing a page, as the following image shows:
image::images/sm-ordershipping-4.png[scaledwidth="100%"]

View File

@@ -0,0 +1,120 @@
[[statemachine-examples-persist]]
== Persist
Persist is a sample that uses the <<statemachine-recipes-persist>> recipe to
demonstrate how database entry update logic can be controlled by a
state machine.
The following image shows the state machine logic and configuration:
image::images/statechart10.png[width=500]
The following listing shows the state machine configuration:
====
.StateMachine Config
[source,java,indent=0]
----
include::samples/demo/persist/Application.java[tags=snippetA]
----
====
The following configuration creates `PersistStateMachineHandler`:
====
.Handler Config
[source,java,indent=0]
----
include::samples/demo/persist/Application.java[tags=snippetB]
----
====
The following listing shows the `Order` class used with this sample:
====
.Order Class
[source,java,indent=0]
----
include::samples/demo/persist/Application.java[tags=snippetC]
----
====
The following example shows the state machine's output:
====
[source,text]
----
sm>persist db
Order [id=1, state=PLACED]
Order [id=2, state=PROCESSING]
Order [id=3, state=SENT]
Order [id=4, state=DELIVERED]
sm>persist process 1
Exit state PLACED
Entry state PROCESSING
sm>persist db
Order [id=2, state=PROCESSING]
Order [id=3, state=SENT]
Order [id=4, state=DELIVERED]
Order [id=1, state=PROCESSING]
sm>persist deliver 3
Exit state SENT
Entry state DELIVERED
sm>persist db
Order [id=2, state=PROCESSING]
Order [id=4, state=DELIVERED]
Order [id=1, state=PROCESSING]
Order [id=3, state=DELIVERED]
----
====
In the preceding run, the state machine:
* Listed rows from an existing embedded database, which is already
populated with sample data.
* Requested to update order `1` into the `PROCESSING` state.
* List database entries again and see that the state has been changed from
`PLACED` to `PROCESSING`.
* Update order `3` to update its state from `SENT` to
`DELIVERED`.
[NOTE]
====
You may wonder where the database is, because there are literally no
signs of it in the sample code. The sample is based on Spring Boot and,
because the necessary classes are in a classpath, an embedded `HSQL` instance
is created automatically.
Spring Boot even creates an instance of `JdbcTemplate`, which you
can autowire, as we did in `Persist.java`, shown in the following listing:
=====
[source,java,indent=0]
----
include::samples/demo/persist/Persist.java[tags=snippetA]
----
=====
====
Next, we need to handle state changes. The following listing shows how we do so:
====
[source,java,indent=0]
----
include::samples/demo/persist/Persist.java[tags=snippetB]
----
====
Finally, we use a `PersistStateChangeListener` to update the database, as the
following listing shows:
====
[source,java,indent=0]
----
include::samples/demo/persist/Persist.java[tags=snippetC]
----
====

View File

@@ -0,0 +1,29 @@
[[statemachine-examples-scope]]
== Scope
Scope is a state machine example that uses session scope to provide an
individual instance for every user.
The following image shows the states and events within the Scope state machine:
image::images/statechart12.png[width=500]
This simple state machine has three states: `S0`, `S1`, and `S2`.
Transitions between those are controlled by three events: `A`, `B`, and `C`.
To start the state machine, run the following command in a terminal:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-scope-{revnumber}.jar
----
====
When the instance is running, you can open a browser and play with the state
machine. If you open the same page in a different browser, (for example, one in
Chrome and one in Firefox), you should get a new state machine
instance for each user session.
The following image shows the state machine in a browser:
image::images/sm-scope-1.png[width=500]

View File

@@ -0,0 +1,81 @@
[[statemachine-examples-security]]
== Security
Security is a state machine example that uses most of the possible combinations of
securing a state machine. It secures sending events, transitions,
and actions.
The following image shows the state machine's states and events:
image::images/statechart13.png[width=500]
To start the state machine, run the following command:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-secure-{revnumber}.jar
----
====
We secure event sending by requiring that users have a role of `USER`.
Spring Security ensures that no other users can send events to this
state machine.
The following listing secures event sending:
====
[source,java,indent=0]
----
include::samples/demo/security/StateMachineConfig.java[tags=snippetA]
----
====
In this sample we define two users:
* A user named `user` who has a role of `USER`
* A user named `admin` who has two roles: `USER` and `ADMIN`
The password for both users is `password`.
The following listing configures the two users:
====
[source,java,indent=0]
----
include::samples/demo/security/StateMachineConfig.java[tags=snippetE]
----
====
We define various transitions between states according to the state chart
shown at the beginning of the example. Only a user with an active `ADMIN` role can run
the external transitions between `S2` and `S3`. Similarly only an `ADMIN` can
run the internal transition the `S1` state.
The following listing defines the transitions, including their security:
====
[source,java,indent=0]
----
include::samples/demo/security/StateMachineConfig.java[tags=snippetB]
----
====
The following listing uses a method called `adminAction` whose return type is `Action` to
specify that the action is secured with a role of `ADMIN`:
====
[source,java,indent=0]
----
include::samples/demo/security/StateMachineConfig.java[tags=snippetC]
----
====
The following `Action` runs an internal transition in state `S` when event `F` is sent.
====
[source,java,indent=0]
----
include::samples/demo/security/StateMachineConfig.java[tags=snippetD]
----
====
The transition itself is secured with a
role of `ADMIN`, so this transition does not run if the current user
does not hate that role.

View File

@@ -0,0 +1,209 @@
[[statemachine-examples-showcase]]
== Showcase
Showcase is a complex state machine that shows all possible transition
topologies up to four levels of state nesting.
The following image shows the state machine:
image::images/statechart2.png[width=500]
The following listing shows the enumeration that defines the possible states:
====
.States
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetB]
----
====
The following listing shows the enumeration that defines the events:
====
.Events
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetC]
----
====
The following listing shows the code that configures the state machine:
====
.Configuration - states
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetAA]
----
====
The following listing shows the code that configures the state machine's transitions:
====
.Configuration - transitions
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetAB]
----
====
The following listing shows the code that configures the state machine's actions and guards:
====
.Configuration - actions and guards
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetAC]
----
====
The following listing shows how the single action is defined:
====
.Action
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetD]
----
====
The following listing shows how the single guard is defined:
====
.Guard
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetE]
----
====
The following listing shows the output that this state machine produces when it runs and
various events are sent to it:
====
[source,text]
----
sm>sm start
Init foo to 0
Entry state S0
Entry state S1
Entry state S11
State machine started
sm>sm event A
Event A send
sm>sm event C
Exit state S11
Exit state S1
Entry state S2
Entry state S21
Entry state S211
Event C send
sm>sm event H
Switch foo to 1
Internal transition source=S0
Event H send
sm>sm event C
Exit state S211
Exit state S21
Exit state S2
Entry state S1
Entry state S11
Event C send
sm>sm event A
Exit state S11
Exit state S1
Entry state S1
Entry state S11
Event A send
----
====
In the preceding output, we can see that:
* The state machine is started, which takes it to its initial state (`S11`)
through superstates (`S1`) and (`S0`). Also, the extended state variable, `foo`, is
initialized to `0`.
* We try to execute a self transition in state `S1` with event `A`, but
nothing happens because the transition is guarded by variable `foo` to
be `1`.
* We send event `C`, which takes us to the other state machine, where
the initial state (`S211`) and its superstates are entered. In there, we
can use event `H`, which does a simple internal transition to flip the
`foo` variable. Then we go back by using event `C`.
* Event `A` is sent again, and now `S1` does a self transition because the
guard evaluates to `true`.
The following example offers a closer look at how hierarchical states and their event
handling works:
====
[source,text]
----
sm>sm variables
No variables
sm>sm start
Init foo to 0
Entry state S0
Entry state S1
Entry state S11
State machine started
sm>sm variables
foo=0
sm>sm event H
Internal transition source=S1
Event H send
sm>sm variables
foo=0
sm>sm event C
Exit state S11
Exit state S1
Entry state S2
Entry state S21
Entry state S211
Event C send
sm>sm variables
foo=0
sm>sm event H
Switch foo to 1
Internal transition source=S0
Event H send
sm>sm variables
foo=1
sm>sm event H
Switch foo to 0
Internal transition source=S2
Event H send
sm>sm variables
foo=0
----
====
In the preceding sample:
* We print extended state variables in various stages.
* With event `H`, we end up running an internal transition,
which is logged with its source state.
* Note how event `H` is handled in
different states (`S0`, `S1`, and `S2`). This is a good example of how
hierarchical states and their event handling works. If state `S2` is
unable to handle event `H` due to a guard condition, its parent is
checked next. This guarantees that, while the machine is on state `S2`, the `foo` flag
is always flipped around. However, in state `S1`, event `H` always
matches to its dummy transition without guard or action, so it never
happens.

View File

@@ -0,0 +1,214 @@
[[statemachine-examples-tasks]]
== Tasks
The Tasks sample demonstrates parallel task handling within
regions and adds error handling to either
automatically or manually fix task problems before continuing back
to a state where the tasks can be run again.
The following image shows the Tasks state machine:
image::images/statechart5.png[width=500]
On a high level, in this state machine:
* We always try to get into the `READY` state so that we can use the
RUN event to execute tasks.
* Tkhe `TASKS` state, which is composed of three independent regions, has been
put in the middle of `FORK` and `JOIN` states, which will cause the regions to
go into their initial states and to be joined by their end states.
* From the `JOIN` state, we automatically go into a `CHOICE` state, which checks
for the existence of error flags in extended state variables. Tasks can set
these flags, and doing so gives the `CHOICE` state the ability to go into the `ERROR`
state, where errors can be handled either automatically or manually.
* The `AUTOMATIC` state in `ERROR` can try to automatically fix an error and goes
back to `READY` if it succeeds. If the error is something what
cannot be handled automatically, user intervention is needed and the
machine is put into the `MANUAL` state by the `FALLBACK` event.
The following listing shows the enumeration that defines the possible states:
====
.States
[source,java,indent=0]
----
include::samples/demo/tasks/Application.java[tags=snippetB]
----
====
The following listing shows the enumeration that defines the events:
====
.Events
[source,java,indent=0]
----
include::samples/demo/tasks/Application.java[tags=snippetC]
----
====
The following listing configures the possible states:
====
.Configuration - states
[source,java,indent=0]
----
include::samples/demo/tasks/Application.java[tags=snippetAA]
----
====
The following listing configures the possible transitions:
====
.Configuration - transitions
[source,java,indent=0]
----
include::samples/demo/tasks/Application.java[tags=snippetAB]
----
====
The following guard sends a choice entry into the `ERROR` state and needs to
return `TRUE` if an error has happened. This guard checks that
all extended state variables(`T1`, `T2`, and `T3`) are `TRUE`.
====
[source,java,indent=0]
----
include::samples/demo/tasks/Application.java[tags=snippetAC]
----
====
The following actions below send events to the state machine to request
the next step, which is either to fall back or to continue back to ready.
====
[source,java,indent=0]
----
include::samples/demo/tasks/Application.java[tags=snippetAD]
----
====
Currently, the default region execution is synchronous, but you can change
it to be asynchronous by changing `TaskExecutor`. Task simulates
work by sleeping two seconds so that you can see how actions in
regions are executed inparallel.
The following listing shows the `TaskExecutor` bean definition:
====
[source,java,indent=0]
----
include::samples/demo/tasks/Application.java[tags=snippetAE]
----
====
The following example shows how this state machine actually works:
====
[source,text]
----
sm>sm start
State machine started
Entry state READY
sm>tasks run
Entry state TASKS
run task on T3
run task on T2
run task on T1
run task on T2 done
run task on T1 done
run task on T3 done
Entry state T2
Entry state T3
Entry state T1
Entry state T1E
Entry state T2E
Entry state T3E
Exit state TASKS
Entry state JOIN
Exit state JOIN
Entry state READY
----
====
In the preceding listing, we can see that tasks run multiple times.
In the next listing, we introduce errors:
====
[source,text]
----
sm>tasks list
Tasks {T1=true, T3=true, T2=true}
sm>tasks fail T1
sm>tasks list
Tasks {T1=false, T3=true, T2=true}
sm>tasks run
Entry state TASKS
run task on T1
run task on T3
run task on T2
run task on T1 done
run task on T3 done
run task on T2 done
Entry state T1
Entry state T3
Entry state T2
Entry state T1E
Entry state T2E
Entry state T3E
Exit state TASKS
Entry state JOIN
Exit state JOIN
Entry state ERROR
Entry state AUTOMATIC
Exit state AUTOMATIC
Exit state ERROR
Entry state READY
----
====
In the preceding listing, if we simulate a failure for task T1, it is fixed
automatically.
In the next listing, we introduce more errors:
====
[source,text]
----
sm>tasks list
Tasks {T1=true, T3=true, T2=true}
sm>tasks fail T2
sm>tasks run
Entry state TASKS
run task on T2
run task on T1
run task on T3
run task on T2 done
run task on T1 done
run task on T3 done
Entry state T2
Entry state T1
Entry state T3
Entry state T1E
Entry state T2E
Entry state T3E
Exit state TASKS
Entry state JOIN
Exit state JOIN
Entry state ERROR
Entry state AUTOMATIC
Exit state AUTOMATIC
Entry state MANUAL
sm>tasks fix
Exit state MANUAL
Exit state ERROR
Entry state READY
----
====
In the precding example, if we simulate failure for either task `T2` or `T3`, the state
machine goes to the `MANUAL` state, where problem needs to be fixed manually
before it can go back to the `READY` state.

View File

@@ -0,0 +1,85 @@
[[statemachine-examples-turnstile]]
== Turnstile
Turnstile is a simple device that gives you access if payment is
made. It is a concept that is simple to model using a state machine. In its
simplest, form there are only two states: `LOCKED` and `UNLOCKED`. Two
events, `COIN` and `PUSH` can happen, depending on whether someone
makes a payment or tries to go through the turnstile.
The following image shows the state machine:
image::images/statechart1.png[width=500]
The following listing shows the enumeration that defines the possible states:
====
.States
[source,java,indent=0]
----
include::samples/demo/turnstile/Application.java[tags=snippetB]
----
====
The following listing shows the enumeration that defines the events:
====
.Events
[source,java,indent=0]
----
include::samples/demo/turnstile/Application.java[tags=snippetC]
----
====
The following listing shows the code that configures the state machine:
====
.Configuration
[source,java,indent=0]
----
include::samples/demo/turnstile/Application.java[tags=snippetA]
----
====
You can see how this sample state machine interacts with events by
running the `turnstile` sample. The following listing shows how to do so
and shows the command's output:
====
[source,text,subs="verbatim,attributes"]
----
$ java -jar spring-statemachine-samples-turnstile-{revnumber}.jar
sm>sm print
+----------------------------------------------------------------+
| SM |
+----------------------------------------------------------------+
| |
| +----------------+ +----------------+ |
| *-->| LOCKED | | UNLOCKED | |
| +----------------+ +----------------+ |
| +---| entry/ | | entry/ |---+ |
| | | exit/ | | exit/ | | |
| | | | | | | |
| PUSH| | |---COIN-->| | |COIN |
| | | | | | | |
| | | | | | | |
| | | |<--PUSH---| | | |
| +-->| | | |<--+ |
| | | | | |
| +----------------+ +----------------+ |
| |
+----------------------------------------------------------------+
sm>sm start
State changed to LOCKED
State machine started
sm>sm event COIN
State changed to UNLOCKED
Event COIN send
sm>sm event PUSH
State changed to LOCKED
Event PUSH send
----
====

View File

@@ -0,0 +1,98 @@
[[statemachine-examples-washer]]
== Washer
The washer sample demonstrates how to use a history state to recover a
running state configuration with a simulated power-off situation.
Anyone who has ever used a washing machine knows that if you somehow pause
the program, it continue from the same state when unpaused.
You can implement this kind of behavior in a state machine by using
a history pseudo state.
The following image shows our state machine for a washer:
image::images/statechart6.png[width=500]
The following listing shows the enumeration that defines the possible states:
====
.States
[source,java,indent=0]
----
include::samples/demo/washer/Application.java[tags=snippetB]
----
====
The following listing shows the enumeration that defines the events:
====
.Events
[source,java,indent=0]
----
include::samples/demo/washer/Application.java[tags=snippetC]
----
====
The following listing configures the possible states:
====
.Configuration - states
[source,java,indent=0]
----
include::samples/demo/washer/Application.java[tags=snippetAA]
----
====
The following listing configures the possible transitions:
====
.Configuration - transitions
[source,java,indent=0]
----
include::samples/demo/washer/Application.java[tags=snippetAB]
----
====
The following example shows how this state machine actually works:
====
[source,text]
----
sm>sm start
Entry state RUNNING
Entry state WASHING
State machine started
sm>sm event RINSE
Exit state WASHING
Entry state RINSING
Event RINSE send
sm>sm event DRY
Exit state RINSING
Entry state DRYING
Event DRY send
sm>sm event CUTPOWER
Exit state DRYING
Exit state RUNNING
Entry state POWEROFF
Event CUTPOWER send
sm>sm event RESTOREPOWER
Exit state POWEROFF
Entry state RUNNING
Entry state WASHING
Entry state DRYING
Event RESTOREPOWER send
----
====
In the preceding run:
* The state machine is started, which causes machine to get initialized.
* The state machine goes to RINSING state.
* The state machine goes to DRYING state.
* The state machine cuts power and goes to POWEROFF state.
* The state is restored from the HISTORY state, which takes state machine back
to its previous known state.

View File

@@ -0,0 +1,64 @@
[[statemachine-examples-web]]
== Web
Web is a distributed state machine example that uses a zookeeper state machine to handle
distributed state. See <<statemachine-examples-zookeeper>>.
NOTE: This example is meant to be run on multiple
browser sessions against multiple different hosts.
This sample uses a modified state machine structure from
<<statemachine-examples-showcase>> to work with a distributed state
machine. The following image shows the state machine logic:
image::images/statechart11.png[width=500]
NOTE: Due to the nature of this sample, an instance of a `Zookeeper` state machine is expected to
be available from a localhost for every individual sample instance.
This demonstration uses an example that starts three different sample instances.
If you run different instances on the same host, you need to
distinguish the port each one uses by adding `--server.port=<myport>` to the command.
Otherwise the default port for each host is `8080`.
In this sample run, we have three hosts: `n1`, `n2`, and `n3`. Each one
has a local zookeeper instance running and a state machine sample running
on a port `8080`.
In there different terminals, start the three different state machines by running
the following command:
====
[source,text,subs="attributes"]
----
# java -jar spring-statemachine-samples-web-{revnumber}.jar
----
====
When all instances are running, you should see that all show similar
information when you access them with a browser. The states should be `S0`, `S1`, and `S11`.
The extended state variable named `foo` should have a value of `0`. The main state is `S11`.
image::images/sm-dist-n1-1.png[width=500]
When you press the `Event C` button in any of the browser windows, the
distributed state is changed to `S211,` which is the target state
denoted by the transition associated with an event of type `C`.
The following image shows the change:
image::images/sm-dist-n2-2.png[width=500]
Now we can press the `Event H` button and see that the
internal transition runs on all state machines to change the
the value of the extended state variable named `foo` from `0` to `1`. This change is
first done on the state machine that receives the event and is then propagated
to the other state machines. You should see only the variable named `foo` change
from `0` to `1`.
image::images/sm-dist-n3-3.png[width=500]
Finally, we can send `Event K`, which takes the state
machine state back to state `S11`. You should see this happen in
all of the browsers. The following image shows the result in one browser:
image::images/sm-dist-n1-4.png[width=500]

View File

@@ -0,0 +1,111 @@
[[statemachine-examples-zookeeper]]
== Zookeeper
Zookeeper is a distributed version from the
<<statemachine-examples-turnstile>> sample.
NOTE: This sample needs an external `Zookeeper` instance that is accessible from
`localhost` and has the default port and settings.
Configuration of this sample is almost the same as the `turnstile` sample. We
add only the configuration for the distributed state machine where we
configure `StateMachineEnsemble`, as the following listing shows:
====
[source,java,indent=0]
----
include::samples/demo/zookeeper/Application.java[tags=snippetA]
----
====
The actual `StateMachineEnsemble` needs to be created as a bean, together
with the `CuratorFramework` client, as the following example shows:
====
[source,java,indent=0]
----
include::samples/demo/zookeeper/Application.java[tags=snippetB]
----
====
For the next example, we need to create two different shell instances.
We need to create one instance, see what happens, and then create the second instance.
The following command starts the shell instances (remember to start only one instance for now):
====
[source,text,subs="attributes"]
----
@n1:~# java -jar spring-statemachine-samples-zookeeper-{revnumber}.jar
----
====
When state machine is started, its initial state is
`LOCKED`. Then it sends a `COIN` event to transition into `UNLOCKED` state.
The following example shows what happens:
====
.Shell1
[source,text]
----
sm>sm start
Entry state LOCKED
State machine started
sm>sm event COIN
Exit state LOCKED
Entry state UNLOCKED
Event COIN send
sm>sm state
UNLOCKED
----
====
Now you can open a second shell instance and start a state machine,
by using the same command that you used to start the first state machine. You should see
that the distributed state (`UNLOCKED`) is entered instead of the default
initial state (`LOCKED`).
The following example shows the state machine and its output:
====
.Shell2
[source,text]
----
sm>sm start
State machine started
sm>sm state
UNLOCKED
----
====
Then from either shell (we use second instance in the next example), send a
`PUSH` event to transit from the `UNLOCKED` into the `LOCKED` state.
The following example shows the state machine command and its output:
====
.Shell2
[source,text]
----
sm>sm event PUSH
Exit state UNLOCKED
Entry state LOCKED
Event PUSH send
----
====
In the other shell (the first shell if you ran the preceding command in the second shell),
you should see the state be changed automatically,
based on distributed state kept in Zookeeper.
The following example shows the state machine command and its output:
====
.Shell1
[source,text]
----
sm>Exit state UNLOCKED
Entry state LOCKED
----
====

File diff suppressed because it is too large Load Diff