Compartilhando dados em uma Arquitetura de Microsserviços usando GraphQL

Bruno Soares
GetNinjas
Published in
6 min readJul 18, 2017

--

English version here.

Aqui no GetNinjas, passamos por alguns dilemas integrando componentes da nossa Arquitetura de Microsserviços e explorando as opções disponíveis, o GraphQL mostrou-se como uma excelente ferramenta. Vou listar aqui alguns problemas com três maneiras comuns de fazer essas integrações, e no final, sugerir o GraphQL como uma opção melhor para alguns casos.

Estratégias comuns

Estratégia #1 — Compartilhar acesso ao banco de dados

Integrar sistemas através dos seus bancos de dados parece ser tão fácil que nos sentimos seduzidos à fazer isso. Tudo o que temos que fazer é compartilhar acesso ao bancos de dados entre os serviços, portanto, o sistema A poderia acessar o banco de dados do sistema B e assim por diante. Qual é o problema com essa abordagem?

#1.1 Desempenho
Problemas com desempenho no serviço A podem afetar o sistema B e tornam difícil escalá-los de forma independente, já que na verdade eles não são independentes.

#1.2 Regras de negócio duplicadas
Imagine que o sistema possui uma lista de usuários com uma propriedade de status, que indica se o usuário está ativado ou não. Se o serviço B buscar esses usuários diretamente do banco de dados, você terá que filtrar por usuários ativados em dois lugares. Caso essa regra mude, as chances de uma das implementações se tornar desatualizada é alta.

Ok, essa estratégia quebra regras básicas da Arquitetura de Microsserviços, por este motivo não vale a pena continuar analisando seus problemas.

Microservices prefer letting each service manage its own database, either different instances of the same database technology, or entirely different database systems.(Decentralized Data Management)

Estratégia #2 — Sincronizar os dados em um banco de dados único

Se você tiver um data warehouse, talvez seja tentado a usá-lo nessas integrações, mas aqui estão alguns detalhes que você precisa se atentar.

#2.1 Diferentes tipos de fonte de dados
Se você tiver componentes de diferentes tipos de fontes de dados em sua arquitetura, por exemplo, alguns deles usando PostgreSQL, MySQL outros usando MongoDB, Redis, Cassandra, Neo4j… Pode ser difícil sincronizar todos eles em um único banco de dados devido aos diferentes formatos.

#2.2 Racing conditions (Condição de corrida)
A sincronização leva algum tempo para ocorrer, então, quando um serviço tenta acessar informações que não foram sincronizadas ainda, esse serviço utilizará uma versão desatualizada dos dados e você começará a perceber alguns problemas, aparentemente, aleatórios. A solução comum é adicionar algum atraso para solicitar essa informação (na verdade, não é uma solução real por trazer outros problemas).

#2.3 Alterações na estrutura do banco de dados
Quando a estrutura dos dados é alterada, esta alteração será sincronizada, e os outros serviços que dependem do formato antigo quebrarão. Você pode minimizar esse problema escrevendo um teste de integração que é executado após mudanças em cada componente, porém, uma mudança em um serviço não deve afetar o outro (See Componentization via Services).

#2.4 Mudanças no tipo da fonte de dados
Talvez seja necessário alterar o tipo de fonte de dados, por exemplo, de um banco de documento para um relacional (aqui no GetNinjas, tivemos um caso como este). Essa alteração afetará todos os serviços usando esses dados.

#2.5 Informação gerada dinamicamente
Há alguns casos em que a informação não é persistida, e sim gerada antes do uso, como a URL do avatar de um usuário. Este tipo de informação vive dentro da aplicação em locais como Decorators, Presenters, arquivos de configuração, não persistida no banco de dados, e obviamente, não será sincronizada, então, nesta estratégia você terá que duplicar a lógica que gerar a URL.

#2.6 Duplicação de queries comuns
Digamos que você tenha uma consulta que retorna os cinco principais produtos similares, e outros serviços desejam usá-la. Em uma estratégia de banco de dados sincronizados, você precisará duplicar essa consulta. O problema com isso é óbvio, quando a consulta for alterada, você precisará alterar todos os outros serviços também.

#2.7 Formatação de dados
É uma boa prática armazenar os dados em formato bruto e formatá-los ao exibir. Usando uma API para integrar sistemas, você também pode retornar dados formatados e a lógica de formatação não fica espalhada entre todos os serviços. Nesta estratégia você terá que salvar os dados formatados ou reimplementar a lógica.

Sincronizar os bancos de dados em um único lugar pode ser uma boa opção para fins de análise, mas para compartilhar dados entre serviços, nem tanto.

Estratégia #3 — REST APIs

Neste ponto, as coisas começam a melhorar muito. Mas podemos listar alguns problemas.

#3.1 Difícil de reutilizar os endpoints existentes
Provavelmente, cada serviço precisará de um conjunto de dados específicos, adicionar novos campos ao endpoint não afetará apenas todos os serviços que consomem esse endpoint, mas também diminuirá o desempenho da API.

#3.2 Criar endpoints específicos para cada serviço
Parece ser uma boa ideia, mas assim que você começa a adicionar mais e mais endpoints, a API torna-se confusa e o tempo de desenvolvimento também aumenta. A necessidade de um sistema de documentação como Swagger começa a fazer sentido.

Para alguns casos, você pode parar de ler por aqui. REST APIs podem fazer um excelente trabalho integrando serviços, MAS o GraphQL está ai, e entender esta ferramenta pode ser muito útil!

GraphQL como API para alguns serviços

Vou descrever abaixo alguns benefícios do GraphQL sobre o REST.

Estou preparando um post sobre pontos ruins do GraphQL, fique ligado :)

Update 09/19/17: Pain Points of GraphQL

Flexibilidade
Você pode consultar o schema navegando através das relações entre os resources economizando requisições ao servidor.

Desempenho
Retorne apenas o que precisa, e não um resource que contém todos os dados.

Exemplo de consulta/resposta da nova API GraphQL do Github usando a GraphiQL IDE.

Destaques

  • Consulte múltiplos resources ao mesmo tempo em uma única requisição ao servidor (neste caso, user e repository).
  • A consulta corresponde exatamente à resposta. Você não precisa ler a documentação ou executar o pedido para conhecer a estrutura da resposta.
  • Você pode passar argumentos para os campos (como no avatarUrl que recebe size).
  • Consulta à recursos aninhados (como por exemplo em organizations que é filho de user).
  • Paginação pronta para uso (veja Relay Cursor Connections Specification).

Documentação
Documente campo a campo durante o desenvolvimento.

Exemplo da tela de documentação gerada automaticamente pelo GraphiQL IDE (API GraphQL do Github).

Destaques:

  • A descrição dos tipos ou campos aparece no topo da janela de documentação.
  • O sistema de tipos torna a definição do schema mais natural.
  • Você pode marcar campos como deprecated (como o databaseId da Organization).

Desenvolvimento
Evolua todo o esquema da API em vez de apenas um endpoint de uma API REST. Quando você adiciona um campo, ele pode ser usado por outros consumidores.

Versionamento orgânico
Basta adicionar campos quando precisar ou marcá-los como deprecated quando você planeja parar de usá-los.

Monitoramento
Acompanhe o uso de cada campo em sua API, você pode acompanhar quem está usando, o desempenho e o uso de campos deprecated.

É claro que o GraphQL não irá substituir todos os casos que estamos acostumados a resolver com outras tecnologias, há tantas por aí, RESTful, SOAP, Sockets, Protobuf, protocolos binários via UDP, Filas, etc. Conhecer outras opções irá ajudá-lo a alcançar seus objetivos melhor e mais rapidamente.

GraphQL como API Gateway

Ao invés de solicitar informações para um serviço específico, você pode solicitá-las para um API Gateway que abstraia o serviço proprietário dos dados.

Vamos usar uma loja online como exemplo. Os serviços que têm mais informações para expor podem usar GraphQL e os serviços que expõem menos podem usar uma API REST simples.

GraphQL como API Gateway.

Nesta abordagem, não importa se os serviços usam uma API REST ou uma API GraphQL. Na verdade, se o seu serviço é realmente simples, o GraphQL só adicionará complexidade, portanto, uma API REST simples pode ser até melhor.

Conclusão

Integrar serviços por seus bancos de dados não é uma boa opção, uma arquitetura que aproveita o melhor de cada tecnologia deve trazer melhores resultados (como sempre).

Agradecimentos especiais à Cristhiane Almeida, Daniel Tamai e Ion D. Filho, que ajudaram a revisar o rascunho deste post.

Espero que tenha gostado das ideias apresentadas aqui. Se você gostou, considere clicar no ícone 👏 para recomendá-lo aos outros para que eles possam ler também. E sinta-se livre para compartilhar em sua rede social favorita :-)

--

--