Boas práticas no consumo de API's

api Set 02, 2019

Estou trabalhando bastante com Serviços Cognitivos da Microsoft tanto em aplicações móveis quanto em Web API’s e nesses cenários me deparei com duas situações recorrentes: intermitência na rede ao acessar a API e Limite de Acesso ao Serviço Cognitivo (10 requisições por segundo, dependendo da camada, por exemplo).

Intermitência na rede é algo muito comum. Precisamos prever essas situações quando estamos desenvolvendo para mobile. 3G e 4G no Brasil são sofríveis, mas independente disso o sistema precisa ser inteligente o suficiente para fazer re-tentativas de conexão ao serviço externo (API), e caso exceda todas as tentativas que pelo menos tenha um fallback para retornar dados que foram armazenados no cache, por exemplo.

Pensando em código, nesses cenários o que vem logo à cabeça é a criação de algorítimos desse tipo:

É um código simples, meio estranho, mas que funciona... ainda falta um Fallback maroto, mas dá pro gasto… tem até cache, vejam, só... Porém, será que não existe uma forma melhor de gerenciar esse problema?

Polly quer biscoito...

Imagem mostra um Papagaio colorido com o texto Polly abaixo.
Esse é o logo do projeto Polly... 

Não existe nada mais chato do que Nirvana... digo... do que escrever código para ficar tratando re-tentativas de execução, fallbacks, validação de exceptions, etc.

E como esse tipo de cenário é algo muito comum dentro da aplicação, é de suma importância que o código criado para gerenciar isso seja feito para ser reaproveitado em várias partes do seu sistema.

Porém, não vamos reinventar a roda, certo? É por isso que uso a biblioteca Polly. E recomendo!

via GIPHY

Polly é uma biblioteca .NET Open Source para tratamento de resiliência e de falhas transitórias que permite que os desenvolvedores criem políticas como Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback de maneira fluente e segura para threads.

Essa descrição extraí do repositório do projeto no Gihub: https://github.com/App-vNext/Polly

Se você trabalha com micro-serviços (a nova onda do momento), Retries, Timeouts e Circuit-breakers são policies que você precisa implementar. (Alguma dessas features podem, e até deveriam, ser gerenciadas por um Service Mesh e/ou API Gateway, mas a ideia aqui não é entrar nessa universo. Talvez num futuro podemos falar sobre isso \o/)

O mais legal de tudo isso é que o Polly tem várias outras funcionalidades. Até mecanismo de cache ele disponibiliza.

Tomei a liberdade de copiar, colar e traduzir a descrição das Policies que o Polly suporta, pra você ter ideia de onde aplicar cada uma delas. Porém, recomendo muito ler a documentação aqui.

Nome Cenário Como Funciona
Retry "Talvez seja apena uma intermitência na rede..." Permite configurar retries automáticos.
Circuit-breaker "Segura as pontas ai enquanto o sistema tenta voltar a funcionar" Interrompe o circuito (bloqueia as execuções) por um determinado período, quando uma determinada quantidade de falhas ocorrerem
Timeout "Não temos todo o tempo do mundo" Garante que o request não terá que esperar além do tempo limite.
Bulkhead Isolation "Uma falha não deve afundar o navio inteiro" Restringe as ações controladas a um conjunto de recursos, isolando seu potencial de afetar outras partes do sistema.
Cache "Você já perguntou isso antes" Guarda as informações na primeira requisição, e, caso solicitado, retorna-as
Fallback "Caia de maneira graciosa" Define um valor alternativo a ser retornado (ou ação a ser executada) em caso de falha.
PolicyWrap "Defesa em profundidade" Permite que qualquer uma das políticas acima seja combinada de maneira flexível.

Nesse post, vou mostrar um exemplo prático, que uso em aplicações que desenvolvo e que necessitam desse tipo de funcionalidade e um vídeo que fiz com o Fabrício Veronez do Coders in Rio falando sobre "Resiliência com Polly" na Semana de Boas Práticas que o canal fez. É bem legal, vale a pena assistir os vídeos desse evento.

Pra começar, dê uma olhada no código abaixo.

Antes de tudo, é importante destacar que o foco aqui é na resiliência nas requisições de API's, utilizando Retries. Nesse caso tenho um cenário específico que cada API tem uma configuração específica para as re-tentativas de requisição, baseadas em algumas regras.

Note que tenho uma classe para configurar essas re-tentativas: RetryPolicyConfiguration. Nessa classe informo a quantidade máxima de re-tentativas (RetryCount) e o intervalo entre elas baseada num fator de multiplicação(RetryAttemptFactor). Já explico.

Meu ServiceBase recebe, além da configuração da policy de Retry, um cliente Http.

Uma dica que dou é, toda vez que você criar uma API, crie um SDK para consumo!

Agora, vamos ver mais de perto o método Get:

Perceba que o objeto this._asyncRetryPolicy que é do tipo AsyncPolicy recebe uma Func. Essa func é o processo que será executado de tal forma que, caso falhe (lance uma Exception), a policy aguarde um determinado tempo e refaça a sua execução.

Criação da Policy

Quando criamos nossa policy definimos a quantidade de retries e o tempo entre eles. A variável retryAttempt me diz qual é a tentativa atual - se é a primeira, segunda, terceira, etc. e o que faço é multiplicar esse valor pelo fator que foi informado na classe de configuração (retryPolicyConfiguration.RetryAttemptFactor) para chegar na quantidade de segundos que o Retry precisa aguardar.

Basicamente, numa configuração com RetryCount =3 e RetryAttemptFactor = 2 temos:

Tentativa retryAttempt Fator Tempo em Segundos
Primeira 1 2 (1 * 2) = 2 Segundos
Segunda 2 2 (2 * 2) = 4 Segundos
Terceira 3 2 (3 * 2) = 6 Segundos

Dessa forma, aguardo um tempo considerável para tentar efetuar o request. Se depois de 12 segundos não conseguir resultado... paciência... pelo menos tentei.

Outro ponto interessante é o Handle<Exception>(e⇒this.CanContinue(e)). Toda vez que uma falha ocorre, a policy invoca o método Handle onde é possível capturar a exceção lançada e informar se o processo de retry deve ou não continuar(true/false). Sensacional, não?

Esse é apenas um exemplo de uso, mas temos mais. Caso queria ver como funciona o Timeout e o Circuit-breaker, assista o vídeo abaixo.

Site do Projeto: http://www.thepollyproject.org/

Policy recommendations for Azure Cognitive Services: http://www.thepollyproject.org/2018/03/06/policy-recommendations-for-azure-cognitive-services/

Código Fonte do Polly: https://github.com/App-vNext/Polly

Aplicações .NET mais estáveis com Polly, Health Checks e Application Insights: https://medium.com/@renato.groffe/aplicações-net-mais-estáveis-com-polly-health-checks-e-application-insights-c51e2c71d7cd

Basicamente era isso.

Photo by Michelle Phillips on Unsplash

Angelo Belchior

Cloud Solutions Architect. Microsoft MVP em duas categorias – AI e Developer Technologies