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:
295
docs/src/reference/asciidoc/sm-examples-cdplayer.adoc
Normal file
295
docs/src/reference/asciidoc/sm-examples-cdplayer.adoc
Normal 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.
|
||||
|
||||
80
docs/src/reference/asciidoc/sm-examples-datajpa.adoc
Normal file
80
docs/src/reference/asciidoc/sm-examples-datajpa.adoc
Normal 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[]
|
||||
----
|
||||
====
|
||||
101
docs/src/reference/asciidoc/sm-examples-datajpamultipersist.adoc
Normal file
101
docs/src/reference/asciidoc/sm-examples-datajpamultipersist.adoc
Normal 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%"]
|
||||
138
docs/src/reference/asciidoc/sm-examples-datajpapersist.adoc
Normal file
138
docs/src/reference/asciidoc/sm-examples-datajpapersist.adoc
Normal 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`.
|
||||
138
docs/src/reference/asciidoc/sm-examples-datapersist.adoc
Normal file
138
docs/src/reference/asciidoc/sm-examples-datapersist.adoc
Normal 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`.
|
||||
55
docs/src/reference/asciidoc/sm-examples-deploy.adoc
Normal file
55
docs/src/reference/asciidoc/sm-examples-deploy.adoc
Normal 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.
|
||||
217
docs/src/reference/asciidoc/sm-examples-eventservice.adoc
Normal file
217
docs/src/reference/asciidoc/sm-examples-eventservice.adoc
Normal 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]
|
||||
137
docs/src/reference/asciidoc/sm-examples-monitoring.adoc
Normal file
137
docs/src/reference/asciidoc/sm-examples-monitoring.adoc
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
----
|
||||
====
|
||||
52
docs/src/reference/asciidoc/sm-examples-ordershipping.adoc
Normal file
52
docs/src/reference/asciidoc/sm-examples-ordershipping.adoc
Normal 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%"]
|
||||
120
docs/src/reference/asciidoc/sm-examples-persist.adoc
Normal file
120
docs/src/reference/asciidoc/sm-examples-persist.adoc
Normal 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]
|
||||
----
|
||||
====
|
||||
29
docs/src/reference/asciidoc/sm-examples-scope.adoc
Normal file
29
docs/src/reference/asciidoc/sm-examples-scope.adoc
Normal 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]
|
||||
81
docs/src/reference/asciidoc/sm-examples-security.adoc
Normal file
81
docs/src/reference/asciidoc/sm-examples-security.adoc
Normal 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.
|
||||
209
docs/src/reference/asciidoc/sm-examples-showcase.adoc
Normal file
209
docs/src/reference/asciidoc/sm-examples-showcase.adoc
Normal 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.
|
||||
|
||||
214
docs/src/reference/asciidoc/sm-examples-tasks.adoc
Normal file
214
docs/src/reference/asciidoc/sm-examples-tasks.adoc
Normal 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.
|
||||
85
docs/src/reference/asciidoc/sm-examples-turnstile.adoc
Normal file
85
docs/src/reference/asciidoc/sm-examples-turnstile.adoc
Normal 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
|
||||
----
|
||||
====
|
||||
98
docs/src/reference/asciidoc/sm-examples-washer.adoc
Normal file
98
docs/src/reference/asciidoc/sm-examples-washer.adoc
Normal 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.
|
||||
|
||||
64
docs/src/reference/asciidoc/sm-examples-web.adoc
Normal file
64
docs/src/reference/asciidoc/sm-examples-web.adoc
Normal 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]
|
||||
111
docs/src/reference/asciidoc/sm-examples-zookeeper.adoc
Normal file
111
docs/src/reference/asciidoc/sm-examples-zookeeper.adoc
Normal 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
Reference in New Issue
Block a user