Máquina de estados con Apache SCXML

2
6110

Finite State Machine con Apache SCXML

0. Índice de
contenidos.

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 17″ (2,93 Ghz Intel Core 2 Duo, 8 GB DDR3)
  • Sistema Operativo: Mac OS X Mavericks 10.9
  • Apache SCXML 0.9
  • Eclipse Kepler
  • scxmlgui

2. Introducción.

En este tutorial vamos a ver cómo crear una máquina de estados sencillita que pase entre tres estados. Para hacer el comportamiento básico vamos a ayudarnos de la herramienta scxmlgui.

Cuando tengamos el scxml con la funcionalidad básica, vamos a añadirle otras funciones en código java gracias a apache SCXML.

Bien, vamos a explicar cómo es el funcionamiento de una máquina de estados, para ello primero vamos a ver los elementos básicos que tienen.

  • Estado: Es la parte más importante, y como su propio nombre indica, es un estado concreto en el flujo de la máquina de estados. Cada estado tiene asociados transiciones y funciones OnEntry y OnExit que se ejecutarán al entrar y al salir del estado, respectivamente. Del estado se sale mediante transiciones que se activan mediante eventos y tienen el estado de destino como Target.
  • Transición: Tienen de propiedades una condición que tiene que cumplir para realizarse, un evento que será la que la dispare y un target que es el estado destino. También se pueden asociar acciones a realizar cuando ocurran.
  • OnEntry y OnExit: Contienen acciones a realizar cuando una transición llegue al estado o salga de él, respectivamente.
  • Accion: Son elementos de código que se incorporan a las transiciones o a OnEntry/OnExit para que sean ejecutadas.

Con esto ya tenemos la base para crear la máquina de estados, así que vamos con ello.

La máquina de estados que voy a realizar tendrá tres estados (A, B y C), con dos transiciones, una entre A y B (transicion_AB) y otra entre B y C (transicion_BC). El estado B tendrá una acción en OnEntry y dos acciones en OnExit. El estado C tendrá una acción en OnEntry. El evento que dispara la transición AB será llamado «Transicion_AB», y el evento de la transición BC «Transicion_BC». Y por último, la transición BC tendrá dos acciones.

En el esquema siguiente queda más claro la explicación

3. Creando el archivo base scxml.

Nos vamos a ayudar de la herramienta scxml, que la podéis encontrar aquí, y su funcionamiento es muy sencillo (ya que sólo vamos a hacer la base y el resto de funcionalidad lo agregaremos desde código java).

Cuando la tengamos abierta, creamos un nuevo archivo (File –> New SCXML). Ahora con el botón derecho sobre la plantilla agregamos un nodo (nodo es el equivalente a estado) con Add node, damos doble click para ponerle de Nombre «A», como aparece en la siguiente imagen.

Ahora, con el botón presionado sobre el nodo, arrastramos a otra zona y soltamos, que nos creará un nuevo nodo y una transición que los une, como hicimos antes, renombramos el nuevo nodo y le ponemos «B».

Ahora damos con el botón derecho sobre la transición y «edit transition». Ahora en la pestaña evento ponemos «transición_AB», y en condición ponemos símplemente true.

Ahora ya sólo nos queda hacer lo mismo para crear un nuevo nodo a partir de B, realizamos los pasos anteriores salvo que el nuevo nodo se llamará «C» y la transición que los une «Transición_BC». El resultado tiene que ser el siguiente:

Guardamos el archivo como «fsm.scxml», y su contenido tiene que ser como el que sigue (salvo los comentarios de dónde está colocado cada nodo):

4. Creando proyecto java.

Bien, ahora ya tenemos el archivo base, vamos a crear un proyecto en eclipse con maven, para ello vamos a File –> new –> Maven Project. (si no ve Maven Project, pulse en others y lo encontrará si tiene instalado el plugin en eclipse, sino tendrá que instalárselo).

Las siguientes pantallas se describen solas, podéis poner los mismos datos de ejemplo que he puesto yo.

Bien, ahora vamos a configurar el archivo pom.xml con las dependencias que vamos a necesitar, tiene que quedar así:

Resumido, hemos incorporado las dependencias para apache SCXML, en la misma versión que viene en la página oficial ( commons-scxml 0.9, commons-beanutils 1.8.2, commons-digester 1.8.1, commons-el 1.0, commons-jexl 1.1, commons-logging 1.1.1 y xalan 2.6.0)

Además he incluido las dependencias para los test (JUnit 4.11 y Mockito 1.9.5).

Una vez hecho esto, vamos a crearnos las clases que vamos a usar. Primero implementaremos nuestra máquina de estados personalizada con los métodos que nos van a hacer falta, heredando de AbstractStateMachine, incluida en el paquete org.apache.commons.scxml.env. Yo he llamado a la clase FSM.java, y la he dejado en la ruta com/autentia/tutorials/fsm, que será donde estén las clases que vamos a implementar.

Dejo el código comentado a continuación:

Bien, ya tenemos la clase principal, ahora vamos a crear una clase que herede de Action, ya que es una clase abstracta, para poder crear nuestras acciones personalizadas, yo lo único que he implementado es un método que imprima en el log un string que se le pasa en el constructor, en este caso será un string informativo del método al cuál se le va a asociar la acción.

La clase CustomAction.java ubicada en la misma ruta que FSM.java es la siguiente:

Creo que hace falta explicar poco de la clase, es muy simple.

Ahora vamos a copiar el archivo fsm.scxml que creamos con el editor gráfico, en la carpeta resource, tanto dentro de main como de test, ya que ahora crearemos los test. En TDD lo primero que hay que crear son los test, pero para que quedaran más claros los he dejado para el final de la explicación.

En la misma ruta que teníamos las anteriores clases, pero partiendo ahora de test, vamos a crear la clase FSMTest.java, con el siguiente contenido:

Al inicio de los métodos hay un pequeño resumen de lo que comprueba cada test. Creo que no tiene mucha complejidad y es bastante legible el código por sí sólo, pero voy a explicar un par de cosas por si no quedan claras.

El método spy, incluido con mockito nos va a permitir saber el orden en el que se llaman a los métodos de esos objetos, y los crea llamando al constructor, cosa que con mock no se hace y no podemos seguir el orden de algunos objetos.

Otra cosa a tener en cuenta es la variable booleana eventdata, la cual se le pasa al lanzar el evento, y si os fijáis, llamamos al método de fsm addConditionToTransitionByStateAndEvent, el cual pasamos el estado b, la transicion que queremos configurar y el String que queremos que tenga la condición de esa transición (STATE_B, TRANSITION_BC, «_eventdata»). _eventdata es el nombre de la variable que da Apache SCXML a las variables que vienen desde la transición.

Como vemos líneas más abajo, introducimos la condición eventdata a la transición TransicionCB (transicionBC = new TriggerEvent(TRANSITION_BC,TriggerEvent.SIGNAL_EVENT, eventdata); y con lo que conseguimos que _eventdata que recoge apache SCXML tenga el valor de la variable booleana eventdata(true) con lo que conseguimos que la transición que pasamos sea lanzada dependiendo de la variable eventdata.

Realmente no necesitamos esto, sólo lo he incluido como información para hacer nuestro código más flexible y configurable.

Ahora ejecutamos la clase como JUnit test:

Vemos que pasan los test:

Y la salida tendría que ser algo parecido a esto:

5. Conclusiones.

Hemos visto cómo crear una máquina de estados sencilla que nos ayudará a que nuestras tareas tengan un flujo definido claro y configurable.

El código en github del proyecto.

Para cualquier duda o aclaración, en los comentarios.

Un saludo.

2 Comentarios

  1. Hola Daniel, excelente articulo realmente, no hay muchos ejemplos sobre Apache Commons SCXML 0.9 (ni siquiera en la pagina oficial). Quisiera hacerte una consulta, sabes como se registran los custom actions a traves de la definicion en XML? Segun lo que habia visto en la pagina de Apache deberia ser algo asi:

    …..

    Incluyendo por supuesto en la definicion inicial el namespace xmlns:my=»http://my.custom-actions.domain/CUSTOM» (que en mi caso no apunta a nada), pero la realidad es que si no agrego en codigo Java los custom action asociados a onEntry y onExit no los ejecuta, es posible hacer esto a traves del xml o es una caracteristica que no existe en esta version?
    Saludos

    • Parece que el codigo que envie no salio, lo copio nuevamente con caracteres escapados:

      <state id=»miEstado»>
      <onexit>
      <my:customAction />
      </onexit>

      </state>

      Saludos

Dejar respuesta

Please enter your comment!
Please enter your name here