Entre bastidores - AWS Flow Framework para Java

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Entre bastidores

Tarea

El tipo primitivo subyacente que AWS Flow Framework para Java utiliza para administrar la ejecución de código asíncrono es la clase Task. Un objeto tipo Task representa trabajo que hay que realizar de manera asíncrona. Cuando llama a un método asíncrono, el marco de trabajo crea una Task para ejecutar el código en ese método y lo pone en una lista para su ejecución más adelante. De manera parecida, al invocar una Activity, se crea una Task para dicha actividad. La llamada al método regresa después de esto, devolviendo normalmente una Promise<T> como resultado futuro de la llamada.

La clase Task es pública y puede usarse directamente. Por ejemplo, podemos volver a escribir el ejemplo de Hello World para que use una Task en lugar de un método asíncrono.

@Override public void startHelloWorld(){ final Promise<String> greeting = client.getName(); new Task(greeting) { @Override protected void doExecute() throws Throwable { client.printGreeting("Hello " + greeting.get() +"!"); } }; }

El marco de trabajo llama al método doExecute() cuando todas las Promises que se han pasado al constructor de las Task están listas. Para obtener más información acerca de la clase Task, consulte la documentación de AWS SDK for Java.

El marco de trabajo también incluye una clase llamada Functor que representa una Task que es también una Promise<T>. El objeto Functor está listo cuando se completa la Task. En el siguiente ejemplo, se crea un Functor para obtener el mensaje de saludo:

Promise<String> greeting = new Functor<String>() { @Override protected Promise<String> doExecute() throws Throwable { return client.getGreeting(); } }; client.printGreeting(greeting);

Orden de ejecución

Las tareas se vuelven elegibles para la ejecución solo cuando todos los parámetros escritos Promise<T>, que se han pasado a la actividad o al método asíncrono correspondientes, están listos. Una Task que está lista para la ejecución se mueve de manera lógica a una cola lista. En otras palabras, se programa para la ejecución. Para ejecutar la tarea, la clase de proceso de trabajo invoca el código que se escribió en el cuerpo del método asíncrono o programa una tarea de actividad en Amazon Simple Workflow Service (AWS) en el caso de un método de actividad.

A medida que las tareas ejecutan y producen resultados, hacen que otras tareas estén preparadas y la ejecución del programa continúa avanzando. La manera en que el marco de trabajo ejecuta tareas es importante para comprender el orden en el que se ejecuta su código asíncrono. El código que aparece secuencialmente en su programa podría no ejecutarse en ese orden.

Promise<String> name = getUserName(); printHelloName(name); printHelloWorld(); System.out.println("Hello, Amazon!"); @Asynchronous private Promise<String> getUserName(){ return Promise.asPromise("Bob"); } @Asynchronous private void printHelloName(Promise<String> name){ System.out.println("Hello, " + name.get() + "!"); } @Asynchronous private void printHelloWorld(){ System.out.println("Hello, World!"); }

El código en la lista de arriba imprimirá lo siguiente:

Hello, Amazon! Hello, World! Hello, Bob

Quizás no sea lo que esperaba pero puede explicarse fácilmente analizando cómo se ejecutaron las tareas para los métodos asíncronos:

  1. La llamada a getUserName crea una Task. La llamaremos Task1. Dado que getUserName no toma ningún parámetro, se pone inmediatamente a Task1 en la cola lista.

  2. A continuación, la llamada a printHelloName crea una Task que tiene que esperar el resultado de getUserName. La llamaremos Task2. Dado que el valor requisito aun no está listo, se pone Task2 en la lista de espera.

  3. A continuación se crea una tarea para printHelloWorld y se añade a la cola lista. La llamaremos Task3.

  4. A continuación, la instrucción println imprime "Hello, Amazon!" en la consola.

  5. En este punto, Task1 y Task3 están en la cola lista y Task2 está en la lista de espera.

  6. El proceso de trabajo ejecuta Task1 y su resultado hace que Task2 esté listo. Task2 se añade a la cola lista por detrás de Task3.

  7. Task3 y Task2 se ejecutan, a continuación, en ese orden.

La ejecución de actividades sigue el mismo patrón. Cuando se llama a un método en el cliente de actividad, este crea una Task que, cuando se ejecuta, programa una actividad en Amazon SWF.

El marco de trabajo confía en características como la generación de códigos y proxies dinámicos para inyectar la lógica para la conversión de llamadas de método en invocaciones de actividad y tareas asíncronas en su programa.

Ejecución del flujo de trabajo

La clase de proceso de trabajo también administra la ejecución de la implementación de flujo de trabajo. Cuando se llama a un método en el cliente de flujo de trabajo, llama a Amazon SWF para crear una instancia de flujo de trabajo. Las tareas de Amazon SWF no deben confundirse con las tareas del marco de trabajo. Una tarea en Amazon SWF es o bien una tarea de actividad o una tarea de decisión. La ejecución de las tareas de actividad es sencilla. La clase de proceso de trabajo de actividad recibe tareas de actividad de Amazon SWF, invoca el método de actividad apropiado de la implementación y devuelve el resultado a Amazon SWF.

La ejecución de las tareas de decisión requiere más pasos. El proceso de trabajo de flujo de trabajo recibe las tareas de decisión de Amazon SWF. Una tarea de decisión es en realidad una solicitud que pregunta a la lógica de flujo de trabajo qué debe hacer a continuación. La primera tarea de decisión se genera para una instancia de flujo de trabajo cuando se inicia a través del cliente de flujo de trabajo. Al recibir esta tarea de decisión, el marco de trabajo comienza a ejecutar el código en el método de flujo de trabajo anotado con @Execute. Este método ejecuta la lógica de coordinación que programa actividades. Cuando el estado de la instancia de flujo de trabajo cambia, por ejemplo, cuando se completa una actividad, se programan tareas de decisión adicionales. En este punto, la lógica de flujo de trabajo puede decidir actuar en función del resultado de la actividad; por ejemplo, podría decidir programar otra actividad.

El marco de trabajo oculta todos estos detalles al desarrollador traduciendo a la perfección tareas de decisión a la lógica del flujo de trabajo. Desde el punto de vista de un desarrollador, el código tiene el mismo aspecto que un programa normal. Internamente, el marco de trabajo lo asigna a llamadas a Amazon SWF y a tareas de decisión mediante el historial que mantiene Amazon SWF. Cuando llega una tarea de decisión, el marco de trabajo reproduce la ejecución del programa incorporando los resultados de las actividades completadas hasta el momento. Las actividades y métodos asíncronos que estaban esperando estos resultados se desbloquean y la ejecución del programa avanza.

En la siguiente tabla se muestra la ejecución del flujo de trabajo de procesamiento de imágenes de ejemplo y el historial correspondiente.

La ejecución del flujo de trabajo de miniaturas
La ejecución del programa de flujo de trabajo El historial que mantiene Amazon SWF
Ejecución inicial
  1. Bucle de envío

  2. getImageUrls

  3. downloadImage

  4. createThumbnail (tarea en la cola de espera)

  5. uploadImage (tarea en la cola de espera)

  6. <siguiente iteración del bucle>

  1. Instancia de flujo de trabajo iniciada, id="1"

  2. downloadImage programada

Reproducir
  1. Bucle de envío

  2. getImageUrls

  3. ruta de imagen downloadImage="foo"

  4. createThumbnail

  5. uploadImage (tarea en la cola de espera)

  6. <siguiente iteración del bucle>

  1. Instancia de flujo de trabajo iniciada, id="1"

  2. downloadImage programada

  3. downloadImage completada, devuelve="foo"

  4. createThumbnail programada

Reproducir
  1. Bucle de envío

  2. getImageUrls

  3. ruta de imagen downloadImage="foo"

  4. ruta de miniatura createThumbnail="bar"

  5. uploadImage

  6. <siguiente iteración del bucle>

  1. Instancia de flujo de trabajo iniciada, id="1"

  2. downloadImage programada

  3. downloadImage completada, devuelve="foo"

  4. createThumbnail programada

  5. createThumbnail completada, devuelve="bar"

  6. uploadImage programada

Reproducir
  1. Bucle de envío

  2. getImageUrls

  3. ruta de imagen downloadImage="foo"

  4. ruta de miniatura createThumbnail="bar"

  5. uploadImage

  6. <siguiente iteración del bucle>

  1. Instancia de flujo de trabajo iniciada, id="1"

  2. downloadImage programada

  3. downloadImage completada, devuelve="foo"

  4. createThumbnail programada

  5. createThumbnail completada, devuelve="bar"

  6. uploadImage programada

  7. uploadImage completada

    ...

Cuando se realiza una llamada a processImage, el marco de trabajo crea una nueva instancia de flujo de trabajo en Amazon SWF. Se trata de un registro duradero de la instancia de flujo de trabajo que se está iniciando. El programa se ejecuta hasta la llamada a la actividad downloadImage que pide a Amazon SWF que programe una actividad. El flujo de trabajo se sigue ejecutando y crea tareas para las actividades posteriores, pero no se pueden ejecutar hasta que la actividad downloadImage se complete; por lo tanto, este episodio de reproducción finaliza. Amazon SWF envía la tarea de la actividad downloadImage para que se ejecute y, una vez finalizada, se registra en el historial junto con el resultado. El flujo de trabajo está ahora listo para avanzar y Amazon SWF genera una tarea de decisión. El marco de trabajo recibe la tarea de decisión y reproduce el flujo de trabajo incorporando el resultado de las imágenes descargadas tal y como está registrado en el historial. Esto desbloquea la tarea de createThumbnail, y la ejecución del programa continúa avanzando con la programación de la tarea de actividad createThumbnail en Amazon SWF. Se repite el mismo proceso para uploadImage. La ejecución del programa sigue su curso hasta que el flujo de trabajo ha procesado todas las imágenes y no hay tareas pendientes. Dado que no se almacena localmente ningún estado de ejecución, cada tarea de decisión podría ejecutarse potencialmente en una máquina diferente. Esto le permite escribir fácilmente programas que sean tolerantes a errores y fácilmente escalables.

No determinismo

Dado que el marco de trabajo confía en la reproducción, es importante que el código de orquestación (todo el código de flujo de trabajo con la excepción de las implementaciones de actividad) sea determinista. Por ejemplo, el flujo de control en su programa no debería depender de un número aleatorio o de la hora actual. Dado que estas cosas cambiarán entre invocaciones, la reproducción podría no seguir la misma ruta a través de la lógica de orquestación. Esto llevará a errores o resultados imprevistos. El marco de trabajo proporciona un WorkflowClock que puede utilizar para obtener la hora actual de manera determinista. Consulte la sección en Contexto de ejecución para obtener más información.

nota

Una conexión de Spring incorrecta de objetos de implementación de flujo de trabajo también puede llevar al no determinismo. Los beans de implementación de flujo de trabajo así como los bean de los que dependen tienen que estar dentro del alcance del flujo de trabajo (WorkflowScope). Por ejemplo, la conexión de un bean de implementación de flujo de trabajo a un bean que mantiene estado y está dentro del contexto global dará como resultado un comportamiento inesperado. Consulte la sección Integración con Spring para obtener más información.