Engatinhando do Monolítico ao Microsserviços: Pooling de DbContext's
Introdução
Meu nome é Otávio Villas Boas e estou desenvolvendo um projeto de estudos sobre arquitetura de software, políticas de resiliência, padrões de projeto, padrões de design, entre outros conceitos, em um contexto de negócio relacionado a lojas virtuais (também chamado de ecommerce's). A ideia desse projeto, apesar de ser pessoal, é levantar requisitos, cair em problemas técnicos, de modo na qual eu produza conteúdos sobre tais conceitos técnicos assim como levantar discussões a respeito desses problemas que eu encontrar. Assim, além de estudar eu consigo compartilhar/discutir a experiência e conhecimento que adquiri ao longo do processo.
Tecnologias Utilizadas no Projeto
- .NET 7
- ASP NET CORE
- PostgreeSQL
- Entity Framework Core
Contextualizando
Imaginem um cenário na qual o Ecommerce que estou desenvolvendo tenham grande de quantidade requisições, assim, o acesso ao banco de dados é feito muitas vezes, não apenas uma única vez. Por outro lado, optei por realizar a escolha de um ORM para acesso a dados no banco de dados transacional, no entanto, a cada vez que uma requisição é feita, é feita a instância de um objeto para fazer o traqueamento e mapeamento dos dados em um contexto a nível de aplicação dos dados que estão no banco de dados.
Problema
Acesso simultâneos ao banco de dados em grandes quantidades provocam uma instanciação e configuração de serviços do objeto de mapeamento e assim gerando um gargalo no momento de se realizar transações/consultas/comandos no banco de dados, visto que, muitos objetos com mesmas configurações são criados simultaneamente.
Solução
Adotei uma solução providenciada pelo Entity Framework Core (ORM amplamente utilizado no .NET), na qual posso reutilizar instâncias do objeto responsável por configurar/traquear/mapear em objetos os dados presente no banco de dados já configuradas em outras requisições por meio do Pooling de DbContext's. Assim, ao invés de criar um novo objeto DbContext, o Entity Framework Core por baixo dos panos armazena esse objeto em memória e caso seja necessário reutilizar ele pega de volta a configuração de serviços presente nesse DbContext (presente já na memória).
Veja a seguir a configuração do Entity Framework Core:
public static void ApplyInfrascructureDependenciesConfiguration(this IServiceCollection serviceCollection,
string? npgsqlConnectionString)
{
#region Entity Framework Core Context Configuration
serviceCollection.AddDbContextPool<DataContext>(
optionsAction: p => p.UseNpgsql(
connectionString: npgsqlConnectionString,
npgsqlOptionsAction: p => p.MigrationsAssembly("OVB.Monolithic.Ecommerce.Infrascructure")));
#endregion
}
Alguns Pontos
- A primeira requisição nunca irá ter a mesma performance em relação as próximas.
- Uso de Pooling provoca um aumento do uso da memória ram no servidor.
- O não uso do Pooling provoca um maior números de alocações e desalocações na memória, assim como, um maior nível de utilização do processador.
Discussão
Considerando o que foi apresentado nesse post, o que vocês acham da utilização de pooling para contextos técnicos não funcionais como esse?
Observações: Caso você encontrem algum outro problema ou solução a ser adotada, por favor, deixe nos comentários. A ideia é que sejam levantadas problemas reais (mesmo que verdadeiramente não exista para o contexto atual do projeto).
Cara muito interessante seu post, é legal ver coisas de .NET por aqui.
Eu recentemente fiz uma API Aspnet core 3.1 que recebe de 3 a 4 milhões de requisições por dia e nesse projeto optei por não usar ORM exatamente por não saber como funcionaria por debaixo dos panos.
Dentro das condições de implementação o banco é oracle Exadata, e o servidor é um monstro com balanceamento de carga.
Está na minha lista de estudos NoSQL, escalabilidade, etc. porque agora o problema ta em contabilizar os registos ingeridos.
Ja ouviu falar do Dapper ?