10

Please note: I am a Java developer with no working knowledge of Scala (sadly). I would ask that any code examples provided in the answer would be using Akka's Java API.

I am trying to use the Akka FSM API to model the following super-simple state machine. In reality, my machine is much more complicated, but the answer to this question will allow me to extrapolate to my actual FSM.

enter image description here

And so I have 2 states: Off and On. You can go fro Off -> On by powering the machine on by calling SomeObject#powerOn(<someArguments>). You can go from On -> Off by powering the machine off by calling SomeObject#powerOff(<someArguments>).

I'm wondering what actors and supporting classes I'll need in order to implement this FSM. I believe the actor representing the FSM has to extend AbstractFSM. But what classes represent the 2 states? What code exposes and implements the powerOn(...) and powerOff(...) state transitions? A working Java example, or even just Java pseudo-code, would go a long way for me here.

smeeb
  • 27,777
  • 57
  • 250
  • 447

3 Answers3

23

I think we can do a bit better than copypasta from the FSM docs (http://doc.akka.io/docs/akka/snapshot/java/lambda-fsm.html). First, let's explore your use case a bit.

You have two triggers (or events, or signals) -- powerOn and powerOff. You would like send these signals to an Actor and have it change state, of which the two meaningful states are On and Off.

Now, strictly speaking an FSM needs one additional component: an action you wish to take on transition.

FSM:
State (S) x Event (E) -> Action (A), State (S')

Read: "When in state S, if signal E is received, produce action A and advance to state S'"

You don't NEED an action, but an Actor cannot be directly inspected, nor directly modified. All mutation and acknowledgement occurs through asynchronous message passing.

In your example, which provides no action to perform on transition, you basically have a state machine that's a no-op. Actions occur, state transitions without side effect and that state is invisible, so a working machine is identical to a broken one. And since this all occurs asynchronously, you don't even know when the broken thing has finished.

So allow me to expand your contract a little bit, and include the following actions in your FSM definitions:

 When in Off, if powerOn is received, advance state to On and respond to the caller with the new state
 When in On, if powerOff is received, advance state to Off and respond to the caller with the new state

Now we might be able to build an FSM that is actually testable.

Let's define a pair of classes for your two signals. (the AbstractFSM DSL expects to match on class):

public static class PowerOn {}
public static class PowerOff {}

Let's define a pair of enums for your two states:

 enum LightswitchState { on, off }

Let's define an AbstractFSM Actor (http://doc.akka.io/japi/akka/2.3.8/akka/actor/AbstractFSM.html). Extending AbstractFSM allows us to define an actor using a chain of FSM definitions similar to those above rather than defining message behavior directly in an onReceive() method. It provides a nice little DSL for these definitions, and (somewhat bizarrely) expects that the definitions be set up in a static initializer.

A quick detour, though: AbstractFSM has two generics defined which are used to provide compile time type checking.

S is the base of State types we wish to use, and D is the base of Data types. If you're building an FSM that will hold and modify data (maybe a power meter for your light switch?), you would build a separate class to hold this data rather than trying to add new members to your subclass of AbstractFSM. Since we have no data, let's define a dummy class just so you can see how it gets passed around:

public static class NoDataItsJustALightswitch {}

And so, with this out of the way, we can build our actor class.

public class Lightswitch extends AbstractFSM<LightswitchState, NoDataItsJustALightswitch> {
    {  //static initializer
        startWith(off, new NoDataItsJustALightswitch()); //okay, we're saying that when a new Lightswitch is born, it'll be in the off state and have a new NoDataItsJustALightswitch() object as data

        //our first FSM definition
        when(off,                                //when in off,
            matchEvent(PowerOn.class,            //if we receive a PowerOn message,
                NoDataItsJustALightswitch.class, //and have data of this type,
                (powerOn, noData) ->             //we'll handle it using this function:
                    goTo(on)                     //go to the on state,
                        .replying(on);           //and reply to the sender that we went to the on state
            )
        );

        //our second FSM definition
        when(on, 
            matchEvent(PowerOff.class, 
                NoDataItsJustALightswitch.class, 
                (powerOn, noData) -> {
                    goTo(off)
                        .replying(off);
                    //here you could use multiline functions,
                    //and use the contents of the event (powerOn) or data (noData) to make decisions, alter content of the state, etc.
                }
            )
        );

        initialize(); //boilerplate
    }
}

I'm sure you're wondering: how do I use this?! So let's make you a test harness using straight JUnit and the Akka Testkit for java:

public class LightswitchTest { 
    @Test public void testLightswitch() {
        ActorSystem system = ActorSystem.create("lightswitchtest");//should make this static if you're going to test a lot of things, actor systems are a bit expensive
        new JavaTestKit(system) {{ //there's that static initializer again
            ActorRef lightswitch = system.actorOf(Props.create(Lightswitch.class)); //here is our lightswitch. It's an actor ref, a reference to an actor that will be created on 
                                                                                    //our behalf of type Lightswitch. We can't, as mentioned earlier, actually touch the instance 
                                                                                    //of Lightswitch, but we can send messages to it via this reference.

            lightswitch.tell(    //using the reference to our actor, tell it
                new PowerOn(),   //to "Power On," using our message type
                getRef());       //and giving it an actor to call back (in this case, the JavaTestKit itself)

            //because it is asynchronous, the tell will return immediately. Somewhere off in the distance, on another thread, our lightbulb is receiving its message

            expectMsgEquals(LightswitchState.on);   //we block until the lightbulb sends us back a message with its current state ("on.")
                                                     //If our actor is broken, this call will timeout and fail.

            lightswitch.tell(new PowerOff(), getRef());

            expectMsgEquals(LightswitchState.off);   

            system.stop(lightswitch); //switch works, kill the instance, leave the system up for further use
        }};
    }
}

And there you are: an FSM lightswitch. Honestly though, an example this trivial doesn't really show the power of FSMs, as a data-free example can be performed as a set of "become/unbecome" behaviors in like half as many LoC with no generics or lambdas. Much more readable IMO.

PS consider learning Scala, if only to be able to read other peoples' code! The first half of the book Atomic Scala is available free online.

PPS if all you really want is a composable state machine, I maintain Pulleys, a state machine engine based on statecharts in pure java. It's getting on in years (lot of XML and old patterns, no DI integration) but if you really want to decouple the implementation of a state machine from inputs and outputs there may be some inspiration there.

Matthew Mark Miller
  • 3,128
  • 1
  • 16
  • 11
  • Thank you so much, I was in th exact same state trying to find a java example and this is exactly what I was looking for – user_mda Feb 28 '18 at 20:12
  • I couldnt find the github project that you mentioned though, can you post a link? – user_mda Feb 28 '18 at 20:17
  • http://github.com/datamill-io/pulleys -- it's now even 3 years older and less desirable. I am designing a new version, likely written in Scala 2.12, focused on implementing Activities (asyncronously triggered actions) and Submachines. – Matthew Mark Miller Feb 28 '18 at 20:44
  • for me the system.actorOf(Props.create(Lightswitch.class) gives a unknown actor creator error. Does the actor also need to extend the AbstractActor class? – user_mda Mar 20 '18 at 18:41
  • can you pass data along with the state ? for eg, if i want to chnage the data based on the state of the actor? – user_mda May 15 '18 at 01:26
2

I know about Actors in Scala.
This Java Start Code may help you, to go ahead:

Yes, extend your SimpleFSM from AbstractFSM.
The State is an enum in the AbstractFSM.
Your <someArguments> can be the Data Part in your AbstractFSM
Your powerOn and powerOff are Actor Messages/Events. And the State switching is in the transitions Part

// states
enum State {
  Off, On
}

enum Uninitialized implements Data {
  Uninitialized
}

public class SimpleFSM extends AbstractFSM<State, Data> {
    {
        // fsm body
        startWith(Off, Uninitialized);

        // transitions
        when(Off,
            matchEvent(... .class ...,
            (... Variable Names ...) ->
              goTo(On).using(...) ); // powerOn(<someArguments>)

        when(On,
            matchEvent(... .class ...,
            (... Variable Names ...) ->
              goTo(Off).using(...) ); // powerOff(<someArguments>)

        initialize();

    }
}

Real working Project see

Scala and Java 8 with Lambda Template for a Akka AbstractFSM

Community
  • 1
  • 1
Robert Halter
  • 362
  • 1
  • 9
  • Thanks @Robert Halter (+1) - this is a great start but unfortunately I am still not *completely* understanding this. For the bounty, can you please answer a few followups? (1) Can you replace all of your elipses (`...`) with actual code/API calls? That would really help tie everything together for me! (2) Can you please explain (even just by adding javadocs/comments above) the purpose of `Data` and `Uninitialized`? They seem useless and unnecessary! – smeeb Jun 23 '15 at 01:28
  • Finally (3) the `SimpleFSM` above is confusing, because all of its code is in an unllabeled code block (`{ ... }`). How would I call this code from the outside? Can you refactor this unlabeled code block into "normal" Java methods (so that I can understand how to call them from *outside* the class)? Thanks again so much! – smeeb Jun 23 '15 at 01:28
  • 1
    @smeeb what is your intention behind in the calls of powerOn and powerOff? – Robert Halter Jun 23 '15 at 08:31
  • Halter I think my use of `` was to simply supply the FSM with data/information from the outside world to help it make its decisions. But in this simple "*Power On, Power Off*" FSM is the need for `` is probably misleading and totally unnecessary! I guess I was thinking ahead to more complicated FSMs where, instead of simple booleans like "*Power On*" I might need to supply the machine with some extra information before it can make its decision to transition state or not. – smeeb Jun 23 '15 at 09:11
  • At the very least, I provided `` in my original question out of ignorance because I've never actually used an FSM before, Akka or otherwise. But think of it as input to the FSM to help it decide to change state or not. Thanks again! – smeeb Jun 23 '15 at 09:13
  • 1
    From Outside you can call actorRef.tell("MSG" , null); where MSG is the Messageobject. This Messageobject is checked in matchEvent with the class and Variable Names – Robert Halter Jun 24 '15 at 07:12
  • Over the AkkaSystem you can ask about the Actors in the System i.E. the FSM is a Actor. There is a Scheduler to repeadly send Message for i.E. a Flip Flop FSM Toggle. – Robert Halter Jun 24 '15 at 11:41
0

Well this is a really old question but if you get as a hit from Google, but if you are still interested implementing FSM with Akka, I suggest to look this part of the documentation.

If you want to see how a practical model driven state machine implementation, you can check my blog1, blog2.

posthumecaver
  • 1,584
  • 4
  • 16
  • 29