<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paulo Walraven</title>
    <description>The latest articles on DEV Community by Paulo Walraven (@pmraven).</description>
    <link>https://dev.to/pmraven</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3714972%2F58f0710b-2e2f-4e7e-9254-936f2f3eb0df.jpeg</url>
      <title>DEV Community: Paulo Walraven</title>
      <link>https://dev.to/pmraven</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pmraven"/>
    <language>en</language>
    <item>
      <title>Do localhost à produção: deployando Worker Services .NET como Windows Service, container Linux e Docker</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Wed, 24 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/do-localhost-a-producao-deployando-worker-services-net-como-windows-service-container-linux-e-o2f</link>
      <guid>https://dev.to/pmraven/do-localhost-a-producao-deployando-worker-services-net-como-windows-service-container-linux-e-o2f</guid>
      <description>&lt;p&gt;Seu Worker Service roda lindamente com F5. Mas "roda na minha máquina" não processa pedido nenhum às 3h da manhã. Em produção, um worker é uma aplicação de longa duração que precisa &lt;strong&gt;iniciar sozinha, sobreviver a reinícios e se recuperar de falhas&lt;/strong&gt; — sem ninguém apertando botão.&lt;/p&gt;

&lt;p&gt;Neste post você vai ver os três caminhos para colocar um Worker Service .NET em produção — &lt;strong&gt;Windows Service&lt;/strong&gt;, &lt;strong&gt;container Docker&lt;/strong&gt; e &lt;strong&gt;Linux&lt;/strong&gt; — e, mais importante, os requisitos de produção que realmente importam por trás de cada comando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que worker não é API
&lt;/h2&gt;

&lt;p&gt;Antes do "como", vale lembrar o que torna um worker diferente de uma aplicação web. Diferentemente de uma API, um Worker Service &lt;strong&gt;não responde a requisições HTTP&lt;/strong&gt; — ele executa continuamente em background.&lt;/p&gt;

&lt;p&gt;Isso muda os requisitos de hosting. O worker precisa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Iniciar automaticamente&lt;/strong&gt;, sem intervenção manual.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Se recuperar de falhas&lt;/strong&gt; sozinho.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rodar sem supervisão&lt;/strong&gt; o tempo todo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É por isso que &lt;strong&gt;como&lt;/strong&gt; você hospeda o worker importa tanto. Vamos aos três modelos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opção 1: rodando como Windows Service
&lt;/h2&gt;

&lt;p&gt;O .NET torna trivial rodar um worker como Windows Service. Primeiro, adicione uma linha ao builder no &lt;code&gt;program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWindowsService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vai aparecer um erro na hora — falta uma dependência. Instale o pacote NuGet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Microsoft.Extensions.Hosting.WindowsServices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que isso faz é dizer ao host que ele deve &lt;strong&gt;se integrar com a infraestrutura de serviços do Windows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Depois, publique o executável (via CLI ou pela GUI do Visual Studio, em configuração &lt;strong&gt;Release&lt;/strong&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por fim, registre o serviço no Windows com o utilitário &lt;code&gt;SC.exe&lt;/code&gt;, num prompt de comando &lt;strong&gt;como administrador&lt;/strong&gt;, apontando o &lt;code&gt;binPath&lt;/code&gt; para o executável publicado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SC create MyJobProcessor binPath= "C:\caminho\para\MyJobProcessor.exe"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se aparecer &lt;code&gt;SC create service success&lt;/code&gt;, está feito. Abra o &lt;strong&gt;Services&lt;/strong&gt; do Windows, procure por &lt;code&gt;MyJobProcessor&lt;/code&gt; e você pode iniciar, parar, configurar para subir automaticamente no boot ou desabilitar — tudo que se espera de um Windows Service típico.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opção 2: rodando dentro de um container (a abordagem moderna)
&lt;/h2&gt;

&lt;p&gt;Em muitos sistemas hoje — especialmente em ambientes cloud — os workers rodam dentro de &lt;strong&gt;containers&lt;/strong&gt;. É a abordagem que costumo recomendar sempre que possível.&lt;/p&gt;

&lt;p&gt;Você pode escrever o &lt;code&gt;Dockerfile&lt;/code&gt; à mão, mas o Visual Studio tem um atalho ótimo: clique com o botão direito no projeto → &lt;strong&gt;Add&lt;/strong&gt; → &lt;strong&gt;Docker Support&lt;/strong&gt;. Ele gera o &lt;code&gt;Dockerfile&lt;/code&gt; para você (escolha &lt;strong&gt;Linux&lt;/strong&gt; como OS do container, que é o mais comum sob o capô).&lt;/p&gt;

&lt;p&gt;Com o &lt;code&gt;Dockerfile&lt;/code&gt; no lugar, construa a imagem pela CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myjobprocessor &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Dois tropeços comuns aqui: a &lt;strong&gt;tag da imagem deve ser minúscula&lt;/strong&gt; (&lt;code&gt;myjobprocessor&lt;/code&gt;, não &lt;code&gt;MyJobProcessor&lt;/code&gt;) e cuidado com &lt;strong&gt;espaços&lt;/strong&gt; no caminho. Erros nesses dois pontos são fáceis de cometer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E rode o container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run myjobprocessor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você verá os logs do worker chegando no terminal. Se parar o container, a aplicação desliga junto — exatamente o comportamento esperado. A partir daí, você pode publicar a imagem para um registry (Docker Hub, um repositório privado, etc.) e deployá-la onde precisar.&lt;/p&gt;

&lt;p&gt;Por que essa é a abordagem moderna? Containers são &lt;strong&gt;muito escaláveis&lt;/strong&gt; e &lt;strong&gt;cross-platform&lt;/strong&gt;: a mesma imagem roda em Linux, em Windows nativo, ou onde quer que você precise empurrá-la. Essa flexibilidade é uma das principais vantagens dos Worker Services.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que realmente importa: os requisitos de produção
&lt;/h2&gt;

&lt;p&gt;Os comandos são a parte fácil. O que separa "deployado" de "confiável" são três requisitos que você leva de qualquer modelo de hosting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restart e resiliência
&lt;/h3&gt;

&lt;p&gt;Independentemente de como o worker foi hospedado, ele &lt;strong&gt;precisa reiniciar com segurança&lt;/strong&gt;. Isso significa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;não perder o rastro do trabalho&lt;/strong&gt; que estava fazendo,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;não deixar o sistema em estado inconsistente&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É justamente por isso que, num sistema real, você &lt;strong&gt;persiste os jobs e rastreia o estado&lt;/strong&gt; deles — para que um restart no meio do caminho não jogue trabalho fora.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuração por ambiente
&lt;/h3&gt;

&lt;p&gt;Em produção, workers tipicamente rodam em &lt;strong&gt;múltiplos ambientes&lt;/strong&gt;: desenvolvimento, staging e produção. E cada um tem seus próprios valores — connection strings, polling intervals, feature flags.&lt;/p&gt;

&lt;p&gt;A boa notícia: como o Worker Service usa o &lt;strong&gt;mesmo .NET host&lt;/strong&gt; do ASP.NET, a configuração funciona &lt;strong&gt;exatamente igual&lt;/strong&gt;. Você usa &lt;code&gt;appsettings.json&lt;/code&gt;, user secrets ou variáveis de ambiente, sem nenhuma cerimônia extra.&lt;/p&gt;

&lt;h3&gt;
  
  
  Escalabilidade
&lt;/h3&gt;

&lt;p&gt;Por enquanto, focamos em rodar &lt;strong&gt;uma única instância&lt;/strong&gt;. Mas o aprendizado fundamental é que &lt;strong&gt;um worker é um processo independente&lt;/strong&gt; — pode ser deployado e gerenciado separadamente da sua API. Isso abre caminho, mais à frente, para rodar &lt;strong&gt;múltiplos workers&lt;/strong&gt; e distribuir o trabalho entre eles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Levar um Worker Service para produção é menos sobre o comando de deploy e mais sobre a mentalidade: uma vez no ar, o worker vira &lt;strong&gt;parte da infraestrutura&lt;/strong&gt; do seu sistema — espera-se que rode continuamente, se recupere de falhas e se comporte de forma previsível. Windows Service, Docker ou Linux são só o "onde"; restart seguro, configuração por ambiente e independência da API são o "porquê".&lt;/p&gt;

&lt;p&gt;É isso que separa processamento em background de um script solto. Qual modelo de hosting faz sentido para o seu cenário — Windows Service ou container? Se puder containerizar, eu sempre aconselharia. Teste os dois caminhos e veja qual encaixa na sua infra.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>workerservice</category>
      <category>deployment</category>
      <category>docker</category>
    </item>
    <item>
      <title>O que é Arquitetura de Software e por que ela importa</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Tue, 23 Jun 2026 15:32:48 +0000</pubDate>
      <link>https://dev.to/pmraven/o-que-e-arquitetura-de-software-e-por-que-ela-importa-4b4c</link>
      <guid>https://dev.to/pmraven/o-que-e-arquitetura-de-software-e-por-que-ela-importa-4b4c</guid>
      <description>&lt;p&gt;Antes de falar sobre monolitos modulares, microsserviços ou qualquer outro estilo arquitetural, precisamos responder uma pergunta mais fundamental: &lt;strong&gt;o que é arquitetura de software e por que deveríamos nos importar com isso?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se você já participou de uma reunião técnica onde alguém desenhou caixas e setas num quadro branco, você viu arquitetura de software em ação. Mas o conceito vai muito além de diagramas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uma definição simples
&lt;/h2&gt;

&lt;p&gt;Arquitetura de software é a &lt;strong&gt;organização fundamental de um sistema&lt;/strong&gt; — os componentes que o compõem e como eles se relacionam entre si.&lt;/p&gt;

&lt;p&gt;Pense assim: assim como um arquiteto elabora o projeto de um edifício antes de qualquer tijolo ser assentado, um arquiteto de software define a estrutura de um sistema antes (e durante) o desenvolvimento. Esse projeto vai guiar centenas de decisões ao longo da vida do projeto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy78fpoy1e100800yjoki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy78fpoy1e100800yjoki.png" alt="Comparação lado a lado entre a planta baixa de um edifício e um diagrama de componentes de software, ilustrando que ambos seguem a mesma lógica de organização estrutural" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que isso importa na prática
&lt;/h2&gt;

&lt;p&gt;A escolha arquitetural de hoje é a dívida técnica (ou o dividendo técnico) de amanhã. Ela impacta diretamente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custo&lt;/strong&gt; — algumas arquiteturas são muito mais caras de construir e operar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desempenho&lt;/strong&gt; — a estrutura do sistema define seus gargalos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalabilidade&lt;/strong&gt; — crescer 10x é fácil em alguns modelos, impossível em outros&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manutenibilidade&lt;/strong&gt; — o quanto é difícil mudar algo sem quebrar outra coisa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Velocidade de entrega&lt;/strong&gt; — times maiores podem trabalhar em paralelo ou ficam se atrapalhando?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nenhum desses fatores é isolado. Quando você otimiza para um, geralmente está fazendo uma concessão em outro. E é exatamente aí que entra o conceito mais importante da arquitetura de software.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decisão mais ignorada no desenvolvimento de software
&lt;/h2&gt;

&lt;p&gt;Na maioria dos projetos, a arquitetura não é uma decisão explícita. É o resultado acidental de centenas de pequenas decisões tomadas sem um plano maior.&lt;/p&gt;

&lt;p&gt;Um time começa com um projeto simples. As features crescem. O prazo aperta. Atalhos são tomados. Cinco anos depois, você tem um sistema que ninguém quer tocar porque qualquer mudança pode quebrar algo em outro lugar completamente inesperado.&lt;/p&gt;

&lt;p&gt;Esse padrão tem até nome: &lt;strong&gt;big ball of mud&lt;/strong&gt;. E é mais comum do que qualquer um gostaria de admitir.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1w17asfvkz2dnnkl4b9t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1w17asfvkz2dnnkl4b9t.png" alt="Visualização do Big Ball of Mud: uma esfera de fios entrelaçados com módulos como Users, Orders, Products e Payments todos conectados de forma caótica entre si, sem nenhuma estrutura ou hierarquia clara" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Estilos arquiteturais: a caixa de ferramentas
&lt;/h2&gt;

&lt;p&gt;A boa notícia é que você não precisa inventar a roda. Ao longo de décadas, a indústria consolidou alguns &lt;strong&gt;estilos arquiteturais&lt;/strong&gt; que representam abordagens bem entendidas para organizar sistemas de software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monolito padrão&lt;/strong&gt; — toda a aplicação em um único código deployado junto&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsserviços&lt;/strong&gt; — a aplicação dividida em muitos serviços independentes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monolito modular&lt;/strong&gt; — uma base de código única, mas organizada em módulos com fronteiras bem definidas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada um tem suas vantagens, seus custos e os contextos nos quais faz mais sentido. Não existe "o melhor" em abstrato — existe o mais adequado para o seu problema específico.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4e9uvktv5r9nepw9a3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4e9uvktv5r9nepw9a3i.png" alt="Diagrama comparativo dos três estilos arquiteturais em colunas: Monolito Padrão representado como um único bloco sólido, Microsserviços como vários blocos pequenos separados conectados por linhas, e Monolito Modular como um bloco grande com divisórias internas bem definidas" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>braziliandevs</category>
      <category>software</category>
      <category>programming</category>
    </item>
    <item>
      <title>Hosted service na API ou worker service dedicado? Um guia de decisão</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Tue, 23 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/hosted-service-na-api-ou-worker-service-dedicado-um-guia-de-decisao-54m8</link>
      <guid>https://dev.to/pmraven/hosted-service-na-api-ou-worker-service-dedicado-um-guia-de-decisao-54m8</guid>
      <description>&lt;p&gt;Você aprendeu a construir um hosted service. Agora vem a pergunta que ninguém respondeu: &lt;strong&gt;onde ele deve rodar?&lt;/strong&gt; Dentro da sua API ASP.NET Core, ou em um worker service separado? Escolher errado aqui custa caro — ou você degrada o desempenho dos seus endpoints, ou adiciona complexidade de infraestrutura sem necessidade.&lt;/p&gt;

&lt;p&gt;Ao final deste post você vai ter um critério claro para decidir, caso a caso, onde colocar seu processamento em background.&lt;/p&gt;

&lt;h2&gt;
  
  
  O mesmo código roda nos dois lugares
&lt;/h2&gt;

&lt;p&gt;Antes da decisão, um fato que dá liberdade: o &lt;strong&gt;mesmo&lt;/strong&gt; &lt;code&gt;BackgroundService&lt;/code&gt; e o &lt;strong&gt;mesmo&lt;/strong&gt; &lt;code&gt;AddHostedService&amp;lt;T&amp;gt;()&lt;/code&gt; funcionam tanto em um Worker Service quanto em uma API ASP.NET Core.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// funciona idêntico na API e no worker service&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja, a decisão &lt;strong&gt;não é técnica de implementação&lt;/strong&gt; — é uma decisão de arquitetura. Você não está escolhendo APIs diferentes; está escolhendo &lt;strong&gt;onde&lt;/strong&gt; sua carga de trabalho vive e como ela escala.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quando o hosted service na API é a escolha certa
&lt;/h2&gt;

&lt;p&gt;Hosted services dentro da própria aplicação brilham para &lt;strong&gt;trabalho leve em background que pertence à aplicação em si&lt;/strong&gt;. Alguns exemplos típicos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atualizar dados em cache&lt;/strong&gt; periodicamente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fazer polling&lt;/strong&gt; de pequenos datasets.&lt;/li&gt;
&lt;li&gt;Executar &lt;strong&gt;tarefas de manutenção&lt;/strong&gt; rotineiras.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O fio condutor: essas tarefas são &lt;strong&gt;estreitamente acopladas&lt;/strong&gt; à aplicação e &lt;strong&gt;não precisam de escalabilidade independente&lt;/strong&gt;. Elas vivem e morrem com a API, e está tudo bem assim. Colocá-las num serviço separado só adicionaria partes móveis sem benefício.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quando mover para um worker service dedicado
&lt;/h2&gt;

&lt;p&gt;Do outro lado estão as cargas mais pesadas. Para trabalho &lt;strong&gt;de longa duração&lt;/strong&gt; ou &lt;strong&gt;intensivo em recursos&lt;/strong&gt;, frequentemente a melhor abordagem é mover esse processamento para um &lt;strong&gt;worker service separado&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O benefício central é duplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A carga de trabalho em background pode &lt;strong&gt;escalar de forma independente&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Você &lt;strong&gt;evita impactar o desempenho da sua API&lt;/strong&gt; — o background pesado não disputa recursos com quem está atendendo requisições.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se o seu job consome muita CPU/memória ou roda por muito tempo, mantê-lo dentro da API significa que cada pico de processamento pode degradar a latência dos seus endpoints. Separar resolve isso.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgnehe0qxow46epbo6mm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgnehe0qxow46epbo6mm.png" alt="Comparação entre hosted service na API e worker service dedicado por tipo de carga" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A regra prática
&lt;/h2&gt;

&lt;p&gt;Dá para resumir a decisão em uma regra simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tarefa leve e estreitamente acoplada à aplicação?&lt;/strong&gt; → um &lt;strong&gt;hosted service&lt;/strong&gt; dentro da API é mais apropriado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tarefa de longa duração, intensiva em recursos ou que precisa de escalabilidade própria?&lt;/strong&gt; → mova para um &lt;strong&gt;worker service&lt;/strong&gt; dedicado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comece simples. Se o trabalho é leve e nasce junto com a API, não há motivo para criar um serviço separado só por purismo. Mas no momento em que ele começa a pesar, a durar ou a exigir escala própria, esse é o sinal de que chegou a hora de tirá-lo de dentro da API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;A escolha entre hosted service na API e worker service dedicado não é sobre código — é sobre acoplamento e escala. Trabalho leve e acoplado fica na API; trabalho pesado, longo ou que precisa escalar sozinho vai para um worker dedicado. Entender quando usar cada um é o que separa um sistema que aguenta produção de um que trava sob carga.&lt;/p&gt;

&lt;p&gt;Olhe para os hosted services que você tem hoje: algum deles está pesando dentro da sua API e merecia virar um worker próprio? Esse é o próximo passo natural — e é exatamente o que construiremos a seguir: um worker service dedicado para processar jobs fora da aplicação web.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>arquitetura</category>
      <category>hostedservice</category>
      <category>workerservice</category>
    </item>
    <item>
      <title>Quando tirar o background work de dentro da API: o caso a favor dos Worker Services</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Mon, 22 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/quando-tirar-o-background-work-de-dentro-da-api-o-caso-a-favor-dos-worker-services-5d2h</link>
      <guid>https://dev.to/pmraven/quando-tirar-o-background-work-de-dentro-da-api-o-caso-a-favor-dos-worker-services-5d2h</guid>
      <description>&lt;p&gt;Seu endpoint que sempre respondia em 80ms começou a oscilar para 800ms — e ninguém mexeu no código dele. O culpado? Aquele &lt;code&gt;BackgroundService&lt;/code&gt; que você colocou na API mês passado para gerar relatórios. Ele está rodando dentro do &lt;strong&gt;mesmo processo&lt;/strong&gt; que suas requisições, e agora os dois estão brigando pela mesma CPU.&lt;/p&gt;

&lt;p&gt;Esse é um dos erros arquiteturais mais silenciosos do .NET: começar com hosted services dentro da API e só descobrir o problema quando a produção já está degradada. Neste post você vai entender &lt;strong&gt;quando&lt;/strong&gt; o background work deve sair da API e por que Worker Services são a resposta — não pela sintaxe, mas pelos trade-offs reais.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema: hosted services compartilham tudo com a sua API
&lt;/h2&gt;

&lt;p&gt;Hosted services (&lt;code&gt;IHostedService&lt;/code&gt; / &lt;code&gt;BackgroundService&lt;/code&gt;) são ótimos para tarefas que &lt;strong&gt;pertencem à aplicação&lt;/strong&gt; e são leves: limpar um cache, emitir um heartbeat, recarregar uma configuração de tempos em tempos.&lt;/p&gt;

&lt;p&gt;Mas há uma característica fácil de esquecer: &lt;strong&gt;eles rodam dentro do mesmo processo da sua API&lt;/strong&gt;. Isso significa que compartilham:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a mesma &lt;strong&gt;memória&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;os mesmos &lt;strong&gt;recursos de CPU&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;o mesmo &lt;strong&gt;ciclo de vida&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para cargas leves, isso é perfeito. O problema aparece quando o trabalho fica pesado.&lt;/p&gt;

&lt;p&gt;Imagine que seu serviço em background está processando arquivos grandes, gerando relatórios ou chamando múltiplos sistemas externos. Se esse trabalho executa &lt;strong&gt;dentro do processo da API&lt;/strong&gt;, ele está competindo com as requisições que chegam pelos mesmos recursos.&lt;/p&gt;

&lt;p&gt;O resultado é direto: &lt;strong&gt;tempos de resposta mais lentos e performance instável&lt;/strong&gt;. E o pior tipo de instabilidade — aquela que não aparece no código de nenhum endpoint, só na conta de recursos compartilhada.&lt;/p&gt;

&lt;h2&gt;
  
  
  A segunda dor: você só consegue escalar tudo junto
&lt;/h2&gt;

&lt;p&gt;Essa é a parte que mais machuca em sistemas que crescem.&lt;/p&gt;

&lt;p&gt;Se o seu processamento em background vive dentro da API, &lt;strong&gt;a única forma de escalá-lo é escalar a API inteira&lt;/strong&gt;. Precisa de mais poder para processar a fila de jobs? Suba mais instâncias da aplicação web junto — mesmo que o tráfego HTTP nem precise disso.&lt;/p&gt;

&lt;p&gt;Isso é ineficiente por definição. Em muitos sistemas reais, as cargas de background têm um perfil de escala &lt;strong&gt;completamente diferente&lt;/strong&gt; da camada web: elas precisam crescer e encolher de forma independente. Acoplar os dois te força a pagar por capacidade que você não precisa, do lado errado.&lt;/p&gt;

&lt;h2&gt;
  
  
  A terceira dor: uma falha derruba o que não devia
&lt;/h2&gt;

&lt;p&gt;Confiabilidade é o terceiro argumento, e talvez o mais convincente.&lt;/p&gt;

&lt;p&gt;Quando o worker mora dentro da API, o &lt;strong&gt;raio de explosão&lt;/strong&gt; de uma falha é a aplicação inteira:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se o worker quebra, você &lt;strong&gt;não quer&lt;/strong&gt; que a API caia junto — mas, no mesmo processo, esse risco existe.&lt;/li&gt;
&lt;li&gt;Se você precisa &lt;strong&gt;reiniciar os workers&lt;/strong&gt;, não deveria ter que reiniciar toda a aplicação web — mas, acoplado, é exatamente isso que acontece.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Separar o trabalho em background cria uma arquitetura muito mais &lt;strong&gt;resiliente&lt;/strong&gt;: cada parte falha, reinicia e se recupera de forma isolada.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: Worker Services como aplicações separadas
&lt;/h2&gt;

&lt;p&gt;Para resolver esses três problemas — contenção de recursos, escala acoplada e falhas que se propagam — o .NET oferece os &lt;strong&gt;Worker Services&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Um Worker Service é uma &lt;strong&gt;aplicação independente&lt;/strong&gt;, dedicada ao processamento em background. O modelo de programação parece familiar (ainda é um &lt;code&gt;BackgroundService&lt;/code&gt; com seu loop), mas a diferença que importa é estrutural: &lt;strong&gt;é outro processo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Isso destrava exatamente o que faltava:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recursos próprios&lt;/strong&gt; — o worker não disputa CPU/memória com as suas requisições HTTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escala independente&lt;/strong&gt; — você sobe mais instâncias do worker sem tocar na API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ciclo de vida isolado&lt;/strong&gt; — reiniciar ou derrubar o worker não afeta a aplicação web.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O melhor: como o Worker Service usa o mesmo generic host do ASP.NET, tudo que você já sabe — dependency injection, configuração, logging — continua valendo. Você ganha o isolamento sem reaprender a stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist: está na hora de extrair o worker?
&lt;/h2&gt;

&lt;p&gt;Use estes sinais como gatilho para tirar o background work de dentro da API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] O trabalho é &lt;strong&gt;pesado&lt;/strong&gt; (processa arquivos, gera relatórios, chama vários sistemas externos).&lt;/li&gt;
&lt;li&gt;[ ] Os tempos de resposta da API &lt;strong&gt;oscilam&lt;/strong&gt; quando o background está ativo.&lt;/li&gt;
&lt;li&gt;[ ] Você precisa &lt;strong&gt;escalar o processamento&lt;/strong&gt; sem escalar a camada web.&lt;/li&gt;
&lt;li&gt;[ ] Uma falha no processamento &lt;strong&gt;não pode&lt;/strong&gt; colocar a API em risco.&lt;/li&gt;
&lt;li&gt;[ ] Você quer &lt;strong&gt;reiniciar/deployar&lt;/strong&gt; o background sem mexer na aplicação web.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se você marcou dois ou mais, o Worker Service deixou de ser luxo e virou decisão de arquitetura.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Hosted services dentro da API são ótimos — até deixarem de ser. No momento em que o trabalho fica pesado, precisa escalar sozinho ou não pode derrubar a aplicação, o lugar certo para ele é &lt;strong&gt;fora da API&lt;/strong&gt;, em um Worker Service independente.&lt;/p&gt;

&lt;p&gt;Já tem um &lt;code&gt;BackgroundService&lt;/code&gt; rodando dentro da sua API hoje? Rode o checklist acima e veja se ele já não está te custando latência. No próximo post da série, vamos colocar a mão na massa e &lt;strong&gt;criar e estruturar&lt;/strong&gt; o nosso primeiro Worker Service do jeito certo.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>workerservice</category>
      <category>backgroundprocessing</category>
      <category>arquitetura</category>
    </item>
    <item>
      <title>Busy waiting, falhas que derrubam o worker e estado de job: armadilhas ao projetar o loop de processamento</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Fri, 19 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/busy-waiting-falhas-que-derrubam-o-worker-e-estado-de-job-armadilhas-ao-projetar-o-loop-de-3fke</link>
      <guid>https://dev.to/pmraven/busy-waiting-falhas-que-derrubam-o-worker-e-estado-de-job-armadilhas-ao-projetar-o-loop-de-3fke</guid>
      <description>&lt;p&gt;"Pega trabalho, processa, repete." O loop de um Worker Service parece a coisa mais simples do mundo — até ele ir para produção e queimar 100% de CPU sem ter nada para fazer, ou cair inteiro porque &lt;strong&gt;um&lt;/strong&gt; job lançou uma exceção. O loop trivial é uma ilusão.&lt;/p&gt;

&lt;p&gt;Neste post você vai percorrer as três armadilhas clássicas ao projetar esse loop — &lt;strong&gt;busy waiting&lt;/strong&gt;, &lt;strong&gt;falhas mal tratadas&lt;/strong&gt; e &lt;strong&gt;estado de job inconsistente&lt;/strong&gt; — e ver a correção idiomática de cada uma. Spoiler: o lugar do &lt;code&gt;try-catch&lt;/code&gt; e o lugar do decremento do contador fazem toda a diferença.&lt;/p&gt;

&lt;h2&gt;
  
  
  O ponto de partida: o padrão "recuperar → processar → repetir"
&lt;/h2&gt;

&lt;p&gt;Quase todo worker segue o mesmo esqueleto. Ele recupera algum trabalho, processa, e repete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loopInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é frequentemente descrito como a fundação de qualquer sistema de processamento em background. Mas, sozinho, &lt;strong&gt;não é suficiente para produção&lt;/strong&gt;. Vamos ver onde ele quebra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Armadilha 1: busy waiting queimando CPU à toa
&lt;/h2&gt;

&lt;p&gt;O primeiro problema é o &lt;strong&gt;busy waiting&lt;/strong&gt;: quando não há trabalho disponível, o loop continua girando e consumindo CPU desnecessariamente. Você está pagando por ciclos de processador para... processar &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A correção mais simples é só processar quando realmente existe um job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loopInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare: não estamos introduzindo um delay novo — já temos o &lt;code&gt;Task.Delay&lt;/code&gt; do fim do loop. Estamos apenas &lt;strong&gt;evitando processar à toa&lt;/strong&gt; quando não há nada. Sem job, o worker dá sua soneca habitual e volta a checar.&lt;/p&gt;

&lt;h3&gt;
  
  
  A versão melhor: verifique quantos jobs existem
&lt;/h3&gt;

&lt;p&gt;Se você consegue saber &lt;strong&gt;quantos&lt;/strong&gt; jobs estão pendentes, dá para ser mais esperto e processá-los em lote — garantindo que só trabalha quando há trabalho:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pendingJobs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTotalPendingJobs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pendingJobs&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// processa...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contar jobs costuma ser uma operação barata (o tamanho de uma coleção ou de uma fila, por exemplo), então o overhead é baixo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mas atenção ao contexto&lt;/strong&gt; — essa é a nuance que quase ninguém comenta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Múltiplos workers compartilhando a fila:&lt;/strong&gt; use uma checagem condicional simples. Se há 42 jobs e você pega um, &lt;strong&gt;não sabe&lt;/strong&gt; quantos sobraram (outro worker pode ter pegado) — você teria que verificar de novo a cada vez.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Você é o único worker&lt;/strong&gt; (a fila vive dentro do próprio serviço): aí não há risco da contagem mudar por baixo dos panos. Você pode processar em batch até esgotar:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pendingJobs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTotalPendingJobs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pendingJobs&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;pendingJobs&lt;/span&gt;&lt;span class="p"&gt;--;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loopInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ideia: você acorda, processa os 42 jobs, dorme por dois segundos, volta e checa se surgiu mais alguma coisa. Esse modelo te deixa &lt;strong&gt;acelerar&lt;/strong&gt; quando há trabalho acumulado — mas só é seguro se você for o único consumidor daquela fila. Com vários serviços competindo pelo mesmo conjunto centralizado de jobs, &lt;strong&gt;não use&lt;/strong&gt; essa abordagem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Armadilha 2: uma falha que derruba o worker inteiro
&lt;/h2&gt;

&lt;p&gt;Aqui está a regra que você nunca pode violar: &lt;strong&gt;uma única falha no processamento jamais pode derrubar o worker inteiro.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se um job lança uma exceção e você não trata, o loop morre e o processo cai — junto com todos os outros jobs que processariam normalmente. A solução básica é um &lt;code&gt;try-catch&lt;/code&gt; &lt;strong&gt;ao redor do processamento específico&lt;/strong&gt;, registrando a exceção:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pendingJobs&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Falha ao processar o job"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;pendingJobs&lt;/span&gt;&lt;span class="p"&gt;--;&lt;/span&gt; &lt;span class="c1"&gt;// FORA do try — sempre decrementa&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como não há UI em um worker, &lt;strong&gt;o log é a sua única fonte de verdade&lt;/strong&gt;. Capturar a exceção e logá-la é o que te permite ver, depois, que um job específico falhou — enquanto o resto continua rodando.&lt;/p&gt;

&lt;h3&gt;
  
  
  O detalhe fatal: onde fica o decremento
&lt;/h3&gt;

&lt;p&gt;Olhe de novo para o exemplo acima. O &lt;code&gt;pendingJobs--&lt;/code&gt; está &lt;strong&gt;fora&lt;/strong&gt; do &lt;code&gt;try&lt;/code&gt;. Isso é intencional e crítico.&lt;/p&gt;

&lt;p&gt;Se você colocar o decremento &lt;strong&gt;dentro&lt;/strong&gt; do &lt;code&gt;try&lt;/code&gt;, e o job sempre falha, você nunca decrementa — e o loop fica &lt;strong&gt;preso para sempre&lt;/strong&gt; com a mesma contagem, ou o contador sai de sincronia. Por isso: o &lt;code&gt;try&lt;/code&gt; envolve &lt;strong&gt;só o trabalho que você quer proteger contra falha&lt;/strong&gt;; o controle do loop fica de fora. Seja cuidadoso com essa fronteira.&lt;/p&gt;

&lt;h2&gt;
  
  
  Armadilha 3: estado de job inconsistente
&lt;/h2&gt;

&lt;p&gt;A terceira armadilha é mais sutil: conforme processa, o worker precisa &lt;strong&gt;rastrear o estado do job&lt;/strong&gt; — pendente, em progresso, completo, falho.&lt;/p&gt;

&lt;p&gt;O padrão geral é marcar o job ao longo do ciclo de vida:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MarkJobInProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MarkJobComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Falha ao processar o job"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MarkJobFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ideia é simples: recuperou o job → marca como &lt;strong&gt;em progresso&lt;/strong&gt; → processa → marca como &lt;strong&gt;completo&lt;/strong&gt;. Se falhar, marca como &lt;strong&gt;falho&lt;/strong&gt; no &lt;code&gt;catch&lt;/code&gt;, mantendo o estado consistente. Você precisa ser bem cuidadoso ao atualizar esse estado, justamente para que uma falha não deixe um job pendurado em "em progresso" para sempre.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjw7f9889f8j869d9uzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjw7f9889f8j869d9uzr.png" alt="Máquina de estados de um job: pendente, em progresso, completo e falho" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Não esqueça: respeite sempre o cancellation token
&lt;/h2&gt;

&lt;p&gt;Por mais que pareça repetitivo, vale insistir: &lt;strong&gt;sempre respeite o &lt;code&gt;CancellationToken&lt;/code&gt;.&lt;/strong&gt; Poder sinalizar que algo foi cancelado é uma prática crucial em qualquer worker. Repare que ele aparece tanto na condição do &lt;code&gt;while&lt;/code&gt; quanto no &lt;code&gt;Task.Delay&lt;/code&gt; dos exemplos — é assim que o worker desliga de forma graciosa quando a aplicação está parando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;O loop "pega, processa, repete" é enganosamente simples. Em produção, ele exige três cuidados: &lt;strong&gt;não queimar CPU em busy waiting&lt;/strong&gt;, &lt;strong&gt;isolar falhas para que um job ruim não derrube o worker&lt;/strong&gt; e &lt;strong&gt;rastrear o estado do job de forma consistente&lt;/strong&gt; — tudo isso respeitando o cancellation token. A mentalidade certa: um worker não é "só mais um loop em background", é um componente de sistema de longa duração que precisa se comportar de forma &lt;strong&gt;previsível&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Revise o loop do seu worker hoje: o &lt;code&gt;try-catch&lt;/code&gt; está ao redor só do processamento? O decremento está fora dele? Se respondeu "não" para alguma, você já encontrou a próxima refatoração. No próximo post, vamos tirar o worker do localhost e colocá-lo para rodar em produção.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>workerservice</category>
      <category>backgroundprocessing</category>
      <category>resiliencia</category>
    </item>
    <item>
      <title>Worker fino, processador gordo: estruturando Worker Services testáveis</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Thu, 18 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/worker-fino-processador-gordo-estruturando-worker-services-testaveis-3hpm</link>
      <guid>https://dev.to/pmraven/worker-fino-processador-gordo-estruturando-worker-services-testaveis-3hpm</guid>
      <description>&lt;p&gt;Abra um Worker Service de seis meses de idade que ninguém estruturou direito e você vai encontrar a mesma cena: uma classe &lt;code&gt;Worker&lt;/code&gt; de duzentas linhas, com lógica de banco, regra de negócio e tratamento de erro, toda enrolada dentro de um &lt;code&gt;while&lt;/code&gt;. Tente escrever um teste unitário pra isso. Não dá — você teria que subir o host inteiro.&lt;/p&gt;

&lt;p&gt;A diferença entre um worker que vira essa bola de neve e um que se mantém manutenível está em uma decisão simples de design: &lt;strong&gt;manter a classe &lt;code&gt;Worker&lt;/code&gt; fina e empurrar a lógica para um &lt;code&gt;JobProcessor&lt;/code&gt; injetável.&lt;/strong&gt; Neste post você vai ver como essa separação destrava testabilidade e evolução — e por que ela é uma questão de arquitetura, não de configuração.&lt;/p&gt;

&lt;h2&gt;
  
  
  O instinto errado: colocar a lógica no worker
&lt;/h2&gt;

&lt;p&gt;Quando você cria um Worker Service (&lt;code&gt;dotnet new worker&lt;/code&gt;), o template te dá um &lt;code&gt;Worker.cs&lt;/code&gt; com um loop pronto. É tentador começar a escrever ali mesmo: pega o job, valida, processa, salva, repete. Tudo dentro do &lt;code&gt;ExecuteAsync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O problema é que o worker passa a ter &lt;strong&gt;duas responsabilidades misturadas&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Orquestrar&lt;/strong&gt; — manter o loop de pé, controlar o intervalo, respeitar o cancellation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executar&lt;/strong&gt; — a regra concreta de processar cada job.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Misturar essas duas coisas é o que torna o worker impossível de testar e difícil de evoluir. Cada mudança na regra de negócio mexe na mesma classe que controla o ciclo de vida do processo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A regra de ouro: o worker deve ser fino
&lt;/h2&gt;

&lt;p&gt;A mentalidade certa é: &lt;strong&gt;a classe &lt;code&gt;Worker&lt;/code&gt; tem uma responsabilidade muito específica — orquestrar a execução, recuperar o trabalho e delegar o processamento.&lt;/strong&gt; Nada mais.&lt;/p&gt;

&lt;p&gt;Toda a lógica de "o que fazer com o job" mora em outro lugar: um componente separado, injetável, que você registra no DI. Vamos chamá-lo de &lt;code&gt;JobProcessor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Primeiro, extraia a lógica para a sua própria classe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessNextJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// toda a regra de processamento vive aqui&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registre no &lt;code&gt;program.cs&lt;/code&gt; como scoped — porque, na prática, esse processador vai depender de coisas como um &lt;code&gt;DbContext&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E deixe o worker apenas &lt;strong&gt;coordenar e delegar&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceScopeFactory&lt;/span&gt; &lt;span class="n"&gt;serviceScopeFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceScopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare no que o worker faz agora: ele vai no nível mais alto de abstração — &lt;strong&gt;"pega o próximo job e faz algo com ele"&lt;/strong&gt; — e só. É tudo o que essa classe precisa saber. Esse é o padrão que se usa o tempo todo em produção: o worker puxa trabalho de uma fila ou event bus, e delega o resto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que essa separação destrava os testes
&lt;/h2&gt;

&lt;p&gt;Aqui está o ganho concreto. Com a lógica isolada no &lt;code&gt;JobProcessor&lt;/code&gt;, você consegue &lt;strong&gt;testá-lo de forma independente do host&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;JobProcessor&lt;/code&gt; é só uma classe comum, com dependências injetadas. Em um teste unitário, você instancia ela com mocks/fakes das dependências, chama &lt;code&gt;ProcessNextJob()&lt;/code&gt; e verifica o comportamento — &lt;strong&gt;sem subir o generic host, sem loop, sem &lt;code&gt;Task.Delay&lt;/code&gt;, sem cancellation token&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Enquanto isso, o &lt;code&gt;Worker&lt;/code&gt; fica tão fino que quase não há o que testar nele: ele só orquestra. A complexidade que realmente merece cobertura de teste — a regra de negócio — está num lugar onde testar é trivial.&lt;/p&gt;

&lt;p&gt;É exatamente isso que a dependency injection te dá: a capacidade de &lt;strong&gt;separar responsabilidades&lt;/strong&gt; e testar cada componente isoladamente. Como bônus, a mesma lógica do &lt;code&gt;JobProcessor&lt;/code&gt; pode ser reutilizada em outros contextos, porque ela não está presa ao loop do worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  A nuance: não deixe o worker engordar de novo
&lt;/h2&gt;

&lt;p&gt;Conforme o sistema cresce, é fácil o worker começar a inchar de novo — uma condicional aqui, um &lt;code&gt;if (job != null)&lt;/code&gt; ali, um contador de jobs pendentes acolá. Antes que perceba, a classe que devia ser fina virou o centro da lógica outra vez.&lt;/p&gt;

&lt;p&gt;Fique atento a esse drift. Sempre que notar o worker crescendo, pergunte: &lt;strong&gt;isso é orquestração ou execução?&lt;/strong&gt; Se for execução, o lugar é o &lt;code&gt;JobProcessor&lt;/code&gt;. O worker coordena; outro componente realiza o trabalho. Manter essa fronteira nítida é o que torna o sistema mais fácil de testar, estender e evoluir ao longo do tempo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Um Worker Service bem feito não é sobre o loop — é sobre &lt;strong&gt;quem faz o quê&lt;/strong&gt;. Worker fino que orquestra, &lt;code&gt;JobProcessor&lt;/code&gt; que executa: essa única decisão de design separa os workers testáveis e duráveis dos que viram dívida técnica.&lt;/p&gt;

&lt;p&gt;Da próxima vez que for escrever lógica dentro de um &lt;code&gt;ExecuteAsync&lt;/code&gt;, pare e pergunte se aquilo não pertence a um processador injetável. Seu eu do futuro — e a sua suíte de testes — vão agradecer. No próximo post, vamos olhar para dentro desse loop e as armadilhas de projetá-lo para produção.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>workerservice</category>
      <category>arquitetura</category>
      <category>testes</category>
    </item>
    <item>
      <title>Graceful shutdown no .NET: o CancellationToken que você está ignorando vai corromper seus dados</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Wed, 17 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/graceful-shutdown-no-net-o-cancellationtoken-que-voce-esta-ignorando-vai-corromper-seus-dados-413g</link>
      <guid>https://dev.to/pmraven/graceful-shutdown-no-net-o-cancellationtoken-que-voce-esta-ignorando-vai-corromper-seus-dados-413g</guid>
      <description>&lt;p&gt;Seu serviço está no meio de processar um pagamento quando o container é reiniciado. Se você não tratou o cancelamento, parabéns: você acabou de deixar dados em memória que nunca chegaram ao banco — e um registro que ninguém mais consegue confiar.&lt;/p&gt;

&lt;p&gt;Ao final deste post você vai entender por que o &lt;code&gt;CancellationToken&lt;/code&gt; no seu loop de background não é detalhe de estilo, mas uma questão de &lt;strong&gt;integridade de dados&lt;/strong&gt; — e qual é o único padrão correto para respeitá-lo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Em produção, sua aplicação reinicia o tempo todo
&lt;/h2&gt;

&lt;p&gt;Talvez no seu ambiente local a aplicação suba uma vez e fique de pé por horas. Em produção é o oposto. Aplicações são reiniciadas &lt;strong&gt;constantemente&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serviços são atualizados em novos deploys.&lt;/li&gt;
&lt;li&gt;Containers são reimplementados (redeploy).&lt;/li&gt;
&lt;li&gt;Serviços escalam para cima e para baixo conforme a carga.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Toda vez que isso acontece, o trabalho em background precisa parar de um jeito específico: &lt;strong&gt;graciosamente, não abruptamente.&lt;/strong&gt; A diferença entre os dois é onde mora o bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  O CancellationToken é o sinal de "hora de parar"
&lt;/h2&gt;

&lt;p&gt;Quando a aplicação começa a desligar, o host dispara um sinal. Esse sinal chega ao seu hosted service através do &lt;code&gt;CancellationToken&lt;/code&gt; recebido no &lt;code&gt;ExecuteAsync&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// stoppingToken é acionado quando a aplicação começa o shutdown&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O token é &lt;strong&gt;acionado quando a aplicação começa a desligar&lt;/strong&gt;. Ele é o seu aviso prévio. O problema é que respeitá-lo é opcional do ponto de vista do compilador — e é muito fácil simplesmente não usá-lo.&lt;/p&gt;

&lt;h2&gt;
  
  
  O padrão correto (e o erro silencioso)
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;ExecuteAsync&lt;/code&gt; te entrega o token, mas nada te obriga a verificá-lo dentro do loop. Esse é o erro silencioso: escrever um loop que ignora o sinal de shutdown.&lt;/p&gt;

&lt;p&gt;O padrão absolutamente correto é verificar o token na condição do loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// faz o trabalho em background&lt;/span&gt;

        &lt;span class="c1"&gt;// repassa o token para as chamadas async também&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dois pontos que fazem esse padrão funcionar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;while (!stoppingToken.IsCancellationRequested)&lt;/code&gt;&lt;/strong&gt; garante que, no início de cada iteração, o loop verifica se já é hora de parar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repassar o token&lt;/strong&gt; para chamadas assíncronas (como &lt;code&gt;Task.Delay&lt;/code&gt;) faz a espera ser interrompida imediatamente quando o shutdown chega — em vez de segurar a aplicação até o fim do delay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O resultado: a tarefa &lt;strong&gt;para rapidamente&lt;/strong&gt; quando o shutdown começa, em vez de ser morta no meio do caminho.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxttdvuig7ciov3osr7hf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxttdvuig7ciov3osr7hf.png" alt="Comparação entre um loop interrompido abruptamente e um loop que respeita o cancelamento" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que isso importa de verdade
&lt;/h2&gt;

&lt;p&gt;Vale insistir nesse ponto, porque ele é fácil de subestimar. Imagine que seu serviço está &lt;strong&gt;processando pagamentos&lt;/strong&gt; ou &lt;strong&gt;processando documentos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se a aplicação desliga, você está no meio de uma operação e &lt;strong&gt;não tratou o cancelamento&lt;/strong&gt;, o estrago pode ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alguns dados ficaram &lt;strong&gt;em memória&lt;/strong&gt; e nunca foram consolidados no banco.&lt;/li&gt;
&lt;li&gt;Ou os dados no banco mudaram, mas a operação não terminou.&lt;/li&gt;
&lt;li&gt;Resultado: dados &lt;strong&gt;incompletos e não confiáveis&lt;/strong&gt; — você não pode confiar na precisão deles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É exatamente esse tipo de inconsistência entre memória e banco que respeitar o &lt;code&gt;CancellationToken&lt;/code&gt; evita. Não é sobre código bonito; é sobre não corromper o estado do seu sistema toda vez que um deploy acontece.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Em produção o shutdown não é exceção — é rotina. O &lt;code&gt;CancellationToken&lt;/code&gt; que o &lt;code&gt;ExecuteAsync&lt;/code&gt; te entrega é o que separa um serviço que para de forma limpa de um que deixa rastros de dados corrompidos a cada reinício. Use &lt;code&gt;while (!stoppingToken.IsCancellationRequested)&lt;/code&gt; e repasse o token para suas chamadas async, sempre.&lt;/p&gt;

&lt;p&gt;Vá ao seu hosted service e verifique agora: seu loop realmente checa o token? E suas operações longas conseguem ser interrompidas no meio sem deixar lixo? Se a resposta for não, você tem um candidato a bug de produção esperando o próximo deploy. No próximo post, fechamos o módulo decidindo quando manter o hosted service na API e quando movê-lo para um worker service dedicado.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>gracefulshutdown</category>
      <category>cancellationtoken</category>
      <category>backgroundservice</category>
    </item>
    <item>
      <title>IServiceScopeFactory em BackgroundService: por que injetar serviços scoped diretamente quebra</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Tue, 16 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/iservicescopefactory-em-backgroundservice-por-que-injetar-servicos-scoped-diretamente-quebra-i3c</link>
      <guid>https://dev.to/pmraven/iservicescopefactory-em-backgroundservice-por-que-injetar-servicos-scoped-diretamente-quebra-i3c</guid>
      <description>&lt;p&gt;Você cria um &lt;code&gt;JobProcessor&lt;/code&gt;, registra como scoped, injeta no seu worker, dá F5 — e o app explode com &lt;code&gt;Cannot consume scoped service ... from singleton&lt;/code&gt;. Bem-vindo a uma das armadilhas mais confusas do .NET: o erro não está no seu &lt;code&gt;JobProcessor&lt;/code&gt;, está em &lt;strong&gt;como&lt;/strong&gt; você o injetou.&lt;/p&gt;

&lt;p&gt;Neste post você vai entender a causa raiz desse erro de lifetime e ver o padrão correto com &lt;code&gt;IServiceScopeFactory&lt;/code&gt; — além de descobrir por que criar um escopo novo a &lt;strong&gt;cada iteração do loop&lt;/strong&gt; não é detalhe, é a parte que importa.&lt;/p&gt;

&lt;h2&gt;
  
  
  O cenário: tudo parece certo, mas não roda
&lt;/h2&gt;

&lt;p&gt;Imagine um worker bem estruturado. Você tirou a lógica de dentro da classe &lt;code&gt;Worker&lt;/code&gt; e criou um componente separado, o &lt;code&gt;JobProcessor&lt;/code&gt;, registrado como &lt;strong&gt;scoped&lt;/strong&gt; no &lt;code&gt;program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por que scoped? Porque, na vida real, o &lt;code&gt;JobProcessor&lt;/code&gt; provavelmente vai depender de um &lt;code&gt;DbContext&lt;/code&gt; — e contexto de banco de dados é um caso clássico de serviço com lifetime scoped. Até aqui, tudo idiomático.&lt;/p&gt;

&lt;p&gt;Aí você injeta direto no worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roda? Não. Você encontra exatamente o erro que estava tentando evitar.&lt;/p&gt;

&lt;h2&gt;
  
  
  A causa raiz: um BackgroundService é um singleton
&lt;/h2&gt;

&lt;p&gt;Aqui está o detalhe que quase todo mundo esquece: &lt;strong&gt;um &lt;code&gt;BackgroundService&lt;/code&gt; (hosted service) é tratado como singleton&lt;/strong&gt; pelo container de DI. Ele é criado uma vez e vive enquanto a aplicação existe.&lt;/p&gt;

&lt;p&gt;E o container do .NET tem uma regra de proteção: &lt;strong&gt;um singleton não pode consumir um serviço scoped diretamente.&lt;/strong&gt; Faz sentido — um serviço scoped tem vida curta e amarrada a um escopo (uma requisição, uma unidade de trabalho), enquanto o singleton vive "para sempre". Se o container deixasse você capturar o scoped no construtor do singleton, você prenderia aquele &lt;code&gt;DbContext&lt;/code&gt; de vida curta a um objeto eterno. É o caminho garantido para vazamento de recursos e bugs sutis.&lt;/p&gt;

&lt;p&gt;Por isso o container barra na largada, com o erro de "scoped from singleton". Não é um capricho — é o runtime te protegendo de um problema pior lá na frente.&lt;/p&gt;

&lt;h2&gt;
  
  
  A correção: injete a factory, não o serviço
&lt;/h2&gt;

&lt;p&gt;A solução é não pedir o &lt;code&gt;JobProcessor&lt;/code&gt; no construtor. Em vez disso, injete um &lt;strong&gt;&lt;code&gt;IServiceScopeFactory&lt;/code&gt;&lt;/strong&gt; e use-o para criar um escopo &lt;strong&gt;dentro do loop&lt;/strong&gt;, resolvendo o serviço scoped a cada iteração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceScopeFactory&lt;/span&gt; &lt;span class="n"&gt;serviceScopeFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceScopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;jobProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessNextJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare no que mudou. Estamos fazendo a &lt;strong&gt;mesma coisa&lt;/strong&gt; de antes — chamar &lt;code&gt;ProcessNextJob()&lt;/code&gt; — mas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Injetamos &lt;code&gt;IServiceScopeFactory&lt;/code&gt; (que é singleton-safe), não o &lt;code&gt;JobProcessor&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A cada volta do loop, &lt;code&gt;CreateScope()&lt;/code&gt; cria um &lt;strong&gt;escopo fresco&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetRequiredService&amp;lt;JobProcessor&amp;gt;()&lt;/code&gt; resolve o serviço scoped &lt;strong&gt;dentro&lt;/strong&gt; daquele escopo.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;using&lt;/code&gt; garante que o escopo é descartado no fim da iteração.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O &lt;code&gt;JobProcessor&lt;/code&gt; continua registrado como scoped — está tudo bem. O que muda é que agora respeitamos o lifetime dele em vez de tentar furá-lo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que um escopo &lt;em&gt;fresco a cada iteração&lt;/em&gt; importa
&lt;/h2&gt;

&lt;p&gt;É tentador pensar "ok, criei o escopo, problema resolvido". Mas o ganho real é mais profundo: &lt;strong&gt;um escopo novo por iteração mantém o worker seguro ao longo do tempo.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O worker em si pode ter &lt;strong&gt;vida longa&lt;/strong&gt; (ele é o coordenador, fica de pé o tempo todo).&lt;/li&gt;
&lt;li&gt;Os serviços que &lt;strong&gt;fazem o trabalho&lt;/strong&gt; ganham o lifetime scoped correto — nascem e morrem dentro de cada iteração.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;using&lt;/code&gt; &lt;strong&gt;descarta as dependências de forma limpa&lt;/strong&gt; ao fim de cada ciclo. Se há um &lt;code&gt;DbContext&lt;/code&gt; ali dentro, ele é criado, usado e liberado a cada job, sem acumular estado de uma iteração para a outra.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É essa combinação que dá o melhor dos dois mundos: um &lt;strong&gt;worker de longa duração coordenando&lt;/strong&gt;, e &lt;strong&gt;serviços de vida curta executando&lt;/strong&gt; sem riscos de estado compartilhado ou conexões penduradas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Quando você vê &lt;code&gt;Cannot consume scoped service from singleton&lt;/code&gt; no seu Worker Service, a mensagem está dizendo uma verdade incômoda: &lt;strong&gt;seu &lt;code&gt;BackgroundService&lt;/code&gt; é singleton, e ele não pode segurar um scoped direto.&lt;/strong&gt; A correção não é mudar o lifetime do &lt;code&gt;JobProcessor&lt;/code&gt; — é injetar &lt;code&gt;IServiceScopeFactory&lt;/code&gt; e criar um escopo fresco a cada iteração do loop.&lt;/p&gt;

&lt;p&gt;Da próxima vez que esbarrar nesse erro, lembre: o worker coordena, o escopo isola, o serviço executa. Já caiu nessa armadilha em algum projeto seu? Teste o padrão acima e veja como o loop fica mais previsível — e seguro com banco de dados.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>workerservice</category>
      <category>dependencyinjection</category>
      <category>backgroundservice</category>
    </item>
    <item>
      <title>O erro do scoped em singleton: por que injetar um DbContext num BackgroundService quebra tudo</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Fri, 12 Jun 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/pmraven/o-erro-do-scoped-em-singleton-por-que-injetar-um-dbcontext-num-backgroundservice-quebra-tudo-4709</link>
      <guid>https://dev.to/pmraven/o-erro-do-scoped-em-singleton-por-que-injetar-um-dbcontext-num-backgroundservice-quebra-tudo-4709</guid>
      <description>&lt;p&gt;Você cria um &lt;code&gt;BackgroundService&lt;/code&gt;, injeta um &lt;code&gt;DbContext&lt;/code&gt; no construtor, roda — e por um tempo parece funcionar. Até começarem os bugs que ninguém consegue reproduzir: estado obsoleto, conexões descartadas, dados que não batem. Esse é, talvez, &lt;strong&gt;o erro mais comum&lt;/strong&gt; ao construir hosted services no .NET.&lt;/p&gt;

&lt;p&gt;Ao final deste post você vai entender por que esse padrão quebra os tempos de vida (lifetimes) das suas dependências e qual é o jeito correto de acessar serviços scoped dentro de um hosted service.&lt;/p&gt;

&lt;h2&gt;
  
  
  O conflito de tempos de vida
&lt;/h2&gt;

&lt;p&gt;Aqui está o detalhe que muita gente não percebe: &lt;strong&gt;hosted services são registrados como Singletons&lt;/strong&gt;. Uma única instância é criada para toda a vida da aplicação.&lt;/p&gt;

&lt;p&gt;Só que muitos serviços comuns no ASP.NET são &lt;strong&gt;scoped&lt;/strong&gt; — uma instância por escopo (tipicamente, por requisição web). Os exemplos clássicos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DbContext&lt;/code&gt; (Entity Framework)&lt;/li&gt;
&lt;li&gt;Repositórios&lt;/li&gt;
&lt;li&gt;Serviços de unit of work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quando você registra um &lt;code&gt;DbContext&lt;/code&gt;, isso acontece de forma padrão:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AddDbContext&lt;/code&gt; registra o &lt;code&gt;AppDbContext&lt;/code&gt; como &lt;strong&gt;scoped&lt;/strong&gt;. Esse é o comportamento padrão do EF Core, e é o correto para o ciclo de uma requisição.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que injetar direto quebra tudo
&lt;/h2&gt;

&lt;p&gt;Um hosted service vive durante &lt;strong&gt;toda a vida da aplicação&lt;/strong&gt;. Um serviço scoped foi feito para viver &lt;strong&gt;apenas dentro de um escopo específico&lt;/strong&gt; — por exemplo, uma requisição web.&lt;/p&gt;

&lt;p&gt;Então, se você injetar a dependência scoped diretamente no construtor do hosted service, você &lt;strong&gt;viola esse limite de tempo de vida&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobProcessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;_dbContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ scoped dentro de um singleton&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// isto é o que NÃO queremos&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_dbContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As consequências de quebrar essa fronteira:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tempos de vida incorretos&lt;/strong&gt; de serviço&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependências descartadas&lt;/strong&gt; sendo usadas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estado obsoleto&lt;/strong&gt; preso em memória pela vida inteira da aplicação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comportamento imprevisível&lt;/strong&gt; geral&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E imprevisibilidade é a última coisa que você quer quando o assunto é falar com o banco de dados.&lt;/p&gt;

&lt;h2&gt;
  
  
  O padrão correto: IServiceScopeFactory
&lt;/h2&gt;

&lt;p&gt;Em vez de injetar a dependência scoped, você injeta um &lt;code&gt;IServiceScopeFactory&lt;/code&gt; e &lt;strong&gt;cria um escopo sempre que precisar resolver serviços scoped&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A fábrica de escopos foi projetada exatamente para isso: te entregar um escopo que você controla, dentro do seu hosted service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobProcessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IServiceScopeFactory&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceScopeFactory&lt;/span&gt; &lt;span class="n"&gt;scopeFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_scopeFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scopeFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Verificando jobs..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// 1. cria um escopo (com using, para ser descartado depois)&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_scopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// 2. resolve o DbContext dentro desse escopo&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// 3. usa normalmente&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os três pontos que fazem isso funcionar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CreateScope()&lt;/code&gt;&lt;/strong&gt; dentro do loop cria um &lt;code&gt;IServiceScope&lt;/code&gt; novo a cada iteração.&lt;/li&gt;
&lt;li&gt;A declaração &lt;strong&gt;&lt;code&gt;using&lt;/code&gt;&lt;/strong&gt; garante que o escopo (e tudo o que ele resolveu) seja &lt;strong&gt;descartado&lt;/strong&gt; ao final.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;GetRequiredService&amp;lt;AppDbContext&amp;gt;()&lt;/code&gt;&lt;/strong&gt; resolve o &lt;code&gt;DbContext&lt;/code&gt; a partir do &lt;code&gt;ServiceProvider&lt;/code&gt; daquele escopo — respeitando o lifetime scoped.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eakle21wzuwfwtu6c44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eakle21wzuwfwtu6c44.png" alt="Loop de hosted service criando e descartando um escopo por iteração para resolver o DbContext" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A ideia central: você cria o escopo para o &lt;code&gt;DbContext&lt;/code&gt; porque o &lt;code&gt;DbContext&lt;/code&gt; &lt;strong&gt;é scoped&lt;/strong&gt;, mas o lugar onde o processamento acontece &lt;strong&gt;roda como singleton&lt;/strong&gt;. O escopo é a ponte segura entre os dois mundos.&lt;/p&gt;

&lt;h2&gt;
  
  
  O aprendizado que evita horas de debug
&lt;/h2&gt;

&lt;p&gt;Guarde essa frase: &lt;strong&gt;um hosted service é um singleton.&lt;/strong&gt; Então, sempre que precisar de uma dependência scoped — como um &lt;code&gt;DbContext&lt;/code&gt; — você deve &lt;strong&gt;criar um escopo primeiro&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Fazer isso garante duas coisas críticas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As dependências scoped são &lt;strong&gt;criadas corretamente&lt;/strong&gt; a cada uso.&lt;/li&gt;
&lt;li&gt;E, igualmente importante, são &lt;strong&gt;descartadas corretamente&lt;/strong&gt; ao final — sem conexões vazando nem estado preso por toda a vida da aplicação.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Injetar um &lt;code&gt;DbContext&lt;/code&gt; direto no construtor de um &lt;code&gt;BackgroundService&lt;/code&gt; é o tipo de erro que não estoura na hora — ele apodrece silenciosamente até virar um bug intermitente em produção. A correção é barata: injete &lt;code&gt;IServiceScopeFactory&lt;/code&gt;, crie um escopo com &lt;code&gt;using&lt;/code&gt; dentro do loop e resolva suas dependências scoped a partir dele.&lt;/p&gt;

&lt;p&gt;Abra seu hosted service agora e confira: tem algum serviço scoped no construtor? Se tiver, você já sabe o que refatorar. No próximo post, vamos falar sobre outro pilar de hosted services em produção: graceful shutdown e o &lt;code&gt;CancellationToken&lt;/code&gt; que você não pode ignorar.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>dependencyinjection</category>
      <category>efcore</category>
      <category>backgroundservice</category>
    </item>
    <item>
      <title>IHostedService vs. BackgroundService: qual usar e por quê</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Wed, 10 Jun 2026 00:00:09 +0000</pubDate>
      <link>https://dev.to/pmraven/ihostedservice-vs-backgroundservice-qual-usar-e-por-que-4pb</link>
      <guid>https://dev.to/pmraven/ihostedservice-vs-backgroundservice-qual-usar-e-por-que-4pb</guid>
      <description>&lt;p&gt;Você herda de &lt;code&gt;BackgroundService&lt;/code&gt;, sobrescreve &lt;code&gt;ExecuteAsync&lt;/code&gt;, e tudo "simplesmente funciona". Mas você sabe o que essa classe esconde de você? Por baixo do açúcar sintático existe uma interface, um host genérico e um ciclo de vida que decide quando seu código começa e — mais importante — quando ele para.&lt;/p&gt;

&lt;p&gt;Ao final deste post você vai saber exatamente o que é o &lt;code&gt;IHostedService&lt;/code&gt;, o que o &lt;code&gt;BackgroundService&lt;/code&gt; abstrai por cima dele, e quando vale a pena descer um nível e implementar a interface na mão.&lt;/p&gt;

&lt;h2&gt;
  
  
  O ponto de partida: o ciclo de vida da aplicação
&lt;/h2&gt;

&lt;p&gt;Toda aplicação ASP.NET Core moderna se constrói sobre o &lt;strong&gt;generic host&lt;/strong&gt; (.NET host). É ele quem gerencia injeção de dependência, configuração, logging e, principalmente, o &lt;strong&gt;startup e o shutdown&lt;/strong&gt; da aplicação.&lt;/p&gt;

&lt;p&gt;Os hosted services se conectam diretamente a esse ciclo de vida. A regra é simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quando a aplicação &lt;strong&gt;inicia&lt;/strong&gt;, o hosted service inicia.&lt;/li&gt;
&lt;li&gt;Quando a aplicação &lt;strong&gt;para&lt;/strong&gt;, o hosted service para.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa amarração ao ciclo de vida não é um detalhe — é justamente o que permite que uma tarefa em background pare &lt;strong&gt;graciosamente&lt;/strong&gt; e libere seus recursos em vez de morrer no meio do trabalho.&lt;/p&gt;

&lt;h2&gt;
  
  
  IHostedService: a interface por baixo de tudo
&lt;/h2&gt;

&lt;p&gt;A forma mais simples de rodar trabalho em background no .NET é um hosted service, e na base dele está a interface &lt;code&gt;IHostedService&lt;/code&gt;. Ela define apenas dois métodos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IHostedService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses dois métodos &lt;strong&gt;definem o ciclo de vida do serviço&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StartAsync&lt;/code&gt; é chamado quando a aplicação inicia.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;StopAsync&lt;/code&gt; é chamado quando a aplicação para.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repare no &lt;code&gt;CancellationToken&lt;/code&gt;: ele não está ali por enfeite. É o que permite que a tarefa em background participe com segurança do shutdown, sinalizando "hora de parar" para que recursos como arquivos abertos ou conexões sejam liberados corretamente.&lt;/p&gt;

&lt;p&gt;Tecnicamente, você &lt;strong&gt;pode&lt;/strong&gt; implementar essa interface diretamente. Mas se fizer isso, todo o controle do loop, do estado e do encerramento fica nas suas mãos — e é aí que entra muito código repetitivo (boilerplate).&lt;/p&gt;

&lt;h2&gt;
  
  
  BackgroundService: o wrapper que você realmente usa
&lt;/h2&gt;

&lt;p&gt;Para evitar esse boilerplate, o ASP.NET fornece uma classe base: &lt;code&gt;BackgroundService&lt;/code&gt;. A maioria dos hosted services que você vê em produção usa exatamente essa classe.&lt;/p&gt;

&lt;p&gt;A diferença é direta: em vez de implementar &lt;strong&gt;dois&lt;/strong&gt; métodos de ciclo de vida, você sobrescreve &lt;strong&gt;um&lt;/strong&gt; só:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobProcessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Verificando jobs..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que está acontecendo aqui:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ExecuteAsync&lt;/code&gt; roda durante &lt;strong&gt;toda a vida&lt;/strong&gt; da aplicação.&lt;/li&gt;
&lt;li&gt;Dentro dele há um &lt;strong&gt;loop&lt;/strong&gt; que continua até a aplicação desligar.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;stoppingToken&lt;/code&gt; é acionado no shutdown, o loop sai, e a tarefa termina de forma controlada.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E o ponto que confunde muita gente: você não implementa &lt;code&gt;IHostedService&lt;/code&gt; aqui porque &lt;strong&gt;o &lt;code&gt;BackgroundService&lt;/code&gt; já faz isso por você&lt;/strong&gt;. Se você olhar dentro da classe (no namespace &lt;code&gt;Microsoft.Extensions.Hosting&lt;/code&gt;), vai ver que ela implementa &lt;code&gt;IHostedService&lt;/code&gt; e cuida do &lt;code&gt;StartAsync&lt;/code&gt;/&lt;code&gt;StopAsync&lt;/code&gt; internamente. O &lt;code&gt;BackgroundService&lt;/code&gt; é, em essência, um wrapper elegante sobre a interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registrando o serviço
&lt;/h2&gt;

&lt;p&gt;Implementar a classe é metade do trabalho. Para o host realmente iniciar o serviço, você o registra no container de injeção de dependência, no &lt;code&gt;program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feito isso, o host &lt;strong&gt;inicia o serviço automaticamente&lt;/strong&gt; quando a aplicação sobe. E você pode registrar quantos hosted services quiser — eles rodam lado a lado. Sobe a aplicação e ambos passam a executar seus loops em paralelo, cada um controlado pelo mesmo ciclo de vida que o &lt;code&gt;IHostedService&lt;/code&gt; fornece.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fme7z6974wu66lmjfj07r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fme7z6974wu66lmjfj07r.png" alt="Linha do tempo do ciclo de vida de um hosted service, do start ao shutdown" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Outro detalhe importante: isso funciona &lt;strong&gt;tanto em um Worker Service quanto em uma API ASP.NET Core&lt;/strong&gt;. O mesmo &lt;code&gt;AddHostedService&amp;lt;T&amp;gt;()&lt;/code&gt; e o mesmo &lt;code&gt;BackgroundService&lt;/code&gt; servem nos dois cenários — a diferença é só onde o serviço vive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Então, quando implementar IHostedService na mão?
&lt;/h2&gt;

&lt;p&gt;Na prática, a resposta para a maioria dos casos é: &lt;strong&gt;use &lt;code&gt;BackgroundService&lt;/code&gt;&lt;/strong&gt;. Ele trata da estrutura de ciclo de vida, garante que a tarefa inicie com a aplicação, pare no shutdown e respeite o cancelamento — deixando você focar no trabalho que realmente importa.&lt;/p&gt;

&lt;p&gt;Você só desce para &lt;code&gt;IHostedService&lt;/code&gt; direto quando precisa de &lt;strong&gt;controle total&lt;/strong&gt; sobre o start e o stop, por exemplo: lógica de inicialização que precisa rodar e terminar antes da aplicação ficar pronta, ou um encerramento que não combina com o modelo de "um loop único" do &lt;code&gt;ExecuteAsync&lt;/code&gt;. Fora esses casos, o wrapper é a escolha certa — e mais segura.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IHostedService&lt;/code&gt; é o contrato; &lt;code&gt;BackgroundService&lt;/code&gt; é a conveniência construída sobre ele. Entender essa relação faz você parar de tratar &lt;code&gt;ExecuteAsync&lt;/code&gt; como mágica e passar a enxergar o ciclo de vida real por trás do seu código em background — o que é o primeiro passo para escrever serviços que param tão bem quanto começam.&lt;/p&gt;

&lt;p&gt;Agora abra seu &lt;code&gt;program.cs&lt;/code&gt;, crie um &lt;code&gt;BackgroundService&lt;/code&gt; simples e observe-o iniciar e parar junto com a aplicação. No próximo post, vamos atacar um dos erros mais comuns com hosted services: injetar dependências scoped em um serviço que, na verdade, é singleton.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>backgroundprocessing</category>
      <category>hostedservice</category>
      <category>csharp</category>
    </item>
    <item>
      <title>O que você precisa para rodar um Teste de Carga de sucesso? (Checklist Prático)</title>
      <dc:creator>Paulo Walraven</dc:creator>
      <pubDate>Tue, 07 Apr 2026 10:32:55 +0000</pubDate>
      <link>https://dev.to/pmraven/o-que-voce-precisa-para-rodar-um-teste-de-carga-de-sucesso-checklist-pratico-5k7</link>
      <guid>https://dev.to/pmraven/o-que-voce-precisa-para-rodar-um-teste-de-carga-de-sucesso-checklist-pratico-5k7</guid>
      <description>&lt;p&gt;Você já se perguntou o que é necessário para garantir que sua aplicação não saia do ar no primeiro pico de acessos? A resposta está no Teste de Carga. Mas, antes de começar a simular milhares de usuários acessando seu sistema, o que exatamente você precisa ter em mãos?&lt;/p&gt;

&lt;p&gt;Aqui está um checklist simples e direto para estruturar um teste de carga eficiente:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ambiente de Teste (Mirroring)
&lt;/h2&gt;

&lt;p&gt;O ideal é não fazer o teste no ambiente de produção para não afetar os usuários reais que estão utilizando o sistema.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Isolamento&lt;/strong&gt;: Utilize um ambiente de homologação ou staging que seja, se possível, uma réplica do hardware e da infraestrutura de produção.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dados&lt;/strong&gt;: Utilize uma base de dados com volume realista. Testar a carga em um banco com apenas 10 registros é completamente diferente de testar em um com 10 milhões. A volumetria muda o comportamento do sistema.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Ferramenta de Geração de Carga
&lt;/h2&gt;

&lt;p&gt;Você vai precisar de um software focado em disparar essas requisições simultâneas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escolha da Ferramenta&lt;/strong&gt;: Utilize opções consolidadas no mercado, como JMeter, K6 ou Locust.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Regra de Ouro&lt;/strong&gt;: A ferramenta deve ser instalada em uma máquina separada do alvo. Se você rodar a ferramenta de teste e o sistema alvo na mesma máquina, o teste será inválido, pois ambos estarão competindo pelos mesmos recursos de processador e memória.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Monitoramento
&lt;/h2&gt;

&lt;p&gt;Apenas gerar carga não adianta se você não souber o que está acontecendo "por baixo dos panos". Você precisa de visibilidade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;APMs (Application Performance Monitoring)&lt;/strong&gt;: Ferramentas como New Relic, Datadog ou Dynatrace ajudam a ver o que acontece no nível da aplicação.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dashboards de Infra&lt;/strong&gt;: Se estiver na nuvem (AWS, Azure, GCP), utilize o CloudWatch ou soluções similares para acompanhar o uso de CPU, uso de memória, I/O de disco e tráfego de rede.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logs&lt;/strong&gt;: Tenha acesso fácil aos logs de erro do servidor para identificar falhas silenciosas ou gargalos durante o pico de carga.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Plano de Teste
&lt;/h2&gt;

&lt;p&gt;Não basta "bombardear" a página inicial do site sem critério. É preciso simular o mundo real.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Caminho do Usuário&lt;/strong&gt;: O que o usuário realmente vai fazer? Desenhe as jornadas (Ex: Login -&amp;gt; Busca -&amp;gt; Adicionar ao carrinho -&amp;gt; Checkout).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Carga Esperada&lt;/strong&gt;: Quantos usuários simultâneos você quer atingir? Qual o tempo de rampa (quanto tempo leva para subir gradualmente de 0 a 1000 usuários)?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com esse planejamento em mãos, você deixa de "adivinhar" o comportamento do seu software e passa a ter dados reais para escalar com segurança. Boa sorte nos testes!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>performance</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
