Introducción a Akka para Java con ejemplos

Tutorial en el que se introduce el modelo de actores, un ejemplo del mismo y su aplicación en Akka para Java.

Índice de contenidos

1. Introducción

Es probable que nuestras aplicaciones deban ejecutar tareas en segundo plano, por ejemplo porque haya que realizar una labor pesada y no queramos que la aplicación quede bloqueada mientras se lleva a cabo. Para conseguirlo, una posible solución es crear hilos en los cuales ejecutar estas tareas; otra es emplear el modelo de actores que nos brinda Akka.

En este tutorial vamos a ver, a través de un ejemplo, algunos conceptos básicos de Akka y cómo se usa si estamos programando en Java.

2. Entorno

Este tutorial se ha desarrollado en el siguiente entorno:

  • Hardware: portátil MacBook Pro (15′, mediados 2010).
  • Sistema operativo: Mac OS X Yosemite 10.10.5.
  • IDE: IntelliJ IDEA 14 Ultimate.
  • Versión de Java: 1.8.0_60

3. Modelo de actores

El modelo de actores tiene como objetivo facilitar el diseño de sistemas basados en concurrencia. Se basa en pequeñas entidades llamadas actores que se envían mensajes y se comportan de una manera determinada ante la recepción de cada uno.

Los actores pueden crear otros actores y tienen la capacidad de supervisar a los que crean. La creación de actores sirve para delegar tareas, lo cual es útil para aliviar reponsabilidades y diseñar un buen modelo de actores: hay que intentar que cada uno tenga el menor número de competencias posible, de tal manera que quede claro qué mensajes debe procesar cada actor, cómo deben reaccionar a los mismos y cómo deben manejar errores. Si un actor no sabe cómo manipular un error, entonces deberá pasarle el problema a su supervisor; de esta manera, se consigue que los errores sean manejados por el actor adecuado. Un par de situaciones en las que un actor debe manejar un error ocurrido en otro actor podrían ser las siguientes:

  • Si un actor supervisa el trabajo de un actor hijo al que delega tareas, entonces sabrá por qué ha fallado el hijo y tendrá que ser el responsable de responder ante el error.
  • Si un actor trabaja con información importante que no puede perder, entonces debe delegar las tareas propensas a fallos a sus hijos y manejar los errores ocurridos en ellos.

4. Ejemplo de actores

Volvamos al pasado unos cuantos siglos e imaginemos un caso simplificado de obtención de una espada, en el cual intervienen los siguientes sujetos, algunos de ellos actores:

  • Espadachín (actor). Sin una espada no es nadie.
  • Herrero (actor). A partir de ciertos minerales, es capaz de forjar una espada.
  • Minero (actor). Puede conseguir los minerales que el herrero necesita para forjar una espada.
  • Mozos. Envían información y transportan objetos.

El espadachín quiere una espada nueva. Sabe que el herrero puede forjarle una pero que éste solamente las fabrica bajo demanda y que tarda varios días desde que le piden una. Manda a un mozo a hacer el pedido y le pide que se encargue de traerle la espada cuando esté lista; de esta manera, el espadachín puede dedicarse a sus labores en lugar de quedarse de brazos cruzados esperando la espada. Esto es programación asíncrona.

Imaginemos que el herrero dispone de los medios y las capacidades para producir una espada a partir de minerales. No sabe si en las minas los tendrán ya listos, así que manda a un mozo que se encargue de obtenerlos y mientras se pone a hacer sus cosas. De nuevo, programación asíncrona.

Si modelamos esta escena con actores tendríamos que el espadachín, el herrero y el minero son actores, mientras que los mozos son el sistema por el que se envían mensajes:

intro_akka_java_actores_ida

Cuando el minero obtenga el material necesario, llamará a un mozo para que lo transporte al herrero. Cuando éste forje la espada, se la dará a un mozo para que se la lleve al espadachín.

intro_akka_java_actores_ida_vuelta

5. Buzones de actores

Cada actor tiene un buzón, que es el lugar donde llegan todos los mensajes que recibe. Los actores están ociosos hasta que les llega un mensaje a su buzón; entonces lo procesan y, cuando terminan, siguen leyendo mensajes hasta que el buzón queda vacío. De nuevo, quedan ociosos hasta que llegue un nuevo mensaje.

Hay diferentes implementaciones de buzones, pero por defecto son de tipo FIFO: el primer mensaje que llega es el primero que el actor lee. Otro tipo de buzón útil es el que tiene en cuenta la prioridad de los mensajes.

Si un actor envía varios mensajes a otro, Akka asegura que llegarán al buzón en orden. Si dos o más actores envían mensajes a un mismo actor, los mensajes de cada uno podrán estar intercalados con los del otro, pero siempre se respetará el orden dentro de cada grupo de mensajes de un mismo actor.

6. Creación del proyecto

Vamos a crear un pequeño proyecto para recrear el ejemplo de la sección anterior. En nuestro caso, será un proyecto Java en IntelliJ IDEA. Si trabajas con Spring, entonces echa un vistazo a este tutorial de Juan Antonio.

intro_akka_java_crear_proyecto_1

intro_akka_java_crear_proyecto_2

intro_akka_java_crear_proyecto_3

La versión de Akka más reciente en el momento de escribir este tutorial es la 2.4.0, publicada el 16 de septiembre de 2015. Esta versión requiere Java 8, mientras que la inmediata anterior, la versión 2.3.14, Java 6. En nuestro caso tenemos Java 8, así que vamos a descargar Akka 2.4.0 (Scala 2.11 and Java 8+).

Akka es muy modular, por lo que dentro del directorio “akka-2.4.0/lib/” del ZIP que hemos descargado encontraremos diferentes archivos JAR. Nos interesan los siguientes:

  • akka/akka-actor_2.11-2.4.0.jar
  • akka/config-1.3.0.jar
  • scala-library-2.11.7.jar

En el directorio de nuestro proyecto creamos una carpeta “lib/” junto a “src/” y metemos ahí los tres archivos. Aparecerán en IntelliJ IDEA:

intro_akka_java_bibliotecas

Seleccionamos los tres, clic derecho y “Add as Library…”.

intro_akka_java_anadir_bibliotecas

7. Actores en Akka

Los actores de Akka en Java deben extender la clase Untyped Actor e implementar el método onReceive. Es en este método donde los actores reciben los mensajes y los manejan según corresponda. Los mensajes pueden ser cualquier tipo de objeto, pero en este tutorial nos limitaremos a definir en cada actor cuáles puede recibir cada uno.

Por otra parte, es importante tener siempre en mente que los mensajes que se envían entre actores deben ser inmutables. La documentación de Akka indica que esto es por convención.

Vamos a construir tres casos para ir viendo poco a poco cómo funciona en Java el sistema de actores de Akka. El código de ejemplo está en este repositorio de GitHub.

Caso 1: manejar mensajes esperados

Primero creamos una clase Main para ejecutar el proyecto.

Main.java

El método main de Akka recibe como argumento el nombre de la clase del actor principal de la aplicación, que será el que comience ejecutándose. En nuestro caso será Espadachin.

Espadachin.java

Los actores de Akka tienen un método preStart que podemos sobreescribir para realizar acciones previas a la recepción del primer mensaje que reciban. En el caso del espadachín, queremos que diga al herrero que le cree una espada.

El actor Espadachin lo habíamos creado de una manera un tanto especial: como actor principal a través del método main de akka.Main. Sin embargo, la creación de actores desde otros actores se realiza a través del método actorOf, al que se le pasan como parámetros una instancia de la clase de configuración Props y el nombre del actor a crear, que debe ser único. El método actorOf devuelve una instancia de ActorRef, es decir, una referencia al actor que ha sido creado. A partir de ella, a través de su método tell, se le pueden mandar mensajes. A este método se le pasa la referencia del propio actor que envía el mensaje, la cual se puede conseguir llamando a getSelf. De esta manera, el receptor del mensaje podrá saber quién se lo ha enviado llamando a getSender.

Herrero.java

Al igual que Espadachin creaba a Herrero en el método preStart, Herrero hace lo propio con Minero.

El herrero recibe órdenes de creación de espadas. Si el mensaje que recibe en oReceive es una de estas órdenes, entonces pide al minero que obtenga los materiales que necesita.

Minero.java

El minero recibe la orden de obtención de materiales y se pone a ello. El método sleep simula el tiempo que le lleva la tarea.

Si ejecutamos la aplicación, gracias a los logs que hemos puesto, obtendremos por consola lo siguiente (con más detalles, como el Path, que se omiten en este tutorial):

Consola tras ejecutar el caso 1

Caso 2: manejar mensajes no esperados

El caso 1 no es un modelo del ejemplo que hemos descrito, ya que al espadachín le falta todavía su arma; sin embargo, nos ha servido para entender las funciones básicas de Akka. Ahora vamos a completar el código; para ello necesitamos que el minero envíe al herrero los materiales y que éste forje la espada y se la envíe al espadachín.

Espadachin.java

En este caso el espadachín saluda al herrero (en preStart) y está pendiente de recibir como mensaje la espada nueva (en onReceive). Además, cuando reciba la espada informará de que se para y, como es el actor principal, la aplicación parará.

Si un actor recibe un mensaje que no sabe cómo manejar, se recomienda llamar al método unhandled, que por defecto puede publicar estos mensajes como mensajes de depuración. Para conseguirlo es necesario configurar Akka (ver http://doc.akka.io/docs/akka/2.4.0/general/configuration.html) con:

También es posible sobreescribir el método unhandled para reaccionar como queramos ante estos mensajes inesperados. En Herrero.java lo hacemos:

Herrero.java

Vemos cómo ahora el herrero recibe los materiales que le manda el minero y se pone a crear la espada para mandársela al espadachín.

Como estamos programando de manera asíncrona y no tenemos al espadachín esperando a que el herrero termine su obra, es necesario que el herrero sepa quién es el que le ha pedido la espada para devolvérsela como mensaje. A través del método getSender es posible obtener la referencia del actor que ha enviado el mensaje. Como el herrero debe, a su vez, pedir al minero minerales, guardará la referencia del espadachín para cuando el minero le entregue el material, y es que al herrero no le vale llamar a getSender cuando termine de crear la espada ya que, como la crea cuando el minero le envía el mensaje, obtendría la referencia del minero y no la del espadachín.

Minero.java

El minero, por su parte, no necesita guardar la referencia de quien le pide los materiales, ya que en cuanto recibe la petición del herrero se pone a ello y puede llamar a getSender para devolvérselos.

Consola tras ejecutar el caso 2

Al ejecutar el programa, vemos cómo hay una parada antes de [Herrero] ha recibido el mensaje: “MATERIALES” y que el herrero ya ha procesado el mensaje SALUDO. Esto nos confirma que después de recibir el mensaje CREAR_ESPADA y pedir material al minero, el herrero llevó a cabo otra tarea (procesar el mensaje SALUDO) y no se quedó esperando a que el minero le devolviese el material.

Por otra parte, comprobamos que en efecto la aplicación finaliza al hacerlo Espadachin.

Caso 3: varios actores del mismo tipo

Imagina que tenemos dos herreros y un minero, pero que el minero no lo crea un herrero, sino que es creado de manera externa y ambos herreros tienen acceso a la referencia de este actor. Si los herreros piden minerales al minero, el minero procesará el primer mensaje que le llegue, picará y devolverá los minerales al herrero que le ha enviado ese mensaje. Acto seguido, procesará el segundo mensaje, picará y devolverá minerales al otro herrero.

Imagina que ahora tenemos una situación análoga en la que dos espadachines piden cada uno una espada al mismo herrero. En principio el herrero debería devolver a cada uno un arma, pero esto no va a ser así. ¿Por qué no funciona correctamente? La diferencia entre el minero y el herrero estriba en que el primero puede hacer un getSender tras obtener el material para saber qué herrero se lo ha pedido, mientras que el herrero no puede llamar a getSender para conseguir la referencia del espadachín tras crear la espada, sino que necesita guardársela previamente. Dado este comportamiento, si dos espadachines pidiesen al mismo herrero una espada, el flujo sería:

  1. El herrero recibe encargo del espadachín 1, actualiza a “espadachín 1” la referencia que guarda y pide material al minero.
  2. El herrero recibe encargo del espadachín 2, actualiza a “espadachín 2” la referencia que guarda y pide material al minero.
  3. El herrero recibe del minero los materiales de la primera petición, crea una espada y se la envía al espadachín 2.
  4. El herrero recibe del minero los materiales de la segunda petición, crea otra espada y se la envía de nuevo al espadachín 2.

Como vemos, el espadachín 1 se queda sin arma. Para evitar esto se pueden plantear, por ejemplo, un par de soluciones:

  • El herrero envía al minero como mensaje un objeto que contenga tanto la petición de materiales como la referencia del espadachín. Cuando el minero le responda, mandará un mensaje que envuelva los materiales y esta referencia. Así, el herrero sabrá a qué espadachín mandarle la espada.
  • El herrero lleva un registro de los espadachines que le han hecho un encargo. Cada vez que produzca una espada, se la enviará al primer espadachín que le pidió una y borrará su referencia del registro.

El problema de la primera solución es que el minero está manejando información que le es irrelevante. Así pues, implementamos la segunda solución.

Main.java

En este caso creamos y guardamos en el main las referencias de todos los actores involucrados. Uno de ellos es especial: ActorSystem, a partir del cual se pueden crear el resto de actores.

Ahora el flujo de mensajes del programa comienza con la activación de ambos espadachines al enviarles el mensaje de que su espada se ha roto.

Espadachin.java
Herrero.java

espadachines es el registro que tiene el herrero para saber quiénes les han hecho un pedido.

Por su parte, el código de Minero.java es el mismo que en el caso 2, solamente hay que cambiar el paquete.

Consola tras ejecutar el caso 3

8. Conclusiones

Como hemos visto, crear actores con Akka y enviar mensajes es una tarea sencilla. La complejidad no debería estar en la implementación, sino en la definición de un buen modelo de actores en el que queden claras las responsabilidades de cada actor y la comunicación entre ellos.

Además de lo visto en este tutorial de introducción, Akka tiene muchos otros componentes y funcionalidades. La documentación oficial los explica y contiene ejemplos en forma de proyectos en Typesafe Activator.

9. Referencias

Java Documentation | Akka

Actor model | Wikipedia