Imagine ter acesso a informações cruciais em todos os cantos da sua aplicação sem a necessidade de passá-las através de parâmetros de métodos. É exatamente isso que o Ambient Context Pattern oferece. Este padrão de design resolve de forma elegante o desafio do acesso a dados contextuais globais, tornando-se especialmente útil quando certas informações precisam estar ao alcance de várias partes do seu código. É particularmente valioso em situações onde a passagem tradicional de dados pode se tornar um pesadelo de manutenção ou resultar em um código tão verboso que perde sua eficácia.
Compreendendo o Ambient Context Pattern
O Ambient Context Pattern estabelece um mecanismo para compartilhar informações contextuais através de toda a aplicação de forma transparente. Este padrão se destaca em diversos cenários de uso.
No contexto de rastreamento de operações, o padrão facilita a implementação de logging, permitindo o acompanhamento detalhado das operações através de diferentes componentes do sistema.
O gerenciamento do contexto do usuário é outro cenário, oferecendo um meio eficiente de manter informações de autenticação, preferências do usuário e definições de permissões e roles acessíveis de forma consistente em toda a aplicação.
Para o contexto de requisição, o padrão proporciona um gerenciamento de headers HTTP, tokens de segurança e informações de roteamento. No âmbito do contexto de ambiente, ele facilita o controle de configurações de cultura, variáveis de ambiente e feature flags.
Ambient Context Pattern
Vamos ao ponto-chave: o Ambient Context Pattern é centrado em uma classe que armazena o contexto ao longo de toda a requisição. Veja um exemplo prático em C#:
public class AmbientContext
{
private static AsyncLocal<AmbientContext> _current = new AsyncLocal<AmbientContext>();
public static AmbientContext Current
{
get => _current.Value ?? (_current.Value = new AmbientContext());
set => _current.Value = value;
}
public string RequestId { get; set; }
public string UserId { get; set; }
public string Culture { get; set; }
public Dictionary<string, string> Headers { get; set; }
public Dictionary<string, object> Items { get; set; }
public AmbientContext()
{
Headers = new Dictionary<string, string>();
Items = new Dictionary<string, object>();
}
}
A utilização de AsyncLocal é fundamental aqui, garantindo o isolamento correto do contexto entre threads e requisições assíncronas. Isso proporciona uma solução robusta para gerenciar o estado do contexto, sem preocupações de interferência cruzada.
Interface de Acesso ao Contexto
Para promover um acoplamento mais flexível e facilitar testes, podemos implementar uma interface de acesso:
public interface IAmbientContextAccessor
{
AmbientContext Context { get; }
void SetContext(AmbientContext context);
}
public class AmbientContextAccessor : IAmbientContextAccessor
{
public AmbientContext Context => AmbientContext.Current;
public void SetContext(AmbientContext context)
{
AmbientContext.Current = context;
}
}
Integração com o Pipeline HTTP usando um Middleware
Para uma integraçã com o pipeline HTTP, podemos empregar um middleware personalizado. Essa abordagem permite estabelecer um fluxo de processamento padrão, facilitando a gestão do ciclo de vida da requisição. Com isso, o Ambient Context pode ser perfeitamente alinhado com as etapas do pipeline, garantindo que o contexto seja adequadamente inicializado, manipulado e finalizado ao longo da execução da requisição.
public class AmbientContextMiddleware
{
private readonly RequestDelegate _next;
private readonly IAmbientContextAccessor _contextAccessor;
public AmbientContextMiddleware(RequestDelegate next, IAmbientContextAccessor contextAccessor)
{
_next = next;
_contextAccessor = contextAccessor;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var ambientContext = new AmbientContext
{
RequestId = httpContext.TraceIdentifier,
UserId = httpContext.User?.Identity?.Name,
Culture = CultureInfo.CurrentCulture.Name
};
foreach (var header in httpContext.Request.Headers)
{
ambientContext.Headers[header.Key] = header.Value.ToString();
}
_contextAccessor.SetContext(ambientContext);
try
{
await _next(httpContext);
}
finally
{
_contextAccessor.SetContext(null);
}
}
}
Configuração na Aplicação
A integração do Ambient Context Pattern à sua aplicação começa com uma configuração simples durante a inicialização. Com isso, o contexto fica disponível e funcionando corretamente em todo o ciclo de vida da aplicação.
builder.Services.AddSingleton<IAmbientContextAccessor,AmbientContextAccessor>();
Utilização em Serviços
O acesso ao contexto é facilitado pela injeção de dependência em serviços da aplicação, permitindo uma integração transparente com o Ambient Context Pattern.
public class UserService
{
private readonly IAmbientContextAccessor _contextAccessor;
public UserService(IAmbientContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public async Task<UserInfo> GetCurrentUserInfoAsync()
{
var context = _contextAccessor.Context;
return new UserInfo
{
UserId = context.UserId,
Culture = context.Culture,
RequestId = context.RequestId
};
}
}
O HttpContextAccessor no .NET
O acesso ao HttpContext é simplificado pelo HttpContextAccessor, uma implementação do Ambient Context Pattern no ecossistema .NET. Isso resolve o desafio de acessar o HttpContext em locais onde a passagem como parâmetro seria impraticável.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.AspNetCore.Http;
/// <summary>
/// Provides access to the current <see cref="HttpContext"/>, if one is available.
/// </summary>
/// <remarks>
/// This interface should be used with caution. It relies on <see cref="System.Threading.AsyncLocal{T}" /> which can have a negative performance impact on async calls.
/// It also creates a dependency on "ambient state" which can make testing more difficult.
/// </remarks>
public interface IHttpContextAccessor
{
/// <summary>
/// Gets or sets the current <see cref="HttpContext"/>. Returns <see langword="null" /> if there is no active <see cref="HttpContext" />.
/// </summary>
HttpContext? HttpContext { get; set; }
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
namespace Microsoft.AspNetCore.Http;
/// <summary>
/// Provides an implementation of <see cref="IHttpContextAccessor" /> based on the current execution context.
/// </summary>
[DebuggerDisplay("HttpContext = {HttpContext}")]
public class HttpContextAccessor : IHttpContextAccessor
{
private static readonly AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
/// <inheritdoc/>
public HttpContext? HttpContext
{
get
{
return _httpContextCurrent.Value?.Context;
}
set
{
var holder = _httpContextCurrent.Value;
if (holder != null)
{
// Clear current HttpContext trapped in the AsyncLocals, as its done.
holder.Context = null;
}
if (value != null)
{
// Use an object indirection to hold the HttpContext in the AsyncLocal,
// so it can be cleared in all ExecutionContexts when its cleared.
_httpContextCurrent.Value = new HttpContextHolder { Context = value };
}
}
}
private sealed class HttpContextHolder
{
public HttpContext? Context;
}
}
Aspectos Arquiteturais e Considerações
Ao implementar o Ambient Context Pattern, é essencial considerar alguns aspectos arquiteturais chave para garantir um funcionamento eficiente.
Gerenciamento do Ciclo de Vida (Isolamento e Limpeza) - Cada requisição deve ter seu próprio contexto isolado, garantindo processamento independente de dados. Além disso, a limpeza do contexto após o processamento é crucial para manter a integridade da aplicação. O uso de AsyncLocal assegura o isolamento adequado entre threads, fundamental em ambientes altamente concorrentes.
Segurança de Thread e Performance - A implementação garante thread safety, eliminando problemas de compartilhamento de estado, e apresenta um overhead mínimo com o uso de AsyncLocal. Isso permite um desempenho eficiente sem comprometer a segurança entre threads.
Testabilidade Facilitada - A interface IAmbientContextAccessor facilita a testabilidade, permitindo a substituição fácil do contexto em testes unitários. Além disso, o middleware pode ser substituído em testes, e o contexto pode ser manipulado conforme necessário para diferentes cenários de teste.
Recomendações de Uso
A implementação do Ambient Context Pattern deve ser utilizado com moderação, reservando-o para informações verdadeiramente globais e evitando o armazenamento de dados específicos de negócio. A manutenção da simplicidade do contexto é crucial para evitar complexidade desnecessária.
A garantia de thread safety deve ser uma prioridade, utilizando AsyncLocal consistentemente para contextos por requisição e evitando estado mutável compartilhado. Quando possível, a implementação de imutabilidade pode trazer benefícios adicionais de segurança e previsibilidade.
Para facilitar os testes, deve-se fornecer mecanismos para substituir o contexto em testes unitários e manter a capacidade de injetar contextos simulados.
Conclusão
O Ambient Context Pattern, como visto no exemplo do HttpContextAccessor no .NET, é uma ferramenta poderosa se usada corretamente. Para aproveitá-lo ao máximo, é preciso entender seus pontos fortes e fracos.
Para fazer o padrão funcionar de forma apropriada, é simples: encontre o equilíbrio entre facilidade de uso e independência entre partes. Use-o apenas para informações compartilhadas em toda a aplicação e faça com cuidado. O resultado é uma estrutura de aplicação mais simples, sem comprometer a manutenção ou testes.
Lembre-se: antes de usar, faça a conta. Pergunte-se se a simplicidade vale a relação mais próxima entre as partes do sistema. Com uma implementação bem pensada, o Ambient Context Pattern pode transformar sua arquitetura de software, tornando-a mais elegante e fácil de manter.
Top comments (0)