Lidando com falhas de conexão em microsserviços

Um erro imperdoável, na implementação de microsserviços é considerar que a conexão é estável e confiável. Por razões variadas, a rede pode falhar tanto ao enviar uma requisição quanto ao trazer uma resposta.

No lado cliente (microsserviço que está fazendo uma requisição) é fundamental implementar:

  • estratégias de proteção ao servidor, 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

No lado servidor (microsserviço que está atendendo uma requisição) é indispensável garantir que todas as requisições sejam idempotentes. Em cenários onde isso não for absolutamente possível, é fundamental garantir que o “estado” do servidor não seja comprometido.

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

O problema com POST

O verbo POST é, por definição, não idempotente.

Considere o seguinte cenário:

MOMENTO 1 – Uma aplicação cliente chama um microsserviço para realizar a inclusão de uma informação. Esta chamada está disponível através de uma operação POST.

MOMENTO 2 – A aplicação servidor recebe a requisição e executa o processamento de forma adequada. Entretanto, a mensagem de resposta acaba se perdendo na rede e, por alguma razão, não chega a aplicação cliente.

MOMENTO 3 – Depois de algum tempo a aplicação cliente identifica um time-out. E, por definição, faz uma nova tentativa de inclusão de informação.

MOMENTO 4 – A aplicação servidor recebe a nova requisição e, inadvertidamente, inclui a mesma informação, uma segunda vez, na base.

Assustador, não acha? Esse é um caso extremamente difícil de detectar e raramente é “pego” em testes de QA.

Uma possível solução

De forma radical, o cenário acima me faz evitar utilizar o verbo post em minhas aplicações. Acabo utilizando um “PUT identificado” (essa solução, aliás vem sendo bastante aplicada estando presente, inclusive em uma aplicação de referência da Microsoft).

[HttpPut]
[ProducesResponseType(typeof(string), (int) HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task GetEcho(
    [FromBody] EchoCommand command,
    [FromHeader(Name = "x-requestid")] string requestId
)
{
    if (!Guid.TryParse(requestId, out var guid))
    {
        return BadRequest();
    }

    var identifiedCommand = new IdentifiedCommand<EchoCommand, string>(
        command,
        guid
    );

    return Ok(await _mediator.Send(identifiedCommand));
}

O caminho que venho adotando é pedir, em operações que mudam o estado do servidor, um identificador da requisição. Este identificador é então ligado ao comando.

A aplicação cliente precisará adicionar um header para identificar a requisição.

 

Um handler global para comandos identificados checa se o identificador está associado a um comando já executado. Caso esteja, gera um erro.

public class IdentifiedCommandHandler<TCommand, TResult>
    : IRequestHandler<IdentifiedCommand<TCommand, TResult>, TResult>
    where TCommand : IRequest
{
    private readonly IMediator _mediator;
    private readonly IRequestManager _requestManager;

    public IdentifiedCommandHandler(IMediator mediator, IRequestManager requestManager)
    {
        _mediator = mediator;
        _requestManager = requestManager;
    }

    public async Task Handle(
        IdentifiedCommand<TCommand, TResult> message,
        CancellationToken cancellationToken = default(CancellationToken)
        )
    {
        if (message.Id == Guid.Empty)
        {
            ThrowMediatrPipelineException.IdentifiedCommandWithoutId();
        }

        if (message.Command == null)
        {
            ThrowMediatrPipelineException.IdentifiedCommandWithoutInnerCommand();
        }

        var alreadyRegistered = await _requestManager.IsRegistered(message.Id, cancellationToken);
        if (alreadyRegistered)
        {
            ThrowMediatrPipelineException.CommandWasAlreadyExecuted();
        }

        await _requestManager.Register(message.Id, cancellationToken);
        var result = await _mediator.Send(message.Command, cancellationToken);
        return result;
    }
}

Todas as exceptions relacionadas a uma falha por duplicidade são mapeadas adequadamente para um Bad Request.

public class HttpGlobalExceptionFilter : IExceptionFilter
{
    private readonly IHostingEnvironment _env;
    private readonly ILogger _logger;

    public HttpGlobalExceptionFilter(
        IHostingEnvironment env,
        ILogger logger
        )
    {
        _env = env;
        _logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
        _logger.LogError(new EventId(context.Exception.HResult),
            context.Exception,
            context.Exception.Message);

        if (context.Exception.GetType() == typeof(MediatrPipelineException))
        {
            var validationException = context.Exception.InnerException as ValidationException;
            if (validationException != null)
            {
                var json = new JsonErrorResponse
                {
                    Messages = validationException.Errors
                        .Select(e => e.ErrorMessage)
                        .ToArray()
                };

                context.Result = new BadRequestObjectResult(json);
            }
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
        }
        else
        {
            var json = new JsonErrorResponse
            {
                Messages = new[]
                {
                    "Internal Error. Try again later.",
                    context.Exception.GetType().ToString(),
                    context.Exception.Message
                }
            };

            context.Result = new ObjectResult(json) { StatusCode = 500 };
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        }
        context.ExceptionHandled = true;
    }

    public class JsonErrorResponse
    {
        public string[] Messages { get; set; }
    }
}

Que geram um Bad Request para o cliente.

Concluindo

A solução perfeita entregaria idempotência. Mas, para isso, eu precisaria armazenar a resposta do servidor e isso poderia conduzir a erros de interpretação. Aqui, opto por deixar claro o que houve de errado.

Mais uma vez, é importante ressaltar que, implementando microsserviços, é fundamental estar preparado para instabilidades de conexão. A questão não é se ocorrerá uma falha, mas quando essa falha irá ocorrer.

O código fonte desse exemplo está disponível no github.

Créditos da imagem da capa para Park Troopers

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:

O banco onde sou correntista está demorando mais para processar recebimentos do que o usual. Ao conversar com meu gerente,...
I worked a lot in the last months updating the RavenDB bootcamp to v4.x. My work is done (for a...
In a previous post, I wrote about how to support sagas using a Workflow Engine as Saga Execution Coordinator (if...
Pimenta nos olhos dos outros é refresco. Quando não é pela satisfação sádica proporcionada pela dor alheia é pelo alívio...
Expressões regulares são fantásticas. Entretanto, precisam ser utilizadas com moderação pois podem impactar de forma perceptível a performance. A expressão...
Uma arquitetura baseada em microsseriços exige que prestemos atenção tanto na escrita destes, quanto nos códigos que os consomem. Neste...

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.

Masterclass

Pare de dar a solução certa para o problema errado

Muita gente boa quebra a cabeça por dias tentando resolver o que não estava quebrado, simplesmente por tentar dar a resposta certa pro problema errado, mas precisa realmente ser assim?

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?