3 Recomendações para Consumir Microsserviços com Segurança e Resiliência

Uma arquitetura baseada em microsseriços exige que prestemos atenção tanto na escrita destes, quanto nos códigos que os consomem.

Neste post, compartilho três recomendações sobre como consumir microsserviços de forma elegante e adequada.

O código-fonte para o exemplo deste post está disponível em meu github.

Se tiver interesse em entender mais sobre microsserviços, recomendo que acesse o Guia de Conteúdo para Microsserviços deste site.

1. Abstraia o interface para seu microsserviço

O acesso a um microsserviço deve ser abstraído da lógica principal de sua aplicação.

 

Minha recomendação é criar interfaces apropriadas para o negócio, isolando o código faz chamadas remotas.

public interface IEchoService
{
    Task Echo(string message,
        CancellationToken cancellationToken = default(CancellationToken)
        );
}

Dessa forma, o código que irá consumir o microsserviço ficará muito mais simples de ler e entender.

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public async Task Echo(EchoViewModel model)
{
    model.Result = await _echoService.Echo(model.Message);
    return View(model);
}

O que temos é uma chamada assíncrona simples. Veja como não há nenhuma menção ao fato de estar sendo feito um request para outro serviço.

Outra vantagem dessa abordagem, é que fica mais simples escrever testes de unidade. Afinal, não estamos “acoplando” nosso código a conexões HTTP.

2. Isole requisições HTTP em um cliente apropriado

Chamadas HTTP para um microsserviço exigem configurações apropriadas de segurança e tratamento de falhas. Recomendo implementar essa lógica uma única vez reaproveitando a implementação sempre que necessário.

Atualmente, venho adotando uma versão adaptada do código de exemplo fornecido pela Microsoft.

public class StandardHttpClient : IHttpClient, IDisposable
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly HttpClient _httpClient;

    public StandardHttpClient(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
        _httpClient = new HttpClient();
    }

    public async Task GetStringAsync(
        string uri, 
        Authorization authorization = default(Authorization),
        CancellationToken cancellationToken = default(CancellationToken)
        )
    {
        var message = new HttpRequestMessage(HttpMethod.Get, uri)
            .CopyAuthorizationHeaderFrom(_httpContextAccessor.HttpContext)
            .Apply(authorization);

        var response = await _httpClient.SendAsync(message, cancellationToken);

        if (response.StatusCode == HttpStatusCode.InternalServerError)
        {
            throw new HttpRequestException();
        }

        return await response.Content.ReadAsStringAsync();
    }

    public async Task PutAsync(
        string uri, 
        T item, string requestId = null, 
        Authorization authorization = default(Authorization),
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var requestMessage = new HttpRequestMessage(HttpMethod.Put, uri)
            .CopyAuthorizationHeaderFrom(_httpContextAccessor.HttpContext)
            .Apply(authorization);
            
        requestMessage.Content = new StringContent(
            JsonConvert.SerializeObject(item), 
            System.Text.Encoding.UTF8, "application/json"
            );
            
        if (requestId != null)
        {
            requestMessage.Headers.Add("x-requestid", requestId);
        }

        var response = await _httpClient.SendAsync(requestMessage, cancellationToken);

        if (response.StatusCode == HttpStatusCode.InternalServerError)
        {
            throw new HttpRequestException();
        }

        return response;
    }

    public void Dispose()
    {
        _httpClient?.Dispose();
    }
}

Chamo especial atenção, nesse código, para a utilização da chave de identificação no PUT (adicionando o cabeçalho x-requestid). É parte da implementação do lado cliente para minha recomendação de como lidar com falhas de conexão.

Quanto a autorização, tento obter do próprio contexto sempre que possível.

public static HttpRequestMessage CopyAuthorizationHeaderFrom(
    this HttpRequestMessage request,
    HttpContext context
)
{
    var authorizationHeader = context
        .Request
        .Headers["Authorization"];

    if (!string.IsNullOrEmpty(authorizationHeader))
    {
        request.Headers.Add("Authorization", new string[] { authorizationHeader });
    }

    return request;
}

Se não estiver disponível, dou opção para o código consumidor prover a informação de autenticação apropriada.

public struct Authorization
{
    public string Token { get; }
    public string Method { get; }

    public Authorization(
        string token = null,
        string method = "Bearer"
    )
    {
        Token = token;
        Method = method;
    }

    public static readonly Authorization Empty = new Authorization();

    public static implicit operator Authorization(string token) =>
        new Authorization(token);

    public bool IsEmpty => Token == null;
}

3. Implemente os padrões Retry e Circuit Breaker de forma transparente

Como destaquei em um post anterior, no lado cliente (microsserviço ou aplicativo que está fazendo uma requisição) é fundamental implementar:

  • estratégias de proteção ao servidor (microsserviço que está sendo consumido), como a implementação do padrão Circuit Breaker
  • estretégias de repetição de requisição, como a implementação do padrão Retry

Tenho feito essa implementação com um “adapter” para o cliente Http que suporta políticas do Polly. Mais uma vez, é importante destacar que esta é uma implementação adaptada de um código de exemplo fornecido pela Microsoft.

public delegate IEnumerable PolicyFactory(string origin);

public class ResilientHttpClient : IHttpClient
{
    private readonly PolicyFactory _policyFactory;
    private readonly StandardHttpClient _standardHttpClient;

    private readonly ConcurrentDictionary<string, PolicyWrap> _policyWrappers =
        new ConcurrentDictionary<string, PolicyWrap>();

    public ResilientHttpClient(
        PolicyFactory policyFactory,
        IHttpContextAccessor accessor
        )
    {
        _policyFactory = policyFactory;
        _standardHttpClient = new StandardHttpClient(accessor);

    }

    public Task GetStringAsync(string uri, Authorization authorization = default(Authorization),
        CancellationToken cancellationToken = default(CancellationToken)) => HttpInvoker(
        new Uri(uri).GetOriginFromUri(),
        () => _standardHttpClient.GetStringAsync(
            uri, authorization, cancellationToken
        )
    );

    public Task PutAsync(string uri, T item, string requestId = null, Authorization authorization = default(Authorization),
        CancellationToken cancellationToken = default(CancellationToken)) => HttpInvoker(
        new Uri(uri).GetOriginFromUri(),
        () => _standardHttpClient.PutAsync(
            uri, item, requestId, authorization, cancellationToken
        )
    );
        
    private async Task HttpInvoker(string origin, Func<Task> action)
    {
        var normalizedOrigin = origin?.Trim()?.ToLower();

        if (!_policyWrappers.TryGetValue(normalizedOrigin, out var policyWrap))
        {
            var policies = _policyFactory(normalizedOrigin).ToArray();
            policyWrap = Policy.WrapAsync(policies);
            _policyWrappers.TryAdd(normalizedOrigin, policyWrap);
        }

        return await policyWrap.ExecuteAsync(action, new Context(normalizedOrigin));
    }
}

public static class UriExtensions
{
    public static string GetOriginFromUri(this Uri uri) =>
        $"{uri.Scheme}://{uri.DnsSafeHost}:{uri.Port}";
}

Repare que tanto o Retry quanto o Circuit Breaker são injetados através da especificação de um Delegate.

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.AddSingleton<IHttpClient, StandardHttpClient>();
services.AddSingleton(sp => (PolicyFactory)((origin) =>
{
    var logger = sp.GetRequiredService<ILogger>();
    return new Policy[]
    {
        Policy.Handle()
            .WaitAndRetryAsync<HttpRequestException>(
                // number of retries
                6,
                // exponential backofff
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                // on retry
                (exception, timeSpan, retryCount, context) =>
                {
                    var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " +
                                $"of {context.PolicyKey} " +
                                $"at {context.ExecutionKey}, " +
                                $"due to: {exception}.";
                    logger.LogWarning(msg);
                    logger.LogDebug(msg);
                }),
        Policy.Handle()
            .CircuitBreakerAsync<HttpRequestException>( 
                // number of exceptions before breaking circuit
                5,
                // time circuit opened before retry
                TimeSpan.FromMinutes(1),
                (exception, duration) =>
                {
                    // on circuit opened
                    logger.LogTrace("Circuit breaker opened");
                },
                () =>
                {
                    // on circuit closed
                    logger.LogTrace("Circuit breaker reset");
                })
    };
}));
services.AddSingleton<IHttpClient, ResilientHttpClient>();

Concluindo

A escrita de código que consome microsserviços não é necessariamente difícil. Mas, precisa ser cuidadosa para evitar excessiva complexidade acidental. Minha abordagem, tem sido construir abstrações e implementar meu código através dessas abstrações.

  • Abstraindo o acesso ao microsserviço em uma classe de serviço com interface amigável
  • Abstraindo complexidades de acesso Http através de um cliente especializado com customizações e padrões que estou adotando
  • Abstraindo implementação de padrões de resiliência através do uso de uma biblioteca apropriada.

Comentários?
Capa: Nicolas Picard

Compartilhe este insight:

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

In this post, I will show how to do your first steps with OpenCV quickly using Visual Studio 2017 and...
Poucas empresas foram tão impactadas por inovações disruptivas quanto a Encyclopaedia Britannica – empresa com mais de 250 anos. Entretanto,...
It’s time to start learning RavenDB 4. Even if you know previous versions of RavenDB, you will probably get good...
The example which motivated this post comes from the excellent book Designing Distributed Systems by Brendan Burns (co-founder of the...
Uma ou duas vezes por ano tenho a oportunidade de encontrar, pessoalmente, o Ayende (líder técnico do projeto do RavenDB)....
That is a question that I have been answering for years. The answer is an emphatic “NO” in most cases....

Curso Reputação e Marketing Pessoal

Masterclasses

01

Introdução do curso

02

Por que sua “reputação” é importante?

03

Como você se apresenta?

04

Como você apresenta suas ideias?

05

Como usar Storytelling?

06

Você tem uma dor? Eu tenho o alívio!

07

Escrita efetiva para não escritores

08

Como aumentar (e manter) sua audiência?

09

Gatilhos! Gatilhos!

10

Triple Threat: Domine Produto, Embalagem e Distribuição

11

Estratégias Vencedoras: Desbloqueie o Poder da Teoria dos Jogos

12

Análise SWOT de sua marca pessoal

13

Soterrado por informações? Aprenda a fazer gestão do conhecimento pessoal, do jeito certo

14

Vendo além do óbvio com a Pentad de Burkle

15

Construindo Reputação através de Métricas: A Arte de Alinhar Expectativas com Lag e Lead Measures

16

A Tríade da Liderança: Navegando entre Líder, Liderado e Contexto no Mundo do Marketing Pessoal

17

Análise PESTEL para Marketing Pessoal

18

Canvas de Proposta de Valor para Marca Pessoal

19

Método OKR para Objetivos Pessoais

20

Análise de Competências de Gallup

21

Feedback 360 Graus para Autoavaliação

22

Modelo de Cinco Forças de Porter

23

Estratégia Blue Ocean para Diferenciação Pessoal

24

Análise de Tendências para Previsão de Mercado

25

Design Thinking para Inovação Pessoal

26

Metodologia Agile para Desenvolvimento Pessoal

27

Análise de Redes Sociais para Ampliar Conexões

Lições complementares

28

Apresentando-se do Jeito Certo

29

O mercado remunera raridade? Como evidenciar a sua?

30

O que pode estar te impedindo de ter sucesso

Recomendações de Leituras

31

Aprendendo a qualificar sua reputação do jeito certo

32

Quem é você?

33

Qual a sua “IDEIA”?

34

StoryTelling

35

Você tem uma dor? Eu tenho o alívio!

36

Escrita efetiva para não escritores

37

Gatilhos!

38

Triple Threat: Domine Produto, Embalagem e Distribuição

39

Estratégias Vencedoras: Desbloqueie o Poder da Teoria do Jogos

40

Análise SWOT de sua marca pessoal

Inscrição realizada com sucesso!

No dia da masterclass você receberá um e-mail com um link para acompanhar a aula ao vivo. Até lá!

A sua subscrição foi enviada com sucesso!

Aguarde, em breve entraremos em contato com você para lhe fornecer mais informações sobre como participar da mentoria.

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano anual do Clube de Estudos:

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano mensal do Clube de Estudos:

× Precisa de ajuda?