gRPC: Alguns pontos de atenção ao modificar contratos .proto

Tony Augusto
7 min readMay 16, 2021

--

O gRPC tem ganhado certa popularidade como alternativa ao uso do REST, inclusive neste artigo você pode acompanhar a criação de um serviço simples passo a passo. Nele (gRPC) temos a figura de um arquivo .proto onde definimos serviços e mensagens, ou seja, este arquivo atua como uma espécie de “contrato” entre as partes comunicantes e como todo contrato a alteração unilateral deve ser feita com cuidado. Hoje quero elencar alguns pontos a serem observados na governança desses contratos.

Cenário

Para facilitar o entendimento do artigo primeiro vamos definir o nosso cenário de exemplo. Imagine que temos 3 participantes:

  1. Um client que envia uma requisição RPC, que no nosso caso será o BloomRPC;
  2. Um serviço chamado create-person que, como o próprio nome diz, cadastrará pessoas na base de dados;
  3. Um serviço chamado create-subscriber que manterá uma base própria que guardará informações de pessoas que ao se cadastrarem decidiram assinar um serviço extra.

O fluxo será o seguinte:

Fluxo do exemplo

Como podemos observar, os nossos serviços estão usando exatamente o mesmo payload e por este motivo nosso time fictício decidiu que ambos poderiam compartilhar a mesma mensagem do arquivo .proto como parâmetro de entrada para dois serviços distintos (CreatePerson e CreateSubscriber) linhas 11 e 15:

Depois de prontos e implantados, ambos os serviços estão funcionando perfeitamente e cada um tem seu arquivo .proto próprio, mas aí vem o seguinte questionamento: o que acontece se eu alterar esse proto em um serviço e não alterar no outro? Vamos analisar alguns impactos.

O código do exemplo pode ser consultado no meu GitHub.

Exemplos de alterações que não causam impactos

Adição de campos a uma mensagem

Suponha que agora o nosso serviço create-person precisa de mais uma informação para o cadastro do cliente, algo como o zipcode (cep). Do outro lado, o serviço de create-subscriber não precisará dessa informação, mas como vimos no tópico de cenário, os serviços CreatePerson (linha 11) e CreateSubscriber (linha 15) compartilham da mesma mensagem PersonRequest (linha 18):

IMPORTANTE: Como você pode observar, cada campo na definição da mensagem possui um número exclusivo. Esses números de campo são usados ​​para identificar seus campos no formato binário da mensagem e não devem ser alterados depois que seu tipo de mensagem estiver em uso. Fonte: Assigning Field Numbers

Ao adicionarmos o campo zipcode no proto do create-person não teremos impacto negativo no serviço create-subscriber, desde que não sejam alterados os números exclusivos dos campos:

Mas o que acontece com o campo “a mais” que será enviado junto com a mensagem PersonRequest e que o create-subscriber não conhece? No nosso cenário ele será um Unknown Field:

Console debug do create-subscriber

Alteração de nomes de mensagens ou campos

Imagine que agora alteramos o nome da mensagem. Por exemplo, PersonRequest agora é PersonMessage (linha 18):

Qual o impacto no nosso create-subscriber que ainda usa a nomenclatura PersonRequest? Sob a perspectiva do gRPC, nenhum!

A codificação protobuf binária é baseada em números de tag, então é isso que você precisa preservar, entretanto, o código gerado pela linguagem específica será alterado e você não usará o mesmo método para acessar o campo. Fonte: https://dzone.com/articles/protobuf-api-contract-guideline

Ou seja, as classes da nossa aplicação Java deverão ser refatoradas caso os nomes mudem, mas se o create-subscriber continuar usando as classes antigas com a nomenclatura anterior, nada precisará ser feito do ponto de vista do funcionamento.

Importante: Alterações de nomes de mensagens ou campos não causam problemas da perspectiva gRPC, mas caso você faça algum tipo de serialização JSON, os nomes podem ser importantes!

Remoção de um campo da mensagem que não esteja em uso

Agora vamos remover o campo zipCode da nossa PersonRequest, qual será o impacto? Desde que não esteja em uso não teremos problemas. Inclusive, caso você tenha que manter diferentes versões de um arquivo .proto, é possível “reservar” o número exclusivo e o nome de um campo removido para que não seja reutilizado acidentalmente e cause problemas de serialização. Para isso basta usar o recurso de Reserved Fields:

Com a adição do reserved nas linhas 9 e 10, caso alguém tente criar novamente uma mensagem com mesmo nome ou mesmo número exclusivo, um erro será informado imediatamente.

Adição de novos serviços ou adição de métodos a serviços existentes

Bom, neste caso é ainda mais simples. A adição de serviços ou métodos não influencia os já existentes, pois os mesmos não estão sendo alterados/excluídos.

Exemplos de alterações que causam impactos

Alteração de números exclusivos (Assigning Field Numbers)

No exemplo de inclusão de novos campos à mensagens existentes vimos que não há quebra de contrato, mas e se alterarmos os números exclusivos dos campos? Imagine que a nossa PersonRequest foi alterada para o seguinte:

Observe que o nosso campo name recebeu o número exclusivo 2 que antes era do email e vice-versa, qual será o impacto no create-subscriber que ainda está seguindo a sequência numérica anterior? Vejamos:

Log create-subscriber

Na imagem podemos observar que o campo name assumiu a posição do email na mensagem, o que fez com que o create-subscriber considerasse um email como sendo um name e vice-versa… Isso é muito problemático e potencialmente difícil de descobrir até que seja tarde, portanto, requer muita atenção.

Alteração de tipo de dado

Agora vamos supor que ao invés da sequência numérica dos campos da mensagem, alteramos o seu tipo. Abaixo o CPF que era StringValue(texto) virou um Int64Value(long):

Neste caso teremos dois problemas, primeiro que valores de CPF que tenham zeros à esquerda deverão ser tratados pois por padrão os zeros serão excluídos e isso não é novidade pra nós desenvolvedores. O segundo problema, esse sim no escopo do gRPC, é a serialização e desserialização do campo CPF que ficará comprometida dado que o create-subscriber o tratará como StringValue e receberá um Int64Value, portanto, o dado não será íntegro.

Alterações/Exclusões de pacote/serviço/métodos em uso

Para entender o quão isso é problemático, basta lembrar que o “gRPC usa o nome do pacote, o nome do serviço e o nome do método para criar a URL” (MICROSOFT), ou seja, ao alterarmos seria o equivalente a alterar uma rota no REST. Para testar vamos alterar o proto do create-person:

Na linha 11 alteramos o pacote, compilamos o projeto e vamos ver o impacto da mudança na comunicação com o create-subscriber que ainda está com o nome do pacote antigo:

Erro retornado para o BloomRPC

Como visto acima, no momento em que o create-person efetuar a chamada para o create-subscriber teremos um erro de “UNIMPLEMENTED” que é basicamente um aviso de que o serviço não está implementado. Observe que no proto não alteramos o pacote (linha 6) que indicamos no option java_package = “br.com.tony.grpc”, dessa forma o projeto create-person compilará normalmente (sem erros de classes não encontradas etc) mesmo após a alteração do pacote que o proto usa para formar a URL, o que pode facilitar que mudanças acidentais passem despercebidas.

O mesmo comportamento anterior, mas com erros diferentes, ocorrerá caso alterássemos o somente os nomes dos serviços e/ou métodos e mantêssemos o pacote correto, a ideia principal aqui é que o pacote que indicamos no package juntamente com o nome dos serviços formará a URL do recurso, então, não devem ser alterados unilateralmente.

Exclusão de mensagens ou campos de mensagens em uso

Imagine que agora ao invés de alterar o pacote ou nome do serviço, nós excluimos a mensagem PersonRequest. Bom, creio que estamos de acordo que isso gerará grandes problemas, pois a estrutura responsável por carregar os dados deixará de existir. Mas para exemplificar faremos uma mudança um pouco menos impactante, vamos remover o campo “email” e manter todo o restante:

O create-subscriber ainda usa o campo “email”, então o que ocorrerá? Vejamos:

Log do create-subscriber

Como visto no log, o email passará a ser vazio, mas os demais campos permanecerão funcionando normalmente pois não alteramos o os números exclusivos.

Considerações

Os exemplos que utilizei são apenas para demonstrar cenários que podem ou não impactar a comunição entre os serviços, alguns deles podem parecer bem óbvios, outros dificilmente ocorreriam no mundo real, mas é sempre bom lembrar que todo cuidado é pouco quando o assunto são integrações!

Até a próxima!

O código de exemplo pode ser consultado no meu GitHub.

Referências:

--

--

Tony Augusto

Desenvolvedor back-end Java e uma pessoa que acredita na propagação do conhecimento