Quando pensamos em processamento agendado ou em tarefas em segundo plano logo pensamos em criar um serviço para rodar em um servidor windows. Isto deixou de ser uma opção quando aplicações web passaram a fazer este serviço. Com o .Net Core Worker tudo passou a ficar ainda mais fácil.
O projeto
Neste artigo vamos criar uma aplicação simples que realiza consultas nos HealthChecks das aplicações e nos avisa quando algo está errado.
Criando o projeto
Vamos executar os comandos para criar a Solution, criar o projeto Worker e anexar o projeto Worker na Solution criada.
dotnet new sln --name HealthCheckSites
dotnet new webapi --name HealthCheckSites
dotnet sln add HealthCheckSites/HealthCheckSites.csproj
A aplicação ficará desta forma depois que excluirmos as classes padrões WeatherForecastController.cs e WeatherForecast.cs.

A classe BackgroundService
Para a execução do evento vamos utilizar a classe BackgroundService. Esta classe é implementada por Microsoft.Extensions.Hosting e herda a interface IHostedService.
A classe BackgroundService possui 4 métodos, o StartAsync, o StopAsync, o ExecuteAsync e o Dispose. Neste nosso exemplo vamos trabalhar somente com o método ExecuteAsync.
A primeira coisa que faremos é criar a nossa classe GetHealthCheck.cs que será nosso Worker chamada GetHealthCheck. Em seguida vamos herdar a classe BackgroundService e na sequência vamos implementar o método ExecuteAsync.
public class GetHealthCheck : BackgroundService
{
private readonly ILogger<GetHealthCheck> _logger;
private readonly IConfiguration _config;
private readonly HttpClient _httpClient;
private readonly List<string> _sites;
public GetHealthCheck(IConfiguration config, ILogger<GetHealthCheck> logger)
{
_logger = logger;
_config = config;
_httpClient = new HttpClient();
_sites = _config.GetSection("HealthChecks").Get<List<string>>();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
foreach (var site in _sites)
{
var result = await _httpClient.GetAsync(site);
if (result.IsSuccessStatusCode)
_logger.LogInformation($"Health check to {site} Ok");
else
_logger.LogError($"Health check error to {site}");
}
await Task.Delay(60000, stoppingToken);
}
}
}
Nesta classe implementamos o método ExecuteAsync com um looping que executará o nosso Worker a cada um minuto, o foreach que buscará todos os sites cadastrados no appsettings.json, o HttpClient que fará a requisição do HealthCheck das aplicações e o Logger que escreverá no console se o HealthCheck foi feito com sucesso ou não.
No lugar de um simples LogInformation ou LogError podemos implementar uma chamada para o Telegram, Teams, Slack, enviar um e-mail, o que quisermos aqui. O importante é comunicar a equipe caso algum HealthCheck apresente algum erro.
Em seguida temos que alterar o Startup.cs para adicionar nossa classe no HostedService:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHostedService<GetHealthCheck>();
}
Após configurarmos tudo basta executar a aplicação para vermos o comportamento do HealthCheck:
dotnet run

No exemplo temos duas urls para verificarmos o HealthCheck. Uma correta e outra propositalmente errada, para vermos o comportamento de um erro na chamada.
Dockerfile
Vamos criar o Dockerfile padrão de uma aplicação .Net Core 3.1 na raiz do nosso repositório.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["src/HealthCheckSites/HealthCheckSites.csproj", "src/HealthCheckSites/"]
RUN dotnet restore "src/HealthCheckSites/HealthCheckSites.csproj"
COPY . .
WORKDIR "/src"
RUN dotnet build "src/HealthCheckSites/HealthCheckSites.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "src/HealthCheckSites/HealthCheckSites.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "HealthCheckSites.dll"]
Com o Dockerfile configurado basta apenas digitarmos os comandos para build e run.
docker build -t health-check-sites .
docker run --rm -d -p 5000:80 --name health-check-sites health-check-sites
Conclusão
Muito cuidado quando for implementar um serviço Worker em uma aplicação. É muito comum hoje fazermos aplicações escaláveis com Docker e Kubernetes, mas o que esquecemos é que se o Worker estiver dentro da sua aplicação de negócio o Worker também vai escalar com a aplicação.
É muito comum ver isto, o Worker escalar e começar a apresentar vários problemas.
Se você precisa de um Worker para controlar o gargalo de um processamento, quando o Worker escalar você terá um problema.
Se você não tratar a idempotência da sua aplicação, quando o Worker escalar você terá um problema.
A melhor forma de evitarmos estes tipos de problema (eu pelo menos faço assim) é sempre criar o Worker como uma aplicação isolada e configurada para não escalar. Assim garanto que não terei estes tipos de problemas.
Os fontes deste artigo podem ser encontrados no meu GitHub.
Interessante! Nem sabia que era possível, se alguém me perguntasse como fazer uma Api executar uma rotina de forma cronológica, indicaria o Azure App logic.
CurtirCurtido por 1 pessoa
Sim, Sidcley.
Também é possível fazer com Azure Funcions ou AWS Lambda. Mas com o Worker, na minha opinião, fica melhor documentado para os outros devs.
Vlw!
CurtirCurtir
Excelente
CurtirCurtido por 1 pessoa
Muito obrigado, Jacqueline.
CurtirCurtir
Excelente! Um item que deve ser observador quando falamos de Microsserviços é a independência de fornecedor, o artigo cobre perfeitamente essa estratégia. Obrigado por compartilhar!
CurtirCurtido por 1 pessoa