Ícone do site Aprenda Golang

Propagação de context

Propagar o mesmo context por toda uma aplicação pode fazer sentido em alguns casos, mas não é uma prática ideal ou recomendada para todas as situações. O context deve ser utilizado cuidadosamente e com objetivos específicos.

Nesse post, vamos explorar os cenários e cuidados ao se utilizar propagação de context.

O package context

O package context é parte da biblioteca padrão do Go e foi introduzido para resolver problemas relacionados ao gerenciamento de deadlines e cancelamentos em goroutines.

Ele fornece uma maneira de passar informações importantes, como limites de tempo e sinais de cancelamento, por meio de chamadas de funções e entre diferentes partes do código.

Para entender um pouco mais sobre esse package, recomendo a leitura dos posts:

Benefícios de propagar o mesmo context

Antes de entendermos os benefícios específicos de propagar o context, é importante destacar o impacto que isso tem na manutenção e escalabilidade de aplicações modernas.

Em sistemas complexos, onde várias operações assíncronas precisam ser coordenadas, o uso adequado do context pode significar a diferença entre um código claro e responsivo e um sistema propenso a erros ou vazamentos de recursos.

Agora, vejamos os principais benefícios que essa prática proporciona.

  1. Cancelamento eficiente: Permite cancelar operações em cascata quando uma requisição é cancelada pelo cliente ou um deadline é atingido.
  2. Gerenciamento de recursos: Facilita o encerramento adequado de goroutines e a liberação de recursos, evitando vazamentos de memória.
  3. Comunicação consistente: Permite passar informações como tokens de autenticação e valores configuráveis de uma parte do código para outra.

Cuidados ao se propagar o mesmo context

Embora o context seja uma ferramenta poderosa, seu uso inadequado pode introduzir problemas, como vazamentos de goroutines, confusão na passagem de valores e cancelamentos indevidos.

Abaixo, vamos ver os principais pontos de atenção ao se propagar context.

Contexto inadequado para determinada operação Se a operação downstream (como uma transação de banco de dados) tiver requisitos diferentes, como um tempo limite maior ou valores distintos, criar um novo context derivado faz mais sentido. Exemplo:

Evitar ropagação excessiva de cancelamentos O cancelamento do context em uma operação de alto nível pode encerrar prematuramente operações downstream que poderiam ser concluídas de forma independente ou reexecutadas. Exemplo: Cancelar uma requisição HTTP de um cliente não deveria, necessariamente, abortar uma operação de escrita no banco de dados que precisa garantir consistência.

Evitar acoplamento indevido Passar o mesmo context para todos os componentes pode criar dependências indesejadas. Cada componente deve receber apenas os valores e configurações necessárias.

Valores desnecessários no contexto Valores armazenados no context de uma camada superior podem ser irrelevantes para outras camadas (como o banco de dados). Isso pode levar a confusão e uso inadequado do context.

    Práticas recomendadas

    Agora que já passamos pelos benefícios e cuidados, para que sua aplicação tire o melhor do package context, vamos olhar algumas boas práticas.

    Evite armazenar context em structs O context é projetado para ser passado explicitamente entre funções e não deve ser armazenado em campos de structs.

    Criar contextos derivados Use funções como context.WithTimeout ou context.WithValue para criar novos contextos baseados no contexto recebido:

    func processRequest(ctx context.Context) error {
        // Timeout mais curto para o banco de dados
        dbCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
        defer cancel()
    
        return executeDatabaseTransaction(dbCtx)
    }

    Delimitar context por responsabilidade Divida o contexto conforme as responsabilidades. Por exemplo:

    Evite depender de valores do context sempre que possível Prefira passar explicitamente os valores necessários para funções e métodos. Use context.WithValue apenas para metadados como IDs de rastreamento, não para dados de negócio.

    Sempre verifique o estado do context Antes de executar tarefas demoradas, valide se o context já foi cancelado.

    func processRequest(ctx context.Context) error {
        for {
            select {
                case <-ctx.Done():
                    fmt.Println("Context foi cancelado:", ctx.Err())
                    return
                default:
                    fmt.Println("Processando...")
                    // ... código
                }
        }
    }

    Documente o uso do context Deixe claro na documentação das funções qual tipo de contexto elas esperam e como ele será usado.

        Conclusão

        Propagar o mesmo context para todas as partes de uma aplicação pode levar a acoplamento excessivo, cancelamentos prematuros e uso inadequado de recursos. Use o context de forma deliberada:

        Essa abordagem mantém o código modular, previsível e mais fácil de manter.

        Se você gostou desse conteúdo, confira nossos planos de assinatura para acesso a conteúdos exclusivos e aprofundados, além de acesso a todos os cursos e imersão.

        Até a próxima!


        Faça parte da comunidade!

        Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.

        Livros Recomendados

        Abaixo listei alguns dos melhores livros que já li sobre GO.

        Sair da versão mobile