Skip to main content

Dependency injection and scopes

Most operational Nexus services are scoped in v3. Jobs, functions, queue operations, health checks, and integrations can use scoped application dependencies, including a DbContext. The scope that resolves a service owns its lifetime.

Calling Nexus in a request or application operation​

Controllers and other request-scoped application services can inject Nexus dependencies directly:

public class OrderPublisher(IEnqueuer<OrderMessage> enqueuer)
{
public Task PublishAsync(OrderMessage message) => enqueuer.EnqueueAsync(message);
}

builder.Services.AddScoped<OrderPublisher>();

The same applies to transient services resolved within that scope. A transient service resolved by a singleton does not make a scoped dependency safe to capture.

Choose an application's lifetime based on its requirements. Stateless or shared singleton dependencies can remain singleton when they do not capture scoped services. Nexus's process-wide interfaces such as INexusInstance, IInstanceEventBroker, IGracefulShutdownService, and IDateTime also remain singleton.

Calling Nexus from a singleton or hosted service​

Inject IServiceScopeFactory and create a scope around each independent operation. Resolve the Nexus service from the scope, and keep the scope alive until its asynchronous work is complete:

public class BackgroundPublisher(IServiceScopeFactory scopeFactory)
{
public async Task PublishAsync(OrderMessage message)
{
await using var scope = scopeFactory.CreateAsyncScope();
var enqueuer = scope.ServiceProvider.GetRequiredService<IEnqueuer<OrderMessage>>();
await enqueuer.EnqueueAsync(message);
}
}

This example assumes no EF unit of work is deferring the enqueue. Do not cache the resolved enqueuer, return it to a caller, or dispose the scope while work is still running. The same pattern applies to INexusFunction, job repositories, queue readers/updaters, and other scoped Nexus dependencies. Do not resolve them from app.Services or another root provider.

Entity Framework unit of work​

When using the EF integration, resolve the context and enqueuer from the same scope. Database queue messages are saved with the entity transaction, and in-memory additions happen after commit. Use SaveChangesAsync() and asynchronous commits. A new scope created by a singleton does not join an arbitrary caller's context or transaction.

For a standalone enqueue that must persist without saving a context, an infrastructure publisher can deliberately use ImmediateQueueOperationScope:

await using var scope = scopeFactory.CreateAsyncScope();
using var immediate = new ImmediateQueueOperationScope();
var enqueuer = scope.ServiceProvider.GetRequiredService<IEnqueuer<OrderMessage>>();
await enqueuer.EnqueueAsync(message);

Normal entity-plus-enqueue operations should keep the EF transaction boundary. Remote API/external-pending delivery cannot roll back with that transaction; use an outbox database queue when delivery must follow the entity commit.

Execution scopes and concurrency​

ExecutionScope and instance behavior
Function callTarget service resolved in a fresh scope for that execution
Queue jobJob resolved in a job execution scope; processing threads share the job instance
Distributed processorFresh scope and processor instance per leased chunk; calls on that instance are sequential
Application instance-event handlersResolved in a scope for handling the application event
Nexus scoped hosted-service wrapperHolds a scope for the worker lifetime, rather than creating a scope per iteration

Scoped does not mean thread safe. Queue jobs using several processing threads must protect shared state and must not concurrently use one EF context. Separate processing executions can also share singleton application dependencies, which must be safe for concurrent use.

Custom registrations and health publishers​

Register custom serializers, repositories, middleware, storage, and other operational replacements with lifetimes appropriate to their dependencies. A singleton replacement cannot inject scoped Nexus services simply because the built-in implementation is now scoped.

The .NET health publisher host expects singleton IHealthCheckPublisher registrations. A publisher that needs scoped Nexus dependencies can use Nexus's adapter:

using CommerceMind.Nexus.Abstractions.Core.DependencyInjection;

builder.Services.AddScopedHealthCheckPublisher<MyHealthCheckPublisher>();

The adapter lazily creates and retains one scope/publisher until disposal, preserving publisher state between reports. It does not create a fresh scope for each report. If an operation needs short-lived resources such as a context per publication, create that inner scope explicitly. A publisher that only uses singleton dependencies can still be registered as a singleton directly.

Enable DI scope validation in development to catch root-provider resolution and singleton captures, including captures through intermediate transient services.