Currently, our ASP.NET Core Web API handles both user HTTP requests and heavy Hangfire background job executions. During peak hours, CPU usage spikes heavily, degrading API response times.
Architecture Separation
We want to move the server component (job execution) into a standalone Worker Service project while keeping job creation inside the Web API project.
Web API Enqueue Setup
// Enqueuing job from API endpoint without processing local jobs
app.MapPost("/api/reports", (IBackgroundJobClient jobClient) =>
{
// Trigger background execution without running processing server locally
jobClient.Enqueue(() => Console.WriteLine("Processing report..."));
return Results.Accepted();
});What is the best practice for sharing job interfaces and contract models between the API client project and the standalone worker service? How do we ensure that the Web API project only enqueues tasks without spawning a Background Processing server instance?
Keep the web app responsible for accepting requests and enqueueing work, then run a separate worker process to execute the jobs. Both applications connect to the same Hangfire storage, but only the worker starts a Hangfire server.
Configure the web app to enqueue jobs
Register Hangfire storage in the ASP.NET Core app so it can create jobs. Do not call
AddHangfireServerhere; that would make the web process run jobs too.Run the server in a Worker Service
Create a separate Worker Service project and reference the assembly containing the job type. Configure it with the same storage settings and register the job's dependencies. The worker's queues must include any queues assigned to the jobs it should process.
The job class can live in a shared application project referenced by both apps. Hangfire creates it in the worker, so its dependencies need to be registered there. For example, put
[Queue("reports")]on the job class or method if it should be handled by thereportsqueue:Keep the job assembly and method signatures compatible between deployments: Hangfire stores job details in storage and the worker must be able to resolve them. You can scale processing by running more worker instances against the same storage. If you use recurring jobs, register their schedules in one deliberate place rather than accidentally registering them from every web and worker instance.