Skip to content

UseCosmosDbPersistence() should support factory delegate to resolve CosmosClient from DI container #1424

@ToniaDemchuk

Description

@ToniaDemchuk

Is your feature request related to a problem? Please describe.

Currently, UseCosmosDbPersistence() requires a CosmosClient instance to be provided at service registration time:

services.AddWorkflow(options =>
{
    options.UseCosmosClientPersistence(cosmosClientInstance, databaseId);
});

This forces consumers to either:

  1. Build the CosmosClient early during configuration phase (anti-pattern)
  2. Use services.BuildServiceProvider() to resolve it from DI (performance penalty, creates duplicate singletons)
  3. Resort to manual registration of internal types like CosmosDbPersistenceProvider using UsePersistence() factory

The issue is that CosmosClient is typically registered as a singleton in the DI container, but UseCosmosDbPersistence() cannot access it from the service provider since the configuration action runs before the container is built.

Current overloads of UseCosmosDbPersistence() force duplication of CosmosClient instances and don't support reusing existing DI registrations:

  1. Connection string overload - Creates its own client, ignoring existing DI registration with custom auth/serialization setup
  2. CosmosClient instance overload - Requires early instantiation, breaking DI patterns and causing duplicate singletons
  3. Endpoint + TokenCredential overload - Hardcodes specific auth method, preventing reuse of existing configured client

Most applications already register CosmosClient for other services (health checks, custom repositories, etc.) with specific authentication methods (managed identity, service principal, connection string). WorkflowCore should reuse this existing registration rather than creating duplicate connections with potentially different auth configurations.

Describe the solution you'd like

Add an overload to UseCosmosDbPersistence() that accepts a factory delegate Func<IServiceProvider, CosmosClient> instead of a direct instance:

services.AddWorkflow(options =>
{
    // New overload - CosmosClient resolved from DI when first needed
    options.UseCosmosDbPersistence(
        sp => sp.GetRequiredService<CosmosClient>(),
        databaseId,
        storageOptions); // Optional: CosmosDbStorageOptions
});

This would internally use UsePersistence() with a factory that resolves CosmosClient from the service provider, aligning with DI best practices and matching the pattern already available for other persistence providers.

Describe alternatives you've considered

  • Manual registration using UsePersistence() - This works but requires consumers to reference internal types (CosmosClientFactory, CosmosDbProvisioner, CosmosDbPersistenceProvider) that are implementation details and may break in future versions:
options.UsePersistence(sp =>
{
    var cosmosClient = sp.GetRequiredService<CosmosClient>();
    var clientFactory = new CosmosClientFactory(cosmosClient);
    var provisioner = new CosmosDbProvisioner(clientFactory, new CosmosDbStorageOptions());
    return new CosmosDbPersistenceProvider(clientFactory, databaseId, provisioner, new CosmosDbStorageOptions());
});

Additional context

This pattern is already established in other parts of .NET ecosystem (e.g., AddDbContext() accepts Action<IServiceProvider, DbContextOptionsBuilder>). The WorkflowCore library already supports factory-based registration via UsePersistence(Func<IServiceProvider, IWorkflowPersistenceProvider>), so extending this to the Cosmos DB provider would be a natural and consistent enhancement.

Environment:

  • WorkflowCore 3.17.0
  • WorkflowCore.Providers.Azure 3.17.0
  • Microsoft.Azure.Cosmos 3.58.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions