BT

Atores de nuvem reativos: Uma web de eventos evolutiva

| por Ali Kheyrollahi Seguir 0 Seguidores , traduzido por Lu Araujo Seguir 1 Seguidores em 23 jul 2015. Tempo estimado de leitura: 29 minutos |

Carl Hewitt definiu o Modelo de Ator em 1973 como:

uma teoria matemática que trata "Atores" como as primitivas universais da computação digital concorrente.

Os atores tem sido foco de tendências recentes na Computação Distribuída. Ultimamente, existe uma série de novidades sobre atores e seu lugar no processamento concorrente baseado na nuvem e no mundo do "Big Data". Enquanto o modelo de atores tenha sido usado historicamente com foco na modelagem e implementação de computação paralela para somente um processo executando em apenas uma máquina, agora passa a ser um modelo adequado para processamento paralelo pesado em ambiente de nuvem.

Neste artigo, vamos primeiramente definir três tipos de atores e explorar seus papéis e responsabilidades. Na sequência, vamos revisar um paradigma de quebra de processos de negócio em "eventos web evolutivos" em que, qualquer passo "significante" no processo de negócio gera um evento com duração. Este evento pode ser processado independentemente por um ou mais atores. Também vamos ver alguns exemplos reais do Modelo de Atores. No final, consideramos as implicações da adoção desse padrão e revisamos um exemplo de implementação desta abordagem utilizando a biblioteca BeeHive.

Modelo de ator

Carl Hewitt, juntamente com Peter Bishop e Richard Steiger, publicou um artigo em 1973 propondo um formalismo que identificou uma classe especial de objetos: atores, como o bloco fundamental de sistemas projetados para implementar algoritmos de Inteligência Artificial.

Um padrão similar pôde ser visto mais tarde em Redes Neurais em que atuam como blocos fundamentais de uma rede neural - na qual um nó tem uma ligação, uma ou mais entradas (e seus pesos correspondentes) e uma ou mais saídas (com seus pesos correspondentes).

A diferença é que um ator, diferentemente de um nó neural, não é apenas uma unidade computacional simples. Atores são inerentemente unidades autônomas dentro de uma rede maior que são ativados pelo recebimento de mensagens.

Ator

De acordo com Hewitt um ator, em resposta a uma mensagem, pode:

  1. enviar um numero finito de mensagens para outros atores;
  2. criar um numero finito de atores;
  3. decidir a respeito do comportamento a ser usado para próxima mensagem que for recebida.

Qualquer combinação destas ações podem ocorrer concorrentemente e, em resposta às mensagens chegando em qualquer ordem. Assim, não existe restrição referente a ordenação de forma que uma implementação de ator deve estar apta a tratar mensagens chegando fora de ordem.

Ator processador

Em descrições posteriores do Modelo de Atores, o primeiro requisito é redefinido como "enviar um número finito de mensagens para o endereço de outros atores". Endereçamento é uma parte integral do modelo que desacopla atores e limita o conhecimento dos atores uns dos outros a um mero token (tal como: endereço). Implementações comuns de endereçamento incluem endpoints de Web Services, endpoints de filas Publish ou Subscribe e endereços de email. Atores que respondem uma mensagem utilizando este requisito são chamados Atores Processadores.

Fabrica de atores

O segundo requisito torna atores capazes de criar outros atores. O conjunto de atores com essa capacidade é convenientemente chamado de Fábrica de Atores. Fábricas de Atores são elementos importantes de um sistema orientado à mensagens em que um ator consome mensagens de uma fila e cria tratadores de mensagens de acordo com o seu tipo. Fábricas de Atores controlam o ciclo de vida dos atores que criam e tem um conhecimento aprofundado dos mesmos - comparados com Atores Processadores que conhecem apenas um endereço. É útil separar Fábricas Atores de Atores Processadores - alinhado com o princípio da responsabilidade única.

Ator estado

O terceiro requisito são os Atores Estado. Estes atores implementam o terceiro requisito e tem uma memória que permite que reajam de maneira diferente a mensagens em uma sequência. Tais atores estão sujeitos a uma série de efeitos colaterais. Primeiramente, quando falamos de mensagens subsequentes, assumimos uma ordem ainda que, conforme a especificação, não exista nenhuma restrição de ordem: a ordem em que mensagens chegam pode gerar complicações. Em segundo lugar, todos aspectos do teorema CAP são aplicáveis a essa "memória" uma vez que é impossível alcançar consistência com alta disponibilidade e tolerância a falhas em partes do sistema. Em resumo, é melhor evitar Atores Estado.

Conforme a proposta original, um ator pode ser baseado em qualquer combinação destes três requisitos, mas enfatizamos que construir atores com apenas uma responsabilidade resulta em projetos melhores e com maior desacoplamento. Neste artigo, utilizamos esta separação e propomos soluções que tiram partido destes benefícios - e vamos mostrar um exemplo de implementação seguindo esta abordagem.

Dependência de atores

O Modelo de Atores, ao restringir as interações de um ator, reduz o conhecimento que este tem a respeito do que está a sua volta. Um Ator Processador precisa conhecer apenas os endereços dos atores que estão ao seu lado na cadeia de atores. Removemos a necessidade desta informação, ao gerar um evento que descreve (nome do evento) e documenta (estado do evento) o que aconteceu. A rede de atores pode identificar o evento de acordo com o tipo do mesmo.

Uma Fábrica de Atores tem apenas o conhecimento necessário para criar atores que irão receber as mensagens que esta repassa. A Fábrica de Atores, controla o tempo de vida dos atores que cria, mas essencialmente, desconhece detalhes do processamento feito por eles.

Ao reduzir a dependência, também diminui o acoplamento dos elementos do sistema permitindo sua evolução. O comportamento de cada ator pode ser modificado de forma independente e, de fato, cada ator pode ser implantado de forma independente dos outros. A dependência reduzida é uma das virtudes mais importantes de um sistema baseado em atores. Ela leva à simplificação do projeto, reduzindo a dependência e desacoplando os elementos que compõem uma aplicação.

Todos os atores, além das características mencionadas anteriormente, dependem de um conjunto de parâmetros de configuração somente de leitura a respeito do ambiente em que operam. Esses parâmetros de configuração podem incluir vários aspectos do ambiente (como endereços) ou padrões de comportamento (tais como: a contagem de tentativas, tamanhos de buffers ou rastreamento de verbosidade).

Atores reativos

Os atores podem ser implementados de forma imperativa ou reativa. No caso de atores Imperativos, este envia mensagens RPC destinadas a um tipo de ator cuja interface (tipo e endereçamento) é conhecida. A mensagem é normalmente entregue através de canais de comunicação, usualmente, similares a implementações de enterprise service bus, no qual os artefatos distribuídos são reduzidos a forma mais simples possível.

(Clique para ampliar)

No caso de atores reativos, o remetente publica um evento representando o processo de negócios realizado (OrderAccepted, FraudDetected, ItemOutOfStock, ItemBackInStock, entre outros) e outros atores podem ou não optar por escutar tais eventos e executar ações de acordo com os mesmos. Através da subscrição aos eventos, os atores podem evoluir de forma independente e processos de negócios são afetados somente no âmbito dos atores subscritos para cada tipo de evento. Isso resulta em menos acoplamento e é uma boa estratégia para o desenvolvimento de sistemas analíticos e transacionais na escala horizontal fornecida pela nuvem.

Implementações de Atores Reativos já existem na indústria. O trabalho de Fred George em Reactive MicroServices é um ótimo exemplo. O Kinesis da Amazon pode ser visto como um framework de baixa granularidade para Atores Reativos.

Modelando um sistema baseado em atores

Sistemas baseados no Modelo de Atores são usados desde 1970. Esses sistemas adequaram atores para implementação de sistemas com alto grau de paralelismo. Erlang é uma das linguagens que incluiu suporte ao Modelo de Atores em si. Aplicações Erlang tem sido utilizadas com sucesso na indústria de telecomunicações por anos.

Atualmente, com a facilidade da capacidade de computação e armazenamento de baixo custo e alta disponibilidade, podemos distribuir um sistema para centenas ou milhares de nós através do clique de um botão. O desafio é projetar sistemas que tirem partido de todo esse poder. Um erro comum na adoção da nuvem é a abordagem "lift-and-shift" que basicamente significa distribuir a mesma arquitetura em camadas presumidas com um banco de dados relacional tradicional na nuvem - o que na maioria das vezes faz pouco sentido. Construir sistemas capazes de escalar horizontalmente e linearmente requer novos paradigmas na modelagem de estruturas de dados, algoritmos e dependências.

O objetivo é um sistemas escalável horizontalmente e linearmente, que é robusto, altamente disponível e ainda sim, fácil de evoluir. Vamos dar uma olhada em vários aspectos de tais sistemas.

Mensagens

Sistemas baseados em mensagens existem a muito tempo na indústria e foi a base da adoção da Arquitetura Orientada a Serviços (SOA) utilizando Entreprise Service Bus. Por outro lado, o uso do HTTP define a comunicação em termos de uma "MensagemResposta" como resposta a um "Request". Um Serviço SOAP responde requisições enviando de volta mensagens SOAP. Mesmo no nível mais baixo, no paradigma RPC, uma chamada de método pode ser modelada como uma mensagem de requisição e uma mensagem de resposta. Então, qual a diferença aqui no que diz respeito a mensagens?

No que diz respeito a Atores Processadores, estes apenas precisam saber o nome do tópico a partir do qual lêem mensagens e o nome do tópico para o qual enviam suas mensagens; detalhes como endereçamento, distribuição, recuperação de falhas, etc podem ser deixados para o responsável pelo controle das mensagens.

De forma a implementar um sistema baseado em atores robusto, deve ser utilizado um sistema de mensagens orientado a tópicos, durável, altamente disponível, tolerante a falhas e horizontalmente escalável. Exemplos na nuvem incluem Apache Kafka e o Service Bus do Windows Azure. EventStore e RabbitMQ são alternativas presumidas quando na nuvem.

Mensagem, evento e fato

Uma mensagem é um pedaço de informação que é destinado a um ator. Tradicionalmente mensagens devem ter um cabeçalho e um corpo. A mesma definição serve para uma mensagem de ator. Por outro lado, um evento, normalmente, é definido como "algo que aconteceu no passado".

De muitas maneiras um evento e uma mensagem são similares. De fato, o livro Enterprise Integration Patterns define Evento como uma subclasse de mensagem, isto é, Evento Mensagem. De qualquer forma, o livro também aponta que a diferença entre uma Mensagem Documento (que é o significado típico do termo Mensagem) e uma Mensagem Evento são o momento em que ocorrem e o conteúdo. Em outras palavras, Garantia de Entrega não é importante para um evento e eventos podem se usados principalmente para Inteligência Empresarial (isto é, medição de métricas de negócio) na qual há tolerância a precisão inexata.

Do outro lado do espectro, eventos são usados como a única fonte de verdade em um sistema - sistemas baseados em fontes de eventos são os principais proponentes dessa abordagem. Qualquer outro dado é gerado a partir de eventos que podem ser armazenados de formar permanente ou utilizar armazenamento baseado em memória volátil.

Definimos Evento como um pedaço de informação marcado com um timestamp, imutável, único e eternamente verdadeiro. Essa é uma definição similar a definição de Fato descrita por Nathan Marz. Também entendemos que nem todos os eventos tem a mesma importância para um negócio, de forma que seus requisitos de armazenamento e disponibilidade são diferentes. Por exemplo, em um cenário de varejo através de e-commerce (amazon.com, por exemplo), um evento de Solicitação de Pedido contém detalhes do pedido (quais produtos foram pedidos e em que quantidades) é de suma importância para o negócio, enquanto estados de transição do pedido (adição e remoção de produtos, alterações de quantidade, etc) apesar de úteis para fins de análise, não tem um nível de importância crítica para o negócio. Além do mais, nem todo tipo de dado é importante o bastante para ser capturado através de eventos: armazenar a posição do mouse do usuário a cada milisegundo durante uma visita a um site não é útil. Assim sendo, um negócio precisa decidir que dado será capturado. Por outro lado, armazenar dados que demandam de alta disponibilidade é caro e precisamos diferenciar requisitos de SLA para cada tipo de dado. Para tanto, diferenciamos entre dois tipos de eventos: eventos de negócio e eventos de log.

Eventos de negócio são as blocos fundamentais do fluxo do negócio e requerem durabilidade e consistência a nível transacional enquanto Eventos de Log são usados principalmente para medir métricas de negócio e realizar análises de forma que não precisam do mesmo nível de disponibilidade. Eventos de Negócio são fundamentais para o núcleo do negócio, enquanto Eventos de Log são dados que oferecem suporte ao processo decisório do negócio, ao atendimento de clientes ou na solução de problemas técnicos. A perda de um evento de negócio é considerada um incidente grave enquanto a perda de um evento de log, ainda que importante para o negócio, não tem impacto catastrófico sobre o mesmo.

Eventos como meio de Inversão de Controle

Injeção de Dependências (ID) é um conceito estabelecido do desenvolvimento de software convencional e é um padrão para alcançar o princípio de Inversão de Controle. Eventos em uma arquitetura com acoplamento fraco permitem atingir o mesmo objetivo que a Injeção de Dependências. O conhecimento é removido do consumidor e passa para o produtor de um evento permitindo atingir o mesmo nível de desacoplamento.

As figuras mostram o contraste do modelo de eventos em relação a uma instrução com destino definido que trata o comando.

Isto é basicamente o mesmo Princípio de Hollywood, "Não nos telefone, nós ligamos pra você", que é utilizado na injeção de dependências. O uso de eventos conduz a programação reativa que tem ganhado popularidade ao longo dos últimos anos. O uso de Programação Reativa (como por exemplo Extensões Reativas - Rx) simplifica a rede de dependências em softwares complexos. Assim, utilizar uma arquitetura reativa orientada a eventos (descrita pelo Manifesto Reativo) pode resultar em uma arquitetura com maior desacoplamento e mais fácil de evoluir.

Substituir comando por eventos é a principal quebra de paradigma em relação a abordagem SOA tradicional baseadas em ESB.

Modelando um evento

Conforme descrito anteriormente, um evento é um pedaço de informação marcado com um timestamp, imutável, único e eternamente verdadeiro. Exemplos de eventos associados a negócios incluem: CustomerCreated, OrderDispatched, BlogTagAdded, entre outros.

É conveniente descrever tipos de eventos. O Tipo de um evento descreve a informação que o evento contém. Um tipo de evento, normalmente, é único dentro do domínio de um sistema.

Um evento usualmente faz referência a um ou mais agregados (ver a seguir) dentro do sistema. Por exemplo: CustomerCreated contém o CustomerId enquanto BlogAddedTag contém o PostId e tags (caso a tag seja modelada como um Value Object) ou um TagId (caso seja uma entidade).

Cada evento - independente de seu conteúdo - deve ter uma identidade própria. É possível que dois eventos contenha a mesma informação, por exemplo, StaffTakenOffSick. É útil utilizar um GUID como a identidade do evento. Em alguns casos é possível utilizar o tipo em conjunto com o Id (por exemplo, existe apenas um CustomerRegistered para um CustomerId), no entanto, a identidade de um evento deve sempre ser definida de forma independente do seu conteúdo.

Um evento deve capturar toda informação referente ao evento de forma econômica. Quando se projeta um Provedor de Eventos, toda informação relacionada ao evento deve ser capturada de forma que todos os diferentes status de uma entidade possam ser reproduzidos a partir do fluxo de eventos para esta entidade. A Fonte dos Eventos é especialmente importante para Análise de Big Data. É neste ponto que estamos interessados em toda informação. ItemAddedToBAsket é um evento importante para fins de análise enquanto para sistemas transacionais é irrelevante até que o item seja enviado como parte da ordem. Por outro lado, um evento de CustomerCreated com toda informação a respeito do cliente é mandatória para sistemas de Big Data na qual dados precisam ser armazenados de forma independente de sistemas transacionais, mas não por um serviço de Verificação de Crédito que tem acesso direto aos dados e tem como foco apenas o estado atual do cliente.

Basicamente para um sistema transacional, preferimos publicar eventos menores e confiamos nas Estruturas de Dados Básicas para armazenar o estado. Em cada caso, eventos são usados como indicadores de transições de estado no sistema e o estado em si é armazenado nas estruturas de dados que vamos revisar rapidamente. Essa abordagem se sustenta no fato de que todo sistema tem acesso ao armazenamento de estruturas de dados - uma premissa que é convenientemente atingida em um cenário de nuvem.

O problema com a abordagem anterior é que vai sobrescrever o estado das entidades, de forma que o sistema de análise vai perder dados que pode ser importantes - não transacionalmente, mas para fins de análise. O sistema de análise precisa se apoiar em seus próprios dados de forma que não vai acessar o armazenamento de dados transacionais. Existem três soluções disponíveis:

  • Tem atores EventEnricher (enriquecedores de eventos) que subscrevem eventos, enriquecem o evento e publicam eventos contendo o estado extra a ser consumido pelo sistema de análise. Neste caso, não estará eliminando o risco de perder alguma transição de estado, uma vez que é provável que o enriquecimento aconteça após duas mudanças de estado sucessivas. Na maior parte dos casos isso é pouco provável ou não é um problema para o sistema de análise;
  • Seus atores publicam dois eventos: uma para consumo transacional e outro para uso do sistema de Análise de Big Data;
  • Abandonar completamente o armazenamento de dados e adota o Fonte de Eventos. Devido a maior complexidade e pelas razões apresentadas anteriormente, não recomendamos essa opção para um sistema cujo principal objetivo é transacional. Esse modelo também não se adequa a alguns tipos de estruturas de dados, como contadores, e resulta em alta concorrência pelos recursos; uma discussão completa deste tópico merece um outro artigo.

Filas

Características de filas necessárias para um Ator Reativo de Nuvem são:

  • Alta disponibilidade e tolerância a falhas;
  • Suporte à subscrição por assuntos;
  • Capacidade para receber uma mensagem e então descartá-la ou fazer seu commit;
  • Capacidade de implementar uma espera (delay) na fila;
  • Suporte para long polling;
  • Suporte opcional para separação em lotes.

Já existem tecnologias como Kafka, Azure Service Buss, RabbitMQ e ActionMQ que disponibilizam as funcionalidades listadas anteriormente - em alguns casos faltam algumas das funcionalidades.

Outras estruturas de dados

Sistemas de software tradicionais usualmente modelam seus dados utilizando linhas e colunas em sistemas relacionais de gerenciamento de dados (RDBMS). Na verdade, não é incomum ver tabelas de bancos de dados utilizadas como filas - quando temos um martelo, tudo se torna um prego. Para um sistema orientado a eventos puro, todos os dados são eventos guardados em um armazenador de eventos - com snapshots opcionais. Em um banco de dados de documentos, dados são armazenados como documentos com indexes opcionais. Independente da ferramenta utilizada, escolher a estrutura de dados apropriada para modelagem dos dados é essencial. Cada estrutura de dados é otimizada para uma certa operação e forçar uma parte dado em um estrutura inadequada usualmente resulta em compromissos que envolvem algum tipo de perda.

Acreditamos que BigTable pode ser usado para expressar estruturas de dados seminais (filas são uma exceção uma vez que são implementadas através de um barramento de serviços) para criar um sistema de nuvem transacional escalável horizontalmente com desempenho aceitável. É notável que o Redis implementa todas as estruturas de dados seminais - uma pena que não foi projetado para prover o nível de consistência necessário para um sistema transacional.

Todo dado armazenado deve ter uma identidade. Isso pode ser alcançado através da associação de cada entidade a um Id. Para tanto, cada entidade deve implementar a interface a seguir:

public interface IHaveIdentity
{
   string Id { get; }
}

Visando a concorrência, entidades podem suportá-la através da implementação da seguinte interface:

public interface IConcurrencyAware
{
   string ETag { get; }
   DateTimeOffset? LastModified { get; }
}

O mecanismos de armazenamento de estruturas de dados vão verificar se uma entidade implementa esta interface e, se for o caso, verificar se existem conflitos de concorrência no caso de updates e deletes.

Vamos revisar estas estruturas chave a seguir.

Chave-Valor

Chave-valor é a estrutura mais básica na qual um valor arbitrário (usualmente um array de bytes não interpretado) é armazenado e associado a uma string que correspondente a chave. Chaves podem ser combinadas para criar coleções. Alternativamente podemos prefixar o nome da chave. Normalmente não é possível iterar por chaves, o que é uma grande desvantagem. Operações em armazenamentos estruturados com Chave-Valor são atômicas e podem oferecer (mas frequentemente não o fazem) suporte a concorrência. De qualquer forma, devem oferecer lock sem timeout e cleanup automático.

Lista chaveada

Uma lista chaveada é similar a uma lista chave-valor, com a diferença de que é possível associar chaves a outras chaves criando, assim, o encadeamento. A lista associada a uma chave pode ser grande, sendo possível iterar por ela ou atualizá-la sem que seja necessário carregar todos os dados.

Coleções

Coleções são estruturas de armazenamento chave-valor que operam de acordo com um tipo de coleção lógico. A diferença entre coleções e estruturas chave-valor simples é a possibilidade de iterar pelos objetos da coleção - apesar de que, dependendo do tamanho da coleção, esse pode ser um processo demorado. Chaves podem ser utilizadas para definir limites de forma a permitir a iteração por conjuntos menores de dados. Coleções devem suportar concorrência (Etag e/ou LastModified ou Timestamp).

Contadores

Contadores, conforme a nome diz, guardam um número que pode ser incrementado atomicamente ou decrementado. Esta é, usualmente, a estrutura de dados que falta em muitos armazéns de dados - no momento em que escrevemos esse artigo, não existem contatores atômicos disponíveis no Windows Azure.

Modelando atores

Conforme mencionado anteriormente, precisamos projetar Atores Processadores e Fábricas de Atores e esquecer Atores Estado. Então, vamos analisar os dois primeiros.

Ator processador

Projeto

Os processos de negócio acontecem no ator processador e, por isso, seu projeto é da maior importância. Devem ser projetados para suportar todos os cenários e, ainda, inerentemente se adequarem a um nível de tolerância a falhas necessário em ambiente de nuvem, nos quais falhas são relativamente comuns.

Propomos a a interface a seguir para Atores Processadores e acreditamos cobrir a maioria dos cenários (Código em C#):

public interface IProcessorActor : IDisposable
{
   Task> ProcessAsync(Event message);
}

O Método anterior aceita um evento e retorna uma promessa para uma lista enumerável de n-eventos. Um ator deveria tipicamente retornar n ou 1 evento. Em algumas circunstâncias pode ser que retorne mais mensagens. Tarefa é uma promessa em C# e é usada para implementar tarefas assíncronas - convenientemente usando o padrão async/await. Processos de nuvem são tipicamente IO-bound (leitura/escrita em filas ou armazéns de dados) e a importância de implementações assíncronas usando IO Completion Ports não podem ser suficientemente enfatizadas.

Tipos de eventos e filas

Atores Processadores assinam um ou mais tipos de eventos. Isso normalmente é feito através da criação de uma assinatura dedicada um tópico. Algumas vezes uma fila simples é suficiente, mas a natureza de uma Arquitetura Orientada a Eventos é que eventos inicialmente criados com um propósito, se tornam úteis para outros propósitos de forma que ter um tópico e uma assinatura default é, usualmente, uma melhor opção. Dito isso, algumas vezes é necessário ser explícito a respeito de um evento quando o mesmo é usado como um mecanismo de parada de um processo com muitos passos.

Um ator tipicamente se registra para receber apenas um tipo de evento. Dito isto, se um passo implementado pelo ator envolver uma ação compensatória no caso de falha nos passos do fluxo de execução, este passo é o melhor lugar para implementação da ação compensatória. Nesse caso, o ator se registra para receber o evento que está associado com a ação compensatória.

Processo

Atores devem ser projetados para executar apenas uma parte do trabalho- e para fazê-lo bem. No caso de uma falha, a mensagem vai retornar a fila e estará disponível para outros atores após um período de timeout. Um processo de novas tentativas vai determinar o número de vezes que uma mensagem pode ser tentada e qual será o delay após a falha.

Um processo Scatter-Gather pode, algumas vezes ser decomposto e processos paralelos convertidos em processos sequênciais e definir múltiplos tipos de eventos e atores para cada processo. De qualquer forma, nem sempre isso é possível devido ao delay introduzido no processo. Assim, a implementação de um processo Scatter-Gatter é melhor quando tem seu workflow de estados representado por uma entidade (guardado em um armazém de dados do tipo coleção) com múltiplas flags. O Ator Scatter retorna múltiplos eventos (um para cada passo) e na finalização de cada processo Scattered, é gerado um evento e um ator com a configuração apropriada. Quando o estado de todas as flags for consistente, o item pode seguir adiante no processo. É muito importante implementar esse workflow de estados com suporte a concorrência.

Quebrar um processo de negócio em etapas leva á:

  • Agilidade e flexibilidade na manutenção do processo. Etapas podem ser alteradas facilmente e branches paralelos podem ser adicionados ou removidos;
  • Isolamento na distribuição. Cada ator pode ser distribuído independentemente;
  • Modelo de programação e testes unitários mais simples;
  • Modelo de tolerância a falhas mais simples para atores;
  • Melhoria na manutenibilidade e rastreabilidade do código.

Todos os itens mencionados anteriormente resultam em uma redução geral nos custos do projeto.

Existem desvantagens:

  • Aumento na latência das operações, uma vez que envolvem acessos de inserção e remoção de elementos nas filas. Atores Reativos de Nuvem visam o tradeoff de latência versus simplicidade e consistência eventual. Na maioria dos casos a latência ainda vai estar na casa de alguns segundos para uma mensagem passar por todo processo, o que, usualmente está dentro dos limites de tolerância;
  • O código é mais complexo, uma vez que está distribuído entre os múltiplos atores o que torna difícil acompanhar a evolução do processo pelo código e comunicar o workflow do processo a stakeholders. Diagramas atualizados do workflow são obrigatórios para manter a visibilidade do processo - uma tarefa que deve ser assumida pelas equipes de governança e arquitetura;
  • É necessário utilizar logs detalhados e ferramentas de log que mostrem o status dos itens a medida que evoluem dentro do sistema. Uma vez que o sistema é construído para nuvem, essas características de log são mandatórias independente das arquitetura, mas os custos e esforços neste sentido não devem ser subestimados.

Fábrica de atores

Fábricas de Atores são responsáveis por: Criar um Ator Processador e gerenciar seu ciclo de vida. Configurar gerentes de fila para receber mensagens de subscrição, encaminhar mensagens que chegam para o processamento por atores processadores. Após o processamento com sucesso de uma mensagem, publicar eventos a serem alocados na fila de mensagem e, então, marcar a mensagem recebida com o indicador de sucesso. No caso de uma falha, abandonar a mensagem ou começar novamente seu processamento. Novas tentativas podem ser tratadas pela Fábrica de Atores ou configuradas em uma fila.

Uma Fábrica de Atores é, normalmente, parte do framework/biblioteca base e não precisa ser implementado. Fábricas de Atores, tipicamente usam um framework de Injeção de Dependência para inicializar o Ator processador juntamente com suas dependências.

BeeHive é um miniframework, correspondente a nossa implementação de atores reativos para nuvem, que vamos revisar na sequência.

BeeHive

BeeHive é uma implementação de código aberto de Atores Reativos para Nuvem desenvolvida em C#. O projeto foca na definição de interfaces e padrões para implementar uma solução de Atores Reativos baseados na nuvem. Está disponível uma implementação de fila e estruturas de dados básicas para Azure. Além disso, implementar as interfaces para outras plataformas de nuvem e tecnologias requer pouco esforço.

Evento

Um evento é formado por um corpo do tipo string (o BeeHive utiliza serialização de JSON mas, apesar da possibilidade de desacoplar a serialização, atualmente não pareceu relevante fazê-lo) com seus metadados:

{
Task> ProcessAsync(Event message);
}

Exemplo de Evento

public sealed class Event : ICloneable
{
   public DateTimeOffset Timestamp { get; set; }
   public string Id { get; private set; }
   public string Url { get; set; }
   public string ContentType { get; set; }
   public string Body { get; set; }
   public string EventType { get; set; }
   public object UnderlyingMessage { get; set; }
   public string QueueName { get; set; }
   public T GetBody()
   {
      ...
   }
   public object Clone()
   {
      ...
   }
}

Implementação do ator

De forma a construir um ator reativo, a interface IProcessorActor deve ser implementada para aceitar um evento e retornar uma promessa (Task) para IEnumerable<Event>. O Ator não tem de lidar com erros inesperados uma vez que todas as exceções não tratadas serão encaminhadas para Fábrica de Atores responsável por alimentar e manter o ciclo de vida do Ator.

A interface IProcessorActor inclui a interface IDisposable que dá ao ator a oportunidade de executar uma limpeza (clean up).

Uma vez que um ator pode se inscrever para receber diferentes tipos de evento, a propriedade EventType do evento é utilizada para descobrir o tipo de evento recebido.

Configuração de atores

A configuração do ator informa a Fábrica de Atores como criar, alimentar e manter um Ator Processador. Uma vez que a configuração de atores pode ser tediosa e trabalhosa, o BeeHive proporciona uma configuração baseada em atributos pronta para uso.

[ActorDescription("OrderShipped-Email")]
public class OrderShippedEmailActor : IProcessorActor
{
   public async Task> ProcessAsync(Event evnt)
   {
      ...
   }
}

O nome do evento tipicamente incluem duas strings separadas por um hífen na qual a primeira é o tipo do evento (que corresponde ao nome da fila) e a segunda é o nome da subscrição. Por exemplo, se nos definimos "OrderAccepted-PaymentProcessing" para o ator PaymentActor, esse vai se inscrever no PaymenteProcessing do tópico OrderAccepted. Se a fila é simples (e não baseada em tópicos), apenas o tipo do evento é usado.

Ao implementar a interface IActorConfiguration é possível carregar a configuração do ator a partir de um arquivo ou banco de dados.

public interface IActorConfiguration
{
IEnumerable GetDescriptors();
}

Caso esteja usando a configuração baseada em atributos, é possível criar uma instância da configuração conforme:

var config = ActorDescriptors.FromAssemblyContaining()
  ToConfiguration();

Exemplo Prismo eCommerce

O Prismo eCommerce é um exemplo de implementação de um processo de negócios utilizando BeeHive. O processo de negócio implementado começa com uma ordem aceita e segue esta ordem por todos os estágios até que a mesma seja enviada ou cancelada. Esse processo envolve autorização de pagamento, verificação de fraude, verificação de inventário, reposição de inventário caso fora de estoque, etc.

(Clique para ampliar)

Implementações em memória e no Azure são incluídas nos exemplos do BeeHive.

Conclusão

Atores Reativos de Nuvem são um paradigma orientado a eventos para construir aplicações na nuvem que são compostas por atores independentes que funcionam através da publicação e consumo de eventos. Diferente de atores imperativos que enviam mensagens RPC para descobrir a localização de outros atores, um ator reativo não sabe que ator(es) vão estar consumindo os eventos que publicar. Um ator reativo está interessado em um ou mais tipos de eventos e realiza uma atividades atômica simplificando ações de tolerância a falhas e compensações. Todos os eventos são armazenados em negociadores de mensagens escaláveis horizontalmente que são, tipicamente, duráveis.

Esse paradigma utiliza um ponto de vista sobre os atores de Hewitt separando a responsabilidade de cada ator, definindo a tríade Ator Processador (responsável por processar eventos conforme explicado anteriormente), Fábrica de Atores (gerencia o ciclo de vida do Ator Processador, alimenta os atores e encaminha eventos que por algum motivo retornaram) e o Ator Estado. Atores Processadores são específicos de cada aplicação e realizam atividades de negócio enquanto Fábricas de Atores são implementadas e disponibilizadas pelo framework que implementa a plataforma. Atores Estado devem ser evitados e todo estado é armazenado em armazéns de dados de alta disponibilidade baseados na nuvem.

Atores Reativos de Nuvem identificam quatro Estruturas de Dados Básicas para armazenar estado: Chave-Valor, Coleções, Contatores e Listas Chaveadas. Chave-Valor pode ser implementada por uma variedade de tecnologias, mas a implementação típica é o S3. As implementações de BigTable do Google (como o DynamoDB) são capazes de armazenar os outros 3 tipos de estrutura de dados.

BeeHive é uma implementação em C# de Atores Reativos de Nuvem que vem com um exemplo de eCommerce.

Este artigo é inspirado pelo trabalho de Fred George em Microserviços e Arquitetura Reativa Orientada a Eventos e é humildemente dedicado a ele.

Sobre o author

Ali Kheyrollahi é um Arquiteto de Soluções, autor, blogger, ativo na comunidade de código aberto atualmente trabalhando em um grande eCommerce em Londres. Ele é apaixonado por HTTP, Web, APIs, REST, DDD e modelagem conceitual se mantendo pragmático na resolução de problemas reais. Tem 12 anos de experiência na indústria tendo trabalhado para inúmeras empresas de blue chip. Tem interesse nas áreas de Visão Computacional e Aprendizagem de Máquina e publicou alguns artigos nessa área. Anteriormente, foi médico e trabalho por 5 anos como Clínico Geral. Ele publica neste Blog e twitta com frequência sob o alias @aliostad.

Avalie esse artigo

Relevância
Estilo/Redação

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

Dê sua opinião

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão
Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

Dê sua opinião

Faça seu login para melhorar sua experiência com o InfoQ e ter acesso a funcionalidades exclusivas


Esqueci minha senha

Follow

Siga seus tópicos e editores favoritos

Acompanhe e seja notificados sobre as mais importantes novidades do mundo do desenvolvimento de software.

Like

Mais interação, mais personalização

Crie seu próprio feed de novidades escolhendo os tópicos e pessoas que você gostaria de acompanhar.

Notifications

Fique por dentro das novidades!

Configure as notificações e acompanhe as novidades relacionada a tópicos, conteúdos e pessoas de seu interesse

BT