No artigo “Microsserviços: Projetando”, falamos sobre como projetar microsserviços e suas aplicações. Abordamos a comunicação entre os microsserviços e iniciamos uma descrição sobre a interação das aplicações com o mundo exterior, isto é, aplicativos clientes, aplicações de terceiros, etc. Nesse post, vamos explorar esse assunto com mais detalhes.
Para que a aplicação possa interagir com o mundo exterior (clientes da aplicação), é necessário expor os serviços que ela oferece. Entretanto, não queremos expor os detalhes de implementação desses serviços. Imagine, então, que a fronteira da aplicação representa uma fachada que separa o mundo exterior das complexidades internas dos microsserviços. Ao mesmo tempo que essa fachada abstrai os detalhes de implementação dos microsserviços e como eles colaboram entre si para implementar uma determinada funcionalidade, ela também é o ponto de interação com os clientes.
É nessa camada que se estabelece a interação entre a aplicação baseada em microsserviços e seus clientes (mundo exterior), provendo acesso aos dados e às funcionalidades oferecidas pelos microsserviços, utilizando o tipo de transporte e o conteúdo apropriado para o consumidor (cliente).
Essa camada da fronteira também pode implementar outras funcionalidades que estão relacionadas a essa interação com os clientes como, por exemplo, autenticação e autorização, balanceamento de carga, estabelecimento de limites para o número de chamadas externas e para volume de tráfego de dados, estabelecimento de políticas de segurança, gerenciamento de cache, além de registros estatísticos e registros de logs referentes às chamadas do mundo externo. Dessa forma, os microsserviços do backend podem tratar apenas de suas responsabilidades, sem se preocupar com a implementação dessas funcionalidades de borda.
Existem três patterns para essa fronteira da aplicação que podem ser escolhidos de acordo com a necessidade de interação e com os tipos dos clientes/consumidores dessa aplicação. Vamos entender como cada pattern funciona.
API Gateway
O API Gateway é uma solução elegante para atender a necessidade de interação da aplicação com seus consumidores, sejam mobile apps, dispositivos de IoT ou outro tipo de cliente (figura 1). Ele abstrai os detalhes de como os microsserviços são implementados e como colaboram entre si e provê um único ponto de entrada para os clientes da aplicação. O API Gateway fica responsável por passar as solicitações dos clientes para os microsserviços da aplicação e transformar ou combinar suas respostas para entregar o resultado de volta aos clientes. Além disso, podem ficar responsáveis por outras funcionalidades relacionadas aos clientes como, por exemplo, autenticação.
Figura 1 – API Gateway com um ou múltiplos clientes
Backends For Frontends – BFF
Suponha que seu API Gateway tenha que atender tanto aplicações para desktop quanto para mobile. As características desses clientes são muito diferentes, tanto em relação à quantidade e ao tipo de dados que exibem quanto ao formato do transporte e o conteúdo que necessitam. Combinar essas características e funcionalidades em uma mesma API pode tornar a implementação desnecessariamente complexa, ou deixa-la inadequada para ambos tipos de clientes.
Uma solução possível é a utilização do pattern BFF. A abordagem desse pattern é prover uma API para cada tipo de cliente com o qual a aplicação precisa interagir (figura 2). Dessa forma, cada API pode ser implementado de forma mais otimizada e responsiva, cuidando das questões específicas do tipo de cliente que atende. Além disso, o BFF tende a ser menor, menos complexo e, provavelmente, mais rápido do que uma API genérica que precisa satisfazer as necessidades de vários tipos de interface.
Figura 2 – Backend for Fronted
O BFF funciona como uma espécie de adaptador, fazendo uma adequação entre os serviços genéricos oferecidos pelos microsserviços e as necessidades específicas de cada cliente.
Um exemplo típico de utilização de Backend For Frontend aparece quando é necessário construir, para a mesma aplicação, dois tipos diferentes de interface de usuário: Web e mobile. Nesse caso, as duas interfaces normalmente apresentam necessidades diferentes no consumo das funcionalidades oferecidas pelos mesmos microsserviços. Tipicamente, o aplicativo mobile precisa de informações resumidas e, muitas vezes, uma única tela exige informações de vários microsserviços diferentes. Ao mesmo tempo, dada a limitação de banda de comunicação típica das redes móveis e a necessidade de economizar bateria, é desejável reduzir o número de chamadas à retaguarda que o aplicativo realiza. Já a interface Web é mais flexível nesses requisitos, além de, comumente, apresentar informações mais detalhadas, não havendo necessidade de economizar banda de comunicação ou de evitar o tráfego de maiores volumes de dados. Nessa situação, e para evitar duplicar a funcionalidade dos microsserviços ou torná-los dependentes dos tipos de clientes que eles atendem o pattern BFF pode ser a melhor solução.
Um aspecto importante do pattern BFF é que ele também pode ser implementado através de microsserviços, e sua interação com os microsserviços de negócios pode se dar utilizando os meios de comunicação de Service Mesh. Ainda, o cenário mais comum é que os BFF específicos também tenham seus serviços disponibilizados para as aplicações cliente através de um API Gateway, que continuará responsável pelas funções de controle de tráfego, estabelecimento de políticas de segurança, etc. A figura 3 exemplifica esse modelo:
Figura 3 –Backend for Frotend e API Gateway
Outra situação onde o uso deste pattern pode ser muito conveniente é quando o uso de uma linguagem diferente para sua implementação é mais apropriado para um determinado tipo de cliente.
Em contrapartida, esta solução não seria necessária quando os clientes, mesmo sendo de tipos diferentes, fazem requisições similares para o backend da aplicação. Nesse caso, bastaria atender esses clientes com uma única API.
Consumer-Driven Gateway
O terceiro pattern que pode ser usado para a construção da fronteira da aplicação baseada em microsservicos é o Consumer-Driven Gateway. A utilização desse pattern está diretamente associada à adoção do GraphQL como um substituto do modelo arquitetural mais utilizado atualmente para a construção de Web Services, que é o padrão REST.
O GraphQL é uma linguagem de consulta e manipulação de dados para APIs. Ela permite que a aplicação cliente defina a estrutura dos dados a serem retornados, eliminando a necessidade de se criarem diferentes APIs para diferentes clientes. Cada cliente define como prefere receber os dados referentes a cada chamada, e também a quantidade de dados a serem retornados.
O modelo GraphQL consiste de uma linguagem de consulta com uma semântica definida de execução, um sistema de tipos, validação estática e introspeção de tipos. A linguagem suporta leitura, escrita e assinatura para posterior recebimento de eventos resultantes da alteração de dados em tempo real.
Embora ofereça flexibilidade e uma grande variedade de recursos, a complexidade de se implementar uma API que suporte as operações previstas pelo GraphQL pode não compensar no caso de serviços mais simples.
Algumas considerações sobre o uso de API Gateway
Para finalizarmos esse post, vamos citar algumas vantagens e desvantagens da utilização de API Gateways.
Vantagens:
- O cliente não precisa se preocupar com os detalhes da implementação dos microsserviços, de suas interações e nem mesmo em localizar esses serviços;
- O número de requisições/roundtrips é menor, já que o cliente interage apenas com o API Gateway e este fica responsável por se comunicar com todos os microsserviços necessários para compor o resultado dessa requisição;
- Provê uma interface otimizada para cada cliente.
Desvantagens:
- Existe um aumento na complexidade do desenvolvimento, uma vez que o API Gateway precisará ser implementado, implantado e gerenciado.
- Pode haver um aumento no tempo de resposta devido a “intermediação” do API Gateway.
Conclusão
Nesse artigo, falamos sobre a fronteira da aplicação e apresentamos algumas soluções para essa camada. Entretanto, para cada aplicação é preciso analisar a complexidade de interação e os tipos de clientes que ela possui para decidir qual é a solução mais adequada para sua fronteira.