BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Microservices: Decomposição de Aplicações para Implantação e Escalabilidade

Microservices: Decomposição de Aplicações para Implantação e Escalabilidade

Favoritos

A arquitetura monolítica é um padrão comumente usado para o desenvolvimento de aplicações corporativas. Esse padrão funciona razoavelmente bem para pequenas aplicações, pois o desenvolvimento, testes e implantação de pequenas aplicações monolíticas é relativamente simples. No entanto, para aplicações grandes e complexas, a arquitetura monolítica torna-se um obstáculo ao desenvolvimento e implantação, dificulta a utilização de uma entrega contínua, além de limitar a adoção de novas tecnologias. Para grandes aplicações, faz mais sentido usar uma arquitetura de microservices, que divide a aplicação em um conjunto de serviços.

A arquitetura de microservices tem muitas vantagens, por exemplo, serviços individuais são mais fáceis de entender e podem ser desenvolvidos e implantados de forma independente. Adotar novas tecnologias e frameworks torna-se mais fácil, pois a adoção pode ser aplicada em um serviço de cada vez. A arquitetura de microservices também tem algumas desvantagens significativas. Em particular, as aplicações são muito mais complexas e constituídas por mais elementos. Para ser utilizada de forma eficaz, a arquitetura de microservices exige um alto nível de automação, como um PaaS. Ao desenvolver microservices, é necessário lidar com alguns problemas complexos de gerenciamento de dados distribuídos. Apesar dos obstáculos, a arquitetura de microservices é adequada para aplicações complexas e de grande porte que estão evoluindo rapidamente, especialmente para aplicações do tipo SaaS.

Existem várias estratégias para evoluir de forma incremental uma aplicação monolítica existente em uma arquitetura de microservices. Os desenvolvedores devem implementar novas funcionalidades na forma de serviços independentes e criar mecanismos para integrar os serviços com a aplicação monolítica. É possível também identificar componentes da aplicação monolítica e refatorá-los em serviços. Embora a refatoração não seja trivial, é melhor do que desenvolver e manter uma aplicação monolítica pesada e inadequada.

Este artigo descreve um padrão de arquitetura que está se tornando muito popular conhecido como Microservice. A ideia da arquitetura de microservices é projetar aplicações grandes, complexas e duráveis em um conjunto coeso de serviços que evoluem ao longo do tempo. O termo microservices sugere fortemente que os serviços sejam pequenos.

Algumas pessoas da comunidade defendem ainda o desenvolvimento de serviços com 10-100 linhas de código. Embora seja desejável que os sistemas sejam pequenos, a quantidade de código não deve ser o principal objetivo. Em vez disso, deve-se visar a decomposição do sistema em serviços, a fim de resolver problemas de desenvolvimento e implantação discutidos neste artigo. Alguns serviços podem ser de fato muito pequenos, enquanto outros podem ser muito grandes.

A essência da arquitetura de microservices, assim como o conceito de sistemas distribuídos; é bem antiga e se assemelha ao SOA.

A arquitetura de microservices já foi considerada uma versão leve ou de granularidade fina de SOA. De fato, uma forma de pensar na arquitetura de microservices está relacionada com SOA, porém sem a o fator comercial e livre de tecnologias WS* e ESB. Apesar de não ser uma ideia totalmente nova, ainda é válida a discussão sobre a arquitetura de microservices, uma vez que difere do tradicional SOA e, mais importante, soluciona vários problemas que muitas organizações sofrem atualmente.

Este artigo apresenta as motivações do uso da arquitetura de microservices e a compara com a tradicional arquitetura monolítica. O artigo discute os benefícios e desvantagens do uso da arquitetura de microservices, além de apresentar soluções para alguns desafios técnicos que incluem a comunicação entre serviços e o gerenciamento de dados distribuído.

A (algumas vezes má) aplicação monolítica

Desde de o inicio do desenvolvimento web, a arquitetura mais utilizada em aplicações corporativas tem sido aquela que empacota todos os componentes do servidor (server-side) em uma única unidade. Muitas aplicações corporativas desenvolvidas em Java são formadas por um simples arquivo WAR ou EAR. O mesmo ocorre com aplicações escritas em outras linguagens, como Ruby e até mesmo C++.

Imagine por exemplo, o desenvolvimento uma loja online que recebe pedidos de clientes, verifica o estoque e o crédito do cliente e envia os produtos. É muito provável que a aplicação seja semelhante a mostrada na Figura 1.

Fig1-small.png

Figura 1 - a arquitetura monolítica

A aplicação é constituída por diversos componentes como, StoreFront UI que implementa a interface com o usuário, serviço para o gerenciamento do catálogo de produtos (Catalog Service), serviço de processamento de pedidos (Order Service) e serviço de gerenciamento de clientes (Customer Service). Estes serviços compartilham o mesmo modelo de domínio, constituído por entidades como Produto (Product) , Pedido (Order) e Cliente (Customer).

Apesar de ter um design modular, a aplicação é implantada de forma monolítica. Por exemplo, em aplicações desenvolvidas em Java, apenas um arquivo WAR é implantado em container web como o Tomcat. Em aplicações desenvolvidas em Rails, um único diretório é implantado no Apache/Nginx através do Phusion Passager, ou aplicações desenvolvidas com JRuby implantadas no Tomcat.

A denominada arquitetura monolítica tem uma série de benefícios. Aplicações monolíticas são simples para desenvolver, uma vez que IDEs e outras ferramentas de desenvolvimento são direcionadas para o desenvolvimento de uma única aplicação. Elas são fáceis de testar, pois é preciso apenas executar o aplicativo. Aplicações monolíticas também são de simples implantação, basta apenas copiar a unidade de implantação - um arquivo ou diretório - para uma máquina com um servidor adequado.

Esta abordagem funciona bem para aplicações relativamente pequenas. Entretanto, a arquitetura monolítica torna-se inadequada para aplicações complexas. Uma grande aplicação monolítica pode dificultar o entendimento e a manutenção para os desenvolvedores. É também um obstáculo constante para implantações. Para implantar as mudanças de um componente, é preciso fazer o build e implantar a aplicação inteira, atividade que pode ser complexa, arriscada, consome tempo, requer a coordenação de muitos desenvolvedores e resulta em longos ciclos de teste.

A arquitetura monolítica também torna difícil testar e adotar novas tecnologias. É difícil, por exemplo, testar um novo framework infraestrutural sem reescrever toda a aplicação, o que é arriscado e impraticável. Consequentemente, é comum ficar restrito as escolhas tecnológicas feitas no inicio do projeto. Em outras palavras, a arquitetura monolítica não é adequada para aplicações grandes e duráveis.

Decomposição de aplicações em serviços

Felizmente existem outras arquiteturas. O livro, The Art of Scalability, descreve um modelo de escalabilidade de três dimensões: the scale cube, apresentado na Figura 2.

1Fig2-small.png

Figura 2 - o scale cube

Neste modelo, a abordagem mais comum para escalar uma aplicação é através da execução de múltiplas cópias idênticas da aplicação, acessíveis através de um balanceador de carga. Conhecida como escalabilidade horizontal, corresponde a escalabilidade no eixo X (X-axis scaling), é uma ótima maneira para aumentar a capacidade e disponibilidade de uma aplicação.

A escalabilidade no eixo Z (Z-axis scaling) é similar a do eixo X, onde cada servidor executa uma cópia idêntica do código. A grande diferença é que cada servidor é responsável por seu conjunto dos dados. Essa abordagem exige a presença de um componente responsável pelo roteamento das requisições para um servidor apropriado. Uma maneira de executar o roteamento é através do uso de parâmetros na requisição, por exemplo, a chave primária da entidade solicitada. Essa abordagem é utilizada normalmente em arquiteturas NoSQL que realizam a distribuição de informações através do processo conhecido como sharding. Outra forma de roteamento é por tipo de cliente. Por exemplo, uma aplicação pode fornecer um SLA mais elevado aos clientes pagantes, encaminhando suas requisições a um conjunto de servidores com mais capacidade.

As abordagens Z-axis scaling e X-axis scaling ampliam a capacidade e a disponibilidade da aplicação. Entretanto, nenhuma resolve os problemas do aumento da complexidade do desenvolvimento e da própria aplicação. Para solucionar estes problemas é preciso aplicar a escalabilidade de eixo Y (Y-axis scaling).

A terceira dimensão de escalabilidade é denominada decomposição funcional ou (Y-axis scaling) aplicada sobre o eixo Y. Enquanto a escalabilidade de eixo Z divide elementos semelhantes, a escalabilidade de eixo Y divide elementos diferentes. A camada de aplicação, (Y-axis scaling) divide uma aplicação monolítica em um conjunto de serviços. Cada serviço implementa um conjunto de funcionalidades relacionadas, por exemplo, gerenciamento de pedidos, gerenciamento de clientes etc.

Decidir como particionar um sistema em um conjunto de serviços é uma arte, mas existem estratégias que podem ajudar. Uma abordagem é particionar os serviços por verbos ou casos de uso. Mais adiante no artigo, é apresentado um exemplo onde a loja online, já particionada, tem um serviço para fechamento de pedidos (Chekout UI service), que implementa a interface com o usuário o caso de uso Chekout.

Outra abordagem é o particionamento por sinônimos ou recursos. Este tipo de serviço é responsável por todas as operações sobre entidades/recursos de um determinado tipo. Por exemplo, faz sentido que a loja tenha o serviço Catalog service, que gerencia o catálogo de produtos.

O ideal é que cada serviço possua poucas responsabilidades. (Uncle) Bob Martin fala sobre a modelagem de classes o com Princípio da Responsabilidade Única (Single Responsible Principle). O princípio define que uma classe só deve ter uma razão para ser modificada. Serviços também podem ser modelados com o Princípio da Responsabilidade Única.

Outra analogia que auxilia na modelagem de serviços é o modelo de funcionalidades Unix. Unix oferece várias funcionalidades como grep, cat e find. Cada funcionalidade executa exatamente uma única coisa, a menos que combinada com outra funcionalidade através de shell script para executar tarefas complexas. Faz sentido modelar serviços baseados nas funcionalidades Unix e criar serviços com responsabilidade única.

É importante notar que o objetivo da decomposição não é ter serviços com 10-100 linhas de código. Em vez disso, o objetivo é solucionar os problemas e limitações da arquitetura monolítica descritos anteriormente. Alguns serviços podem muito bem ser pequenos, mas outros serão substancialmente grandes.

A Figura 3 apresenta o resultado da adoção da decomposição Y-axis scaling na aplicação de exemplo.

Fig3-small.png

Figura 3 - arquitetura de microservices

A aplicação é decomposta em diversos serviços de front end, que implementam diferentes partes da interface do usuário e múltiplos serviços de back end. Os serviços de front end são, Catalog UI, que implementa a busca e a navegação de produtos, e o Checkout UI, que implementa o carrinho de compras e o processo de checkout. Os serviços de back end possuem a mesma lógica descrita no inicio do artigo. Cada componente lógico da aplicação foi transformado em um serviço independente (standalone). A seguir, são apresentadas as consequências dessa transformação.

Benefícios e desvantagens da arquitetura de microservices

Esta arquitetura possui muitos benefícios. Primeiramente, cada microservice é relativamente pequeno. O código é facilmente compreendido pelo desenvolvedor. A baixa quantidade de código não torna a IDE lenta, tornando os desenvolvedores mais produtivos. Além disso, cada serviço inicia muito mais rápido que uma grande aplicação monolítica, que novamente, torna os desenvolvedores mais produtivos e agiliza as implantações.

Segundo, cada serviço pode ser implantado independentemente de outros serviços. Caso os desenvolvedores responsáveis por um serviço necessitem implantar uma modificação para um determinado serviço, não há necessidade de coordenação com outros desenvolvedores. Pode-se simplesmente implantar as modificações. A arquitetura de microservices torna viável a implantação contínua.

Terceiro, cada serviço pode ser escalado de forma independente de outros serviços através da duplicação (X-axis scaling) e particionamento (Z-axis scaling). Além disso, cada serviço pode ser implantado em um hardware mais adequado para as exigências de seus recursos. Situação bem diferente da utilização de uma arquitetura monolítica, que possui componentes com diferentes necessidades, por exemplo, uso intenso de CPU x uso intensivo de memória, são juntamente implantados.

A arquitetura de microservices facilita escalar o desenvolvimento. Pode-se organizar o esforço de desenvolvimento em vários times pequenos, por exemplo, two pizza teams. Cada equipe é responsável pelo desenvolvimento e implantação de um único serviço ou um conjunto de serviços relacionados. Cada time pode desenvolver, implantar e escalar seus serviços, independente de todos os outros times.

A arquitetura de microservices também melhora o isolamento de falhas. Por exemplo, um memory leak em um serviço afeta apenas aquele serviço. Outros serviços irão continuar a receber requisições normalmente. Em contrapartida, em uma arquitetura monolítica, um componente com comportamento inadequado irá comprometer todo o sistema.

A arquitetura de microservices elimina qualquer compromisso de longo prazo com a stack de tecnologia. Em princípio, ao desenvolver um novo serviço, os desenvolvedores são livres para escolher qualquer framework e linguagem adequados para aquele serviço. Embora, em muitas organizações as escolhas possam ter restrições, o ponto principal é a independência sobre as decisões tomadas anteriormente.

Além disso, devido aos serviços serem pequenos, torna-se prático reescrevê-los usando melhores linguagens e tecnologias. Caso ocorra uma falha ao experimentar uma nova tecnologia , pode-se descartar o trabalho sem afetar todo o projeto. Situação bem diferente da utilização de uma arquitetura monolítica, onde as escolhas tecnológicas iniciais restringem severamente a possibilidade de adotar diferentes linguagens e frameworks no futuro.

Desvantagens

É claro que nenhuma tecnologia é uma bala de prata, e a arquitetura de microservices possui uma série de desvantagens e problemas significativos. Primeiramente, os desenvolvedores devem lidar com a complexidade adicional de desenvolvimento de sistemas distribuídos. Os desenvolvedores devem implementar um mecanismo de comunicação entre processos. A implementação de casos de uso que abrangem vários serviços sem o uso de transações distribuídas é difícil. IDEs e outras ferramentas de desenvolvimento tem o foco na construção de aplicações monolíticas e não oferecem suporte direto para o desenvolvimento de aplicações distribuídas. Escrever testes automatizados para vários serviços é um desafio. Estes problemas não existem em uma arquitetura monolítica.

A arquitetura de microservices introduz uma complexidade operacional significativa. Existem muito mais elementos (múltiplas instancias de diferentes serviços) que devem ser gerenciados em produção. Para alcançar o sucesso necessita-se de um alto nível de automação, seja por código desenvolvido pela própria equipe ou tecnologias PaaS como Netflix Asgard e componentes relacionados, ou tecnologias personalizados como Pivotal Cloud Foundry.

Além disso, a implantação de funcionalidades que abrangem vários serviços requer uma coordenação cuidadosa entre as várias equipes de desenvolvimento. É preciso criar um plano ordenado de implantações de serviços com base nas dependências entre serviços. Entretanto, implantar atualizações em uma arquitetura monolítica é mais simples, pois é executado de forma atômica, onde apenas um artefato precisa ser implantado.

Outro desafio da arquitetura de microservices é decidir qual o momento do ciclo de vida da aplicação deve-se adotar esta arquitetura. Ao desenvolver a primeira versão de uma aplicação, geralmente os problemas resolvidos pela arquitetura não existem. Entretanto, o uso de uma arquitetura distribuída elaborada irá atrasar o desenvolvimento.

Isso pode ser um grande dilema para startups cujo grande desafio é, muitas vezes, evoluir rapidamente o modelo de negócio juntamente com a aplicação. Aplicar a decomposição (Y-axis scaling) pode tornar as iterações mais demoradas. Mais tarde, quando o desafio é a escalabilidade, necessita-se usar a decomposição funcional, porém as dependências podem estar tão emaranhadas que torna difícil decompor a aplicação monolítica em um conjunto de serviços.

Devido a esses problemas, a adoção da arquitetura de microservices não deve ser tratada de forma leviana. No entanto, para aplicações que necessitam de escalabilidade, como uma aplicação web voltada para o consumidor ou aplicação SaaS, geralmente é a escolha certa. Sites conhecidos, tais como eBay[PDF], Amazon.com, Groupon e Gilt evoluíram de uma arquitetura monolítica para uma arquitetura de microservices.

Uma vez apresentados os benefícios e desvantagens, é o momento de analisar algumas questões fundamentais de projeto em uma arquitetura de microservices, começando com mecanismos de comunicação entre microservices e entre os seus clientes.

Mecanismos de comunicação em uma arquitetura de microservices

Em uma arquitetura de microservices, os padrões de comunicação entre clientes e a aplicação, bem como entre os próprios componentes da aplicação, são diferentes de uma aplicação monolítica. Primeiramente, são abordadas as questões de interação entre clientes e os microservices. Em seguida, são abordados os mecanismos de comunicação entre microservices.

Padrão API gateway

Em uma arquitetura monolítica, os clientes da aplicação (navegadores web e aplicações nativas), realizam requisições HTTP para um balanceador de carga que direciona a requisição para uma das N instâncias idênticas da aplicação. Porém, em uma arquitetura de microservices, a aplicação monolítica foi substituída por uma série de serviços. Em razão disso, a questão a ser respondida é: Como os clientes interagem com os microservices?

Uma aplicação cliente móvel nativa, por exemplo, pode realizar requisições HTTP RESTful diretamente para os serviços como ilustrado pela Figura 4.

3Fig4.png

Figura 4 - chamada direta de serviços

A primeira vista pode parecer atraente, entretanto, existe uma incompatibilidade significativa de granularidade entre APIs compostas por serviços individuais e a exigência de dados dos clientes. Por exemplo, exibir uma página web pode exigir chamadas para diversos serviços. A Amazon.com, relata que algumas páginas web realizam chamadas para mais de 100 serviços. Realizar todas essas requisições, mesmo com internet de alta velocidade, seria muito ineficaz, resultando em uma péssima experiência ao usuário.

Uma abordagem muito melhor para os clientes é realizar poucas requisições por página, preferencialmente uma, através de um servidor de front end denominado API gateway, como mostra a Figura 5.

3Fig5.png

Figura 5 - API gateway

A API gateway localiza-se entre os clientes da aplicação e os microservices, e proporciona APIs adaptadas para as necessidades de diferentes clientes. API gateway fornece uma API de granularidade grossa para clientes móveis e uma API com granularidade fina para clientes desktop que utilizam uma rede de alta velocidade. Neste exemplo, os clientes desktop realizam várias requisições para recuperar informações sobre um produto, porém, um cliente móvel realiza uma única requisição.

A API gateway recebe as requisições dos clientes e realiza diversas outras requisições para microservices conectados por redes locais de alta velocidade. A Netflix, por exemplo, relata que cada requisição envolve em média seis serviços de back end. Neste exemplo, as requisições com granularidade fina provenientes de um cliente desktop, são simplesmente proxy para o serviço correspondente, ao passo que cada requisição de granularidade grossa de um cliente móvel é tratada através da agregação de chamadas para vários serviços.

Além de otimizar a comunicação entre clientes e a aplicação, a API gateway encapsula os detalhes dos microservices, permitindo sua evolução sem causar impacto aos clientes. Por exemplo, dois microservices podem ser agrupados em um único serviço. Outro microservice pode ser dividido em dois ou mais serviços. Apenas a API gateway precisa ser atualizada para refletir estas mudanças. Dessa forma, os clientes não são afetados.

Até este ponto, foi apresentado o papel da API gateway como mediador entre a aplicação e seus clientes. A seguir, é apresentado como implementar a comunicação entre microservices.

Mecanismos de comunicação inter-service

Outra grande diferença da arquitetura de microservices é a forma de interação entre os diferentes componentes da aplicação. Em uma aplicação monolítica, componentes comunicam-se através de chamadas de métodos. Mas em uma arquitetura de microservices, diferentes serviços são executados em diferentes processos. Consequentemente, os serviços devem utilizar uma comunicação entre processos (IPC - inter-process communication).

HTTP síncrono

Existem duas principais abordagens para a comunicação entre processos em uma arquitetura de microservices. Uma opção é utilizar mecanismos baseados em HTTP síncrono como REST ou SOAP. Esta opção é simples, conhecida e facilmente gerenciável por firewalls, ou seja, permite a comunicação através da Internet, facilitando a comunicação no formato request/reply. A desvantagem do HTTP é a falta de suporte a outros padrões de comunicação, como publish/subscribe.

Outra limitação é que cliente e servidor devem estar disponíveis simultaneamente, situação difícil de ser garantida, pois os sistemas distribuídos são propensos a falhas parciais. Além disso, um cliente HTTP precisa saber o endereço e a porta do servidor. Embora pareça simples, essas informações podem não estar disponíveis diretamente, especialmente em uma infraestrutura de nuvem que usa auto-scaling onde instâncias de serviço são efêmeras. Essa abordagem exige o uso de um mecanismo de descoberta de serviço. Algumas aplicações usam um registro de serviços como o Apache ZooKeeper ou Netflix Eureka. Em outras aplicações, serviços devem ser registrados com um balanceador de carga, como um ELB interno em um VPC Amazon.

Mensagens Assíncronas

Uma alternativa para o HTTP síncrono é um mecanismo assíncrono baseado em mensagens como um broker de mensagens AMQP. Esta abordagem tem muitos benefícios, pois desacopla os produtores de mensagens dos consumidores. O broker é capaz de enfileirar e manter as mensagens até serem processadas por consumidores. O produtor simplesmente envia a mensagem para o broker, sem a necessidade de um mecanismo de descoberta de serviços. A comunicação baseada em mensagens também suporta uma variedade de padrões de comunicação, incluindo os pedidos de caminho único (one-way requests) e publish-subscribe. Uma desvantagem do uso de mensagens é a necessidade de um broker, que é mais um componente que aumenta a complexidade do sistema. Outra desvantagem é que a comunicação síncrona (request/reply) não se ajusta naturalmente a esse estilo de comunicação.

Existem prós e contras nas duas abordagens. É desejável que as aplicações utilizem a combinação das duas abordagens. Por exemplo, a próxima seção discute como resolver os problemas de gerenciamento de dados que surgem em uma arquitetura distribuída, será apresentado como HTTP e mensageria são usadas em conjunto.

Gerenciamento de dados descentralizados

Uma consequência da decomposição da aplicação em serviços é o particionamento da base de dados. Para garantir o baixo acoplamento, cada serviço tem sua própria base de dados (schema). Além disso, diferentes serviços podem usar diferentes tipos de base de dados, conhecido como arquitetura de persistência poliglota. Por exemplo, um serviço que necessita de transações ACID pode utilizar um banco de dados relacional, enquanto um serviço que manipula uma rede social pode utilizar um bando de dados baseado em grafos. Particionar a base de dados é essencial, mas isso resulta em um novo problema: como lidar com as solicitações que acessam dados pertencentes a vários serviços? A seguir é abordado como manipular as solicitações de leitura e atualização.

Manipulação de requisições de leitura

O exemplo da loja eletrônica define que cada cliente possui um limite de crédito. Quando um cliente tenta fazer um pedido, o sistema deve verificar se a soma de todos os pedidos em aberto não ultrapassa o limite de crédito. A implementação dessa regra de negócio é trivial em uma aplicação monolítica. Entretanto, é muito mais difícil de implementar essa verificação em um sistema onde os clientes (customers) são gerenciados pelo CustomerService e os pedidos são gerenciados por OrderService. De alguma forma, OrderService deve acessar o limite de crédito mantido pelo CustomerService.

Uma solução é OrderService recuperar o limite de crédito através de chamadas remotas RPC (Remote Procedure Call) ao CustomerService. Esta abordagem é simples de implementar e garante que OrderService sempre tem o limite de crédito atualizado. A desvantagem é que ele reduz a disponibilidade, pois CustomerService deve estar em execução para que OrderService realize um pedido, além de aumentar o tempo de resposta, devido a chamada remota adicional.

Outra abordagem é armazenar uma cópia do limite de crédito em OrderService. Esta abordagem elimina a necessidade de uma requisição ao CustomerService, aumenta a disponibilidade e reduz o tempo de resposta. Porém, implica na implementação de um mecanismo de atualização da cópia do limite de crédito quando ocorrer alguma modificação em CustomerService.

Manipulação de requisições de atualização

Manter o limite de crédito atualizado no OrderService é um exemplo comum de problema resultante da manipulação de requisições de atualização de dados de diferentes serviços.

Transações distribuídas

Uma solução é utilizar transações distribuídas. Por exemplo, ao atualizar o limite de crédito do cliente, CustomerService poderia usar uma transação distribuída para atualizar o limite de crédito juntamente com o valor mantido por OrderService. O uso de transações distribuídas garante consistência aos dados. A desvantagem é a redução da disponibilidade do sistema, uma vez que todos os elementos envolvidos devem estar disponíveis para que a transação seja efetivada. Além disso, transações distribuídas estão caindo em desuso, e não são geralmente suportadas nas stacks de software modernas, como REST, bancos de dados NoSQL etc.

Atualizações assíncronas baseadas em eventos

A outra abordagem é usar a replicação assíncrona baseada em eventos. Serviços publicam eventos anunciando que alguns dados foram alterados. Outros serviços que estão inscritos para receber esses eventos, atualizam seus dados. Por exemplo, quando CustomerService atualiza o limite de crédito de um cliente, ele publica um CustomerCreditLimitUpdatedEvent, que contém o ID do cliente e o novo limite de crédito. OrderService recebe esses eventos e atualiza sua cópia do limite de crédito. O fluxo de eventos é mostrado na Figura 6.

Fig6-small.png

Figura 6 - replicação do limite de crédito através de eventos

O grande benefício desta abordagem é o desacoplamento entre produtores e consumidores de eventos. O desacoplamento simplifica o desenvolvimento e aumenta a disponibilidade, em comparação ao uso de transações distribuídas. Caso nenhum consumidor esteja disponível para processar um evento, o broker de mensagens irá enfileirar o evento até que a mensagem possa ser processada. A grande desvantagem desta abordagem é troca de consistência em favor de disponibilidade. A aplicação deve ser escrita de forma a tolerar eventuais inconsistências de dados. Desenvolvedores podem ter que implementar mecanismos de transação com suporte à rollbacks por compensação. Apesar desses inconvenientes, esta é a abordagem preferida para muitas aplicações.

Refatoração de aplicações monolíticas

Infelizmente, nem sempre é possível trabalhar em um projeto totalmente novo (greenfield project). Há uma grande chance de trabalhar em times responsáveis por enormes e assustadoras aplicações monolíticas. A boa noticia é que existem técnicas que podem ser utilizadas para decompor aplicações monolíticas em um conjunto de serviços.

A primeira coisa a ser feita é não tornar o problema ainda maior. Implementações de novas e significativas funcionalidades não devem ser realizadas na aplicação monolítica. Novas funcionalidades devem ser implementadas como serviços independentes, como ilustra a Figura 7. Esta não é uma atividade fácil, pois é necessário desenvolver uma complexa interface (glue code) para realizar a integração entre os serviços e a aplicação monolítica. Este é um bom começo para a decomposição de aplicações monolíticas.

1Fig7.png

Figura 7 - refatorando para um serviço

O próximo passo é a identificação de componentes da aplicação monolítica que serão transformados em serviços coesos e independentes. Bons candidatos para essa transformação são componentes que estão em constante modificação ou que possuem conflitos de recursos, como grande quantidade de dados em memória ou operações intensas de CPU. A camada de apresentação também é uma boa candidata. Pode-se transformar o componente em um serviço e desenvolver uma interface (glue code) para integrar com o resto da aplicação. Embora não seja trivial, esta atividade permite migrar gradativamente para uma arquitetura de microservices.

Sobre o autor

Chris Richardson é desenvolvedor e arquiteto. É Java Champion, estrela do JavaOne e autor do livro POJOs in Action, livro que descreve o desenvolvimento de aplicações enterprise Java com uso de POJOs e frameworks como Spring e Hibernate. Chris também é fundador da original Clound Foundry, o início da plataforma Paas da Amazon EC2. Atua como consultor em organizações que buscam melhorar o desenvolvimento e implantação de aplicações através da utilização de tecnologias como computação em nuvem, microservices e NoSQL. Twitter @crichardson.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT