惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
博客园_首页
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Help Net Security
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
A
About on SuperTechFans
腾讯CDC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
DataBreaches.Net
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
月光博客
月光博客
云风的 BLOG
云风的 BLOG
博客园 - 聂微东
Hugging Face - Blog
Hugging Face - Blog
雷峰网
雷峰网
C
Check Point Blog
V
V2EX
T
Tailwind CSS Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Security Blog
Microsoft Security Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Worker fino, processador gordo: estruturando Worker Servi...
Paulo Walraven · 2026-06-18 · via DEV Community

Abra um Worker Service de seis meses de idade que ninguém estruturou direito e você vai encontrar a mesma cena: uma classe Worker de duzentas linhas, com lógica de banco, regra de negócio e tratamento de erro, toda enrolada dentro de um while. Tente escrever um teste unitário pra isso. Não dá — você teria que subir o host inteiro.

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: manter a classe Worker fina e empurrar a lógica para um JobProcessor injetável. 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.

O instinto errado: colocar a lógica no worker

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

O problema é que o worker passa a ter duas responsabilidades misturadas:

  1. Orquestrar — manter o loop de pé, controlar o intervalo, respeitar o cancellation.
  2. Executar — a regra concreta de processar cada job.

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.

A regra de ouro: o worker deve ser fino

A mentalidade certa é: a classe Worker tem uma responsabilidade muito específica — orquestrar a execução, recuperar o trabalho e delegar o processamento. Nada mais.

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 JobProcessor.

Primeiro, extraia a lógica para a sua própria classe:

public class JobProcessor
{
    public Task ProcessNextJob()
    {
        // toda a regra de processamento vive aqui
        return Task.CompletedTask;
    }
}

Registre no program.cs como scoped — porque, na prática, esse processador vai depender de coisas como um DbContext:

builder.Services.AddScoped<JobProcessor>();

E deixe o worker apenas coordenar e delegar:

public class Worker(IServiceScopeFactory serviceScopeFactory) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = serviceScopeFactory.CreateScope();
            var jobProcessor = scope.ServiceProvider.GetRequiredService<JobProcessor>();

            await jobProcessor.ProcessNextJob();

            await Task.Delay(2000, stoppingToken);
        }
    }
}

Repare no que o worker faz agora: ele vai no nível mais alto de abstração — "pega o próximo job e faz algo com ele" — 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.

Por que essa separação destrava os testes

Aqui está o ganho concreto. Com a lógica isolada no JobProcessor, você consegue testá-lo de forma independente do host.

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

Enquanto isso, o Worker 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.

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

A nuance: não deixe o worker engordar de novo

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

Fique atento a esse drift. Sempre que notar o worker crescendo, pergunte: isso é orquestração ou execução? Se for execução, o lugar é o JobProcessor. 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.

Conclusão

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

Da próxima vez que for escrever lógica dentro de um ExecuteAsync, 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.