Reference doc updates

This commit is contained in:
Janne Valkealahti
2015-04-05 22:22:45 +01:00
parent 3f3a8bbc65
commit e5c67b1c22
13 changed files with 289 additions and 42 deletions

View File

@@ -29,7 +29,7 @@ Assuming we have states _STATE1_, _STATE2_ and events _EVENT1_,
_EVENT2_, logic of state machine can be defined as shown in below
quick example.
image::images/statechart0.png[]
image::images/statechart0.png[width=500]
[source,java,indent=0]
----
@@ -129,16 +129,53 @@ A transition is a relationship between a source state and a target
state. A switch from a state to another is a _state transition_ caused
by a _trigger_.
===== Internal Transition
Internal transition is used when action needs to be executed without
causing a state transition. With internal transition source and target
state is always a same and it is identical with self-transition in the
absence of state entry and exit actions.
===== External vs. Local Transition
Most of the cases external and local transition are functionally
equivalent expect in cases where transition is happening between super
and sub states. Local transition doesn't cause exit and entry to
source state if target state is a substate of a source state. Other
way around, local transition doesn't cause exit and entry to target
state if target is a superstate of a source state.
image::images/statechart4.png[width=500]
Above image shows a different between local and external transitions
with a very simplistic super and sub states.
==== Actions
Actions are the ones which really glues state machine state changes
with a users own code. State machine can execute action on various
changes and steps in a state machine like entering or exiting a state,
or doing a state transition.
Actions usually have access to a state context which gives running
code a choice to interact with a state machine in a various ways.
State context i.e. is exposing a whole state machine so user can
access extended state variables, event headers if transition is based
on an event, or actual transition where it is possible to see more
detailed where this state change is coming from and where it is going.
==== Hierarchical State Machines
Concept of a hierarchical state machine is used to simplify state
design when particular states can only exist together.
Hierarchical states are really an innovation in UML state machine over
a traditional state machines like Mealy or Moore machines.
Hierarchical states allows to define some level of abstraction is a
sense how java developer would define a class structure with abstract
classes. For example having a nested state machine user is able to
define transition on a multiple level of states possibly with a
different conditions. State machine will always try to see if current
state is able to handle an event together with a transition guard
conditions. If these conditions are not evaluated to true, state
machine will simply see what a super state can handle.
==== Regions
Regions which are also called as orthogonal regions are usually viewed
as exclusive OR operation applied to a states. Concept of a region in

View File

@@ -0,0 +1,43 @@
[[statemachine-faq]]
= FAQ
This chapter tries to give solutions to question user is most likely
to ask.
== State Changes
.I want to transit to next state automatically
{zwsp} +
There are few choices a state machine developer can choose.
* Implement an action and send appropriate event into a state machine
which triggers a transition into a proper target state.
* Define deferred event within a state and before sending an event
send a event which will be deferred and thus causing next
appropriate state transition when it is more convenient to handle
that event.
* Implement a triggerless transition which will automatically cause
state transition into a next state when state has entry and its
actions has been completed.
.How do I defer an event
{zwsp} +
For more complete example and explanation, see cdplayer sample.
== Extented State
.How I can initialise variables on state machine start
{zwsp} +
Important concept in a state machine is that nothing really happens
unless there is a trigger which is causing a state transition which
then can fire actions. However, having said that, Spring Statemachine
always have an initial transition when state machine is started. With
this initial transition user can execute a simple action which within
a _StateContext_ can do whatever it likes with an extended state
variables.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -36,4 +36,5 @@ include::introduction.adoc[]
include::sm.adoc[]
include::sm-examples.adoc[]
include::faq.adoc[]
include::appendix.adoc[]

View File

@@ -10,18 +10,25 @@ familiar with state machine concepts.
== Requirements
Spring Statemachine {revnumber} is built and tested with JDK 7 and Spring
Framework {spring-version}.
Framework {spring-version} and doesn't require any other dependencies
outside of Spring Framework. Samples will require spring-shell and
spring-boot which will pull other dependencies beyond Framework
itself.
== Background
State machines are powerful because behaviour is always supposed to be
State machines are powerful because behaviour is always guaranteed to be
consistent and relatively easily debugged due to ways how operational
rules are written in stone when machine is started. Idea is that your
application is and may exist in a finite number of states and then something
happens which takes your application from one state to the next.
happens which takes your application from one state to the next. What
will drive a state machine are triggers which are either based on an
events or timers.
It is much easier to design high level logic outside of your
application and then interact witha state machine with a various
different ways.
application and then interact with a state machine with a various
different ways. You will simple send interact with a state machine by
sending event, listening what a state machine does or simply request a
current state.
Traditionally state machines are added to a existing project when
developer realizes that code base is starting to look like a plate
@@ -31,7 +38,7 @@ ask developer to go home when things are starting to look too complex.
== Usage Scenarios
Project is a good candiate to use state machines if:
Project is a good candiate to use state machine if:
* Application or part of its structure can be represented as states.
* You want to split complex logic into smaller manageable tasks.
@@ -47,3 +54,12 @@ You are already trying to implement a state machine if:
enum is set and then making further exceptions what to do when certain
combination of your flags and enums exists or doesn't exist together.
== Feature Limitations
A lot of features are planned thus not all expected feature are ntot
yet implemented:
* Regions are not fully supported and currently contains many bugs.
* Features around pseudo states are missing functionality like history
states, fork/join with regions, etc.

View File

@@ -18,5 +18,7 @@ This reference documentations contains following parts.
<<statemachine-examples>> more detailed state machine samples
<<statemachine-faq>> frequently ask questions
<<appendices>> generic info about used material and state machines

View File

@@ -15,7 +15,7 @@ simples form there are only two states, `LOCKED` and `UNLOCKED`. Two
events, `COIN` and `PUSH` can happen if you try to go through it or
you make a payment.
image::images/statechart1.png[]
image::images/statechart1.png[width=500]
.States
[source,java,indent=0]
@@ -79,7 +79,7 @@ Event PUSH send
Showcase is a complex state machine showing all possible transition
topologies up to four levels of state nesting.
image::images/statechart2.png[width=200]
image::images/statechart2.png[width=500]
.States
[source,java,indent=0]
@@ -93,19 +93,31 @@ include::samples/demo/showcase/Application.java[tags=snippetB]
include::samples/demo/showcase/Application.java[tags=snippetC]
----
.Configuration
.Configuration - states
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetA]
include::samples/demo/showcase/Application.java[tags=snippetAA]
----
.Guard
.Configuration - transitions
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetAB]
----
.Configuration - actions and guard
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetAC]
----
.Action
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetD]
----
.Action
.Guard
[source,java,indent=0]
----
include::samples/demo/showcase/Application.java[tags=snippetE]
@@ -133,14 +145,25 @@ and probably few nested if/else clauses, that will do the job, but what
about if you need to make all this behaviour much more complex, do you
really want to keep adding more flags and if/else clauses.
image::images/statechart3.png[]
image::images/statechart3.png[width=500]
Lets go throught how this sample and its state machine is designed and
how those two interacts with each other.
how those two interacts with each other. Below three config sections
are used withing a _EnumStateMachineConfigurerAdapter_.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetA]
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]
----
What we did in above configuration:
@@ -160,9 +183,14 @@ needed to automatically track elapsed time within a playing track and
to have facility to make a decision when to switch to next track.
** With event _PLAY_ if source state is _IDLE_ and target state is
_BUSY_ we defined action _playAction_ and guard _playGuard_.
** Lastly with event _LOAD_ and state _OPEN_ we defined internal
** With event _LOAD_ and state _OPEN_ we defined internal
transition with action _loadAction_ which will insert cd disc into
extended state variables.
** _PLAYING_ state defined three internal transitions where one is
triggered by a timer executing a _playingAction_ which updates
extended state variables. Other two transitions are with _trackAction_
with different events, _BACK_ and _FORWARD_ respectively which handles
when user wants to go back or forward in tracks.
This machine only have six states which are introduced as an enum.
[source,java,indent=0]
@@ -227,5 +255,19 @@ disc has been loaded.
include::samples/demo/cdplayer/Application.java[tags=snippetJ]
----
Now lets see how this cd player works and we can go a little deeper in
its state machine logic.
_PlayingAction_ is updating extended state variable _ELAPSEDTIME_ which
cd player itself can read and update lcd status. Action also handles
track shift if user is going back or forward in tracks.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetK]
----
_TrackAction_ handles track shift action if user is going back or forward
in tracks. If it is a last track of a cd, playing is stopped and _STOP_
event sent to a state machine.
[source,java,indent=0]
----
include::samples/demo/cdplayer/Application.java[tags=snippetL]
----

View File

@@ -14,7 +14,6 @@ that Spring Statemachine provides to any Spring based application.
[[sm-config]]
== Statemachine Configuration
One of the common tasks when using a Statemachine is to design its
runtime configuration. This chapter will focus on how Spring
Statemachine is configured and how it leverages Spring's lightweight
@@ -22,10 +21,10 @@ IoC containers to simplify the application internals to make it more
manageable.
=== Configuring States
We'll get into more complex configuration examples a bit later but
lets first start with a something simple. For most simple state
machine you
machine you just use `EnumStateMachineConfigurerAdapter` and define
possible states, choose initial and optional end state.
[source,java,indent=0]
----
@@ -33,6 +32,9 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetA]
----
=== Configuring Hierarchical States
Hierarchical states can be defined by using multiple `withStates()`
calls where `parent()` can be used to indicate that these
particular states are sub-states of some other state.
[source,java,indent=0]
----
@@ -40,6 +42,9 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetB]
----
=== Configuring Transitions
We support three different types of transitions, `external`,
`internal` and `local`. Transitions are either triggered by a signal
which is an event sent into a state machine or a timer.
[source,java,indent=0]
----
@@ -47,13 +52,26 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetC]
----
=== Configuring Guards
Guards are used to protect state transitions. Interface _Guard_ is
used to do an evaluation where method has access to _StateContext_.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetD]
----
In above two different types of guard configuration is used. Firstly a
simply _Guard_ is created as a bean and attached to transition between
states `S1` and `S2`.
Secondly a simple spel expression can be used as a guard where
expression must return a `Boolean` value. Behind a scenes this spel
based guard is a _SpelExpressionGuard_. This was attached to
transition between states `S2` and `S3`. Both guard in above sample
always evaluate to true.
=== Configuring Actions
Actions can be defined with various steps within a state transitions.
[source,java,indent=0]
----
@@ -127,19 +145,44 @@ include::samples/DocsConfigurationSampleTests.java[tags=snippetG]
----
=== State Machine Listener
For using _StateMachineListener_ you can either extend it and
Using _StateMachineListener_ you can either extend it and
implement all callback methods or use _StateMachineListenerAdapter_
class which contains stub method implementations and choose which ones
to override.
=== Limitations and Problems
TBD ctx events may create too much traffic, etc.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetH]
----
In above example we simply created our own listener class
_StateMachineEventListener_ which extends
_StateMachineListenerAdapter_.
Once you have your own listener defined, it can be registered into a
state machine via its interface as shown below. It's just a matter of
flavour if it's hooked up within a spring configuration or done
manually at any time of application life-cycle.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetM]
----
=== Limitations and Problems
Spring application context is not a fastest event bus out there so it
is advised to give some thought what is a rate of events state machine
is sending. For better performance it may be better to use
_StateMachineListener_ interface. For this specific reason it is
possible to use `contextEvents` flag with _@EnableStateMachine_ and
_@EnableStateMachineFactory_ to disable Spring application context
events as shown above.
[source,java,indent=0]
----
include::samples/DocsConfigurationSampleTests.java[tags=snippetN]
----
[[sm-context]]
== Context Integration
It is a little limited to do interaction with a state machine by

View File

@@ -0,0 +1,21 @@
+---------------------------------------------------------+
| |
| LOCAL EXTERNAL |
| +-------------------+ +-------------------+ |
| | +----------+ | | +----------+ | |
| | | | | +-------->| | | |
| |----->| | | | | | | | |
| | | | | +--| | | | |
| | +----------+ | | +----------+ | |
| +-------------------+ +-------------------+ |
| |
| |
| +-------------------+ +-------------------+ |
| | +----------+ | | +----------+ | |
| | | | | +---------| | | |
| |<-----| | | | | | | | |
| | | | | +->| | | | |
| | +----------+ | | +----------+ | |
| +-------------------+ +-------------------+ |
| |
+---------------------------------------------------------+

View File

@@ -79,10 +79,10 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
states
.withStates()
.initial(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class))
.state(States.S1)
.and()
.withStates()
.parent(States.S1)
.initial(States.S2)
.state(States.S2);
}
@@ -100,22 +100,23 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
states
.withStates()
.initial(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class))
.and()
.withStates()
.initial(States.S2)
.state(States.S2);
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions
.withExternal()
.source(States.S1).target(States.S2)
.event(Events.E1)
.and()
.withInternal()
.source(States.S2)
.event(Events.E2)
.and()
.withLocal();
.withLocal()
.source(States.S2).target(States.S3)
.event(Events.E3);
}
}
@@ -130,10 +131,15 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions
.withExternal()
.source(States.S1)
.target(States.S2)
.source(States.S1).target(States.S2)
.event(Events.E1)
.guard(guard());
.guard(guard())
.and()
.withExternal()
.source(States.S2).target(States.S3)
.event(Events.E2)
.guardExpression("true");
}
@Bean
@@ -280,4 +286,32 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
}
// end::snippetL[]
// tag::snippetM[]
static class Config7 {
@Autowired
StateMachine<States, Events> stateMachine;
@Bean
public StateMachineEventListener stateMachineEventListener() {
StateMachineEventListener listener = new StateMachineEventListener();
stateMachine.addStateListener(listener);
return listener;
}
}
// end::snippetM[]
// tag::snippetN[]
@Configuration
@EnableStateMachine(contextEvents = false)
public static class Config8 extends EnumStateMachineConfigurerAdapter<States, Events> {
}
@Configuration
@EnableStateMachineFactory(contextEvents = false)
public static class Config9 extends EnumStateMachineConfigurerAdapter<States, Events> {
}
// end::snippetN[]
}

View File

@@ -23,12 +23,12 @@ import org.springframework.statemachine.guard.Guard;
@Configuration
public class Application {
//tag::snippetA[]
@Configuration
@EnableStateMachine
static class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
//tag::snippetAA[]
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
@@ -53,7 +53,9 @@ public class Application {
.state(States.PAUSED);
}
//end::snippetAA[]
//tag::snippetAB[]
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
@@ -97,7 +99,9 @@ public class Application {
.withInternal()
.source(States.OPEN).event(Events.LOAD).action(loadAction());
}
//end::snippetAB[]
//tag::snippetAC[]
@Bean
public ClosedEntryAction closedEntryAction() {
return new ClosedEntryAction();
@@ -127,9 +131,9 @@ public class Application {
public PlayGuard playGuard() {
return new PlayGuard();
}
//end::snippetAC[]
}
//end::snippetA[]
//tag::snippetB[]

View File

@@ -14,12 +14,12 @@ import org.springframework.statemachine.guard.Guard;
@Configuration
public class Application {
//tag::snippetA[]
@Configuration
@EnableStateMachine
static class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
//tag::snippetAA[]
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
@@ -53,7 +53,9 @@ public class Application {
.initial(States.S211)
.state(States.S211);
}
//end::snippetAA[]
//tag::snippetAB[]
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
@@ -112,7 +114,9 @@ public class Application {
.source(States.S11).target(States.S12).event(Events.I);
}
//end::snippetAB[]
//tag::snippetAC[]
@Bean
public FooGuard foo0Guard() {
return new FooGuard(0);
@@ -127,9 +131,9 @@ public class Application {
public FooAction fooAction() {
return new FooAction();
}
//end::snippetAC[]
}
//end::snippetA[]
//tag::snippetB[]
public static enum States {