Nos bastidores - AWS Flow Framework para Java

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Nos bastidores

Tarefa

O primitivo subjacente que o AWS Flow Framework do Java usa para gerenciar a execução do código assíncrono é a classe Task. Um objeto do tipo Task representa trabalho que precisa ser executado de forma assíncrona. Quando você chama um método assíncrono, a estrutura cria uma Task para executar o código nesse método e coloca-a em uma lista para execução em um momento posterior. De modo semelhante, quando você invoca uma Activity, uma Task é criada para ela. A chamada do método retorna depois disso, geralmente retornando uma Promise<T> como o resultado futuro da chamada.

A classe Task é pública e pode ser usada diretamente. Por exemplo, podemos reescrever o exemplo de Hello World para usar uma Task em vez de um método assí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() +"!"); } }; }

A estrutura chama o método doExecute() quando todas as Promises passadas para o construtor da Task ficam prontas. Para obter mais detalhes sobre a classe Task, consulte a documentação do AWS SDK for Java.

A estrutura também inclui uma classe chamada Functor que representa uma Task que também é uma Promise<T>. O objeto Functor fica pronto quando a Task é concluída. No exemplo a seguir, um Functor é criado para obter a mensagem de saudação:

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

Ordem de execução

As tarefas se tornam qualificadas para execução somente quando todos os parâmetros tipados de Promise<T>, passados para a atividade ou o método assíncrono correspondente estiverem prontos. Uma Task pronta para execução é movida logicamente para uma fila pronta. Ou seja, ela é programada para execução. A classe worker executa a tarefa invocando o código que você escreveu no corpo do método assíncrono ou agendando uma tarefa de atividade no Amazon Simple Workflow Service (AWS) no caso de um método de atividade.

Conforme as tarefas executam e produzem resultados, elas fazem que outras tarefas fiquem prontas e a execução do programa continua. A forma como a estrutura executa tarefas é importante para compreender a ordem em que o código assíncrono é executado. O código que aparece sequencialmente em seu programa pode não ser realmente executado naquela ordem.

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!"); }

O código na lista acima imprimirá o seguinte:

Hello, Amazon! Hello, World! Hello, Bob

Isso pode não ser o que você esperava, mas pode ser facilmente explicado avaliando a maneira como as tarefas dos métodos assíncronos foram executadas:

  1. A chamada para getUserName cria uma Task. Vamos chamá-la de Task1. Como getUserName não usa nenhum parâmetro, a Task1 é colocada imediatamente na fila pronta.

  2. Em seguida, a chamada para printHelloName cria uma Task que precisa esperar o resultado de getUserName. Vamos chamá-la de Task2. Como o valor necessário ainda não está pronto, a Task2 é colocada na lista de espera.

  3. Em seguida, uma tarefa para printHelloWorld é criada e adicionada à fila pronta. Vamos chamá-la de Task3.

  4. A instrução println imprime "Hello, Amazon!". para o console.

  5. Nesse ponto, Task1 e Task3 estão na fila pronta e Task2 está na lista de espera.

  6. O operador executa Task1 e seu resultado faz com que Task2 fique pronto. O Task2 é adicionado à fila pronta atrás de Task3.

  7. A Task3 e a Task2 são então executadas nessa ordem.

A execução de atividades segue o mesmo padrão. Quando você chama um método no cliente de atividade, ele cria um Task que, após a execução, agenda uma atividade no Amazon SWF.

A estrutura depende de recursos, como a geração de código e os proxies dinâmicos, para injetar a lógica para converter chamadas de métodos em invocações de atividades e tarefas assíncronas em seu programa.

Execução de fluxo de trabalho

A execução da implementação do fluxo de trabalho também é gerenciada pela classe do operador. Quando você chama um método no cliente de fluxo de trabalho, ele chama o Amazon SWF para criar uma instância de fluxo de trabalho. As tarefas no Amazon SWF não devem ser confundidas com as tarefas na estrutura. Uma tarefa no Amazon SWF é uma tarefa de atividade ou uma tarefa de decisão. A execução de tarefas de atividades é simples. A classe activity worker recebe tarefas de atividade do Amazon SWF, invoca o método de atividade apropriado em sua implementação e retorna o resultado ao Amazon SWF.

A execução de tarefas de decisão é mais envolvida. O trabalhador do fluxo de trabalho recebe tarefas de decisão do Amazon SWF. Uma tarefa de decisão é efetivamente uma solicitação que pergunta à lógica do fluxo de trabalho o que fazer em seguida. A primeira tarefa de decisão é gerada para uma instância de fluxo de trabalho quando é iniciada no cliente de fluxo de trabalho. Ao receber essa tarefa de decisão, a estrutura começa a executar o código no método de fluxo de trabalho anotado com @Execute. Esse método executa a lógica de coordenação que programa as atividades. Quando o estado da instância do fluxo de trabalho muda - por exemplo, quando uma atividade é concluída - outras tarefas de decisão são agendadas. Nesse ponto, a lógica do fluxo de trabalho pode decidir realizar uma ação com base no resultado da atividade; por exemplo, pode decidir programar outra atividade.

A estrutura oculta todos esses detalhes do desenvolvedor convertendo perfeitamente as tarefas de decisão na lógica do fluxo de trabalho. Do ponto de vista do desenvolvedor, o código parece ser exatamente um programa normal. Por baixo dos panos, a estrutura mapeia as chamadas para o Amazon SWF e as tarefas de decisão usando o histórico mantido pelo Amazon SWF. Quando uma tarefa chega, a estrutura reproduz a execução do programa conectando os resultados das atividades concluídas até o momento. Os métodos e as atividades assíncronos que estavam esperando esses resultados são desbloqueados, e a execução do programa continua.

A execução do fluxo de trabalho de processamento de imagem de exemplo e o histórico correspondente são mostrados na tabela a seguir.

Execução de fluxo de trabalho de miniaturas
Execução do programa do fluxo de trabalho Histórico mantido pelo Amazon SWF
Execução inicial
  1. Loop de expedição

  2. getImageUrls

  3. downloadImage

  4. createThumbnail (tarefa na fila de espera)

  5. uploadImage (tarefa na fila de espera)

  6. <próxima iteração do loop>

  1. Instância de fluxo de trabalho iniciada, id= "1"

  2. downloadImage programado

Reproduzir novamente
  1. Loop de expedição

  2. getImageUrls

  3. downloadImage image path="foo"

  4. createThumbnail

  5. uploadImage (tarefa na fila de espera)

  6. <próxima iteração do loop>

  1. Instância de fluxo de trabalho iniciada, id= "1"

  2. downloadImage programado

  3. downloadImage concluído, retorno = "foo”

  4. createThumbnail programado

Reproduzir novamente
  1. Loop de expedição

  2. getImageUrls

  3. downloadImage image path="foo"

  4. createThumbnail thumbnail path="bar"

  5. uploadImage

  6. <próxima iteração do loop>

  1. Instância de fluxo de trabalho iniciada, id= "1"

  2. downloadImage programado

  3. downloadImage concluído, retorno = "foo”

  4. createThumbnail programado

  5. createThumbnail concluído, retorno = "bar"

  6. uploadImage programado

Reproduzir novamente
  1. Loop de expedição

  2. getImageUrls

  3. downloadImage image path="foo"

  4. createThumbnail thumbnail path="bar"

  5. uploadImage

  6. <próxima iteração do loop>

  1. Instância de fluxo de trabalho iniciada, id= "1"

  2. downloadImage programado

  3. downloadImage concluído, retorno = "foo”

  4. createThumbnail programado

  5. createThumbnail concluído, retorno = "bar"

  6. uploadImage programado

  7. uploadImage concluído

    ...

Quando uma chamada para processImage é feita, a estrutura cria uma nova instância de fluxo de trabalho no Amazon SWF. Este é um registro durável da instância de fluxo de trabalho que está sendo iniciada. O programa é executado até a chamada para a atividade downloadImage, que pede ao Amazon SWF para agendar uma atividade. O fluxo de trabalho continua a ser executado e cria tarefas para as atividades subsequentes, mas elas não podem ser executadas até que a atividade downloadImage seja concluída; portanto, esse episódio de repetição termina. O Amazon SWF despacha a tarefa para a atividade downloadImage para execução e, uma vez concluída, um registro é feito no histórico junto com o resultado. O fluxo de trabalho agora está pronto para avançar e uma tarefa de decisão é gerada pelo Amazon SWF. A estrutura recebe a tarefa de decisão e reproduz a conexão do fluxo de trabalho no resultado da imagem obtida por download conforme registrado no histórico. Isso desbloqueia a tarefa para createThumbnail, e a execução do programa continua mais adiante, agendando a tarefa de atividade createThumbnail na Amazon. O mesmo processo se repete para uploadImage. A execução do programa continua dessa maneira até que o fluxo de trabalho tenha processado todas as imagens e não houver tarefas pendentes. Como nenhum estado de execução é armazenado localmente, cada tarefa de decisão pode ser potencialmente executada em uma máquina diferente. Isso permite que você escreva facilmente programas que são tolerantes a falhas e facilmente escaláveis.

Não determinismo

Como a estrutura depende da reprodução, é importante que o código de orquestração (todo código de fluxo de trabalho com exceção de implementações de atividades) seja determinista. Por exemplo, o fluxo de controle em seu programa não deve depender de um número aleatório ou da hora atual. Já que essas coisas serão alteradas entre invocações, a reprodução não pode seguir o mesmo caminho na lógica da orquestração. Isso levará a resultados ou a erros inesperados. A estrutura fornece um WorkflowClock que você pode usar para obter a hora atual de forma determinista. Consulte a seção em Contexto de execução para obter mais detalhes.

nota

A conexão incorreta de objetos da implementação de fluxo de trabalho do Spring também pode levar ao não determinismo. Os beans de implementação de fluxo de trabalho bem como os beans dos quais dependem devem estar no escopo do fluxo de trabalho (WorkflowScope). Por exemplo, a conexão de um bean de implementação de fluxo de trabalho a um bean que mantém o estado e esteja no contexto global resultará em comportamento inesperado. Consulte a seção Integração com o Spring para obter mais detalhes.