Incremental work on configuration API

This commit is contained in:
Sipke Schoorstra 2022-05-03 16:17:20 +02:00
parent 3900e32368
commit bc4b19b430
18 changed files with 132 additions and 109 deletions

View file

@ -1,7 +1,7 @@
using Elsa.Activities;
using Elsa.Services;
namespace Elsa.Modules.Activities.Resolvers;
namespace Elsa.ActivityNodeResolvers;
/// <summary>
/// Returns a list of outbound activities for a given <see cref="Switch"/> activity's branches.

View file

@ -138,6 +138,10 @@ public class ActivityExecutionContext
public T GetRequiredService<T>() where T : notnull => WorkflowExecutionContext.GetRequiredService<T>();
public object GetRequiredService(Type serviceType) => WorkflowExecutionContext.GetRequiredService(serviceType);
public T GetOrCreateService<T>() where T : notnull => WorkflowExecutionContext.GetOrCreateService<T>();
public object GetOrCreateService(Type serviceType) => WorkflowExecutionContext.GetOrCreateService(serviceType);
public T? GetService<T>() where T : notnull => WorkflowExecutionContext.GetService<T>();
public object? GetService(Type serviceType) => WorkflowExecutionContext.GetService(serviceType);
public T? Get<T>(Input<T>? input) => input == null ? default : Get<T>(input.LocationReference);
public object? Get(RegisterLocationReference locationReference)

View file

@ -82,6 +82,10 @@ public class WorkflowExecutionContext
public T GetRequiredService<T>() where T : notnull => _serviceProvider.GetRequiredService<T>();
public object GetRequiredService(Type serviceType) => _serviceProvider.GetRequiredService(serviceType);
public T GetOrCreateService<T>() where T : notnull => ActivatorUtilities.GetServiceOrCreateInstance<T>(_serviceProvider);
public object GetOrCreateService(Type serviceType) => ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, serviceType);
public T? GetService<T>() where T : notnull => _serviceProvider.GetService<T>();
public object? GetService(Type serviceType) => _serviceProvider.GetService(serviceType);
public void AddCompletionCallback(ActivityExecutionContext owner, IActivity child, ActivityCompletionCallback completionCallback)
{

View file

@ -51,8 +51,8 @@ public class ElsaOptionsConfigurator
foreach (var configurator in _configurators)
{
configurator.ConfigureServices(Services);
configurator.ConfigureHostedServices(Services);
configurator.ConfigureServices(this);
configurator.ConfigureHostedServices(this);
}
foreach (var hostedServiceDescriptor in _hostedServiceDescriptors.OrderBy(x => x.Order))
@ -72,7 +72,6 @@ public class ElsaOptionsConfigurator
.AddSingleton<IIdentityGraphService, IdentityGraphService>()
.AddSingleton<IWorkflowStateSerializer, WorkflowStateSerializer>()
.AddSingleton<IActivitySchedulerFactory, ActivitySchedulerFactory>()
.AddSingleton<IActivityNodeResolver, OutboundActivityNodeResolver>()
.AddSingleton<IHasher, Hasher>()
.AddSingleton<IIdentityGenerator, RandomIdentityGenerator>()
.AddSingleton<ISystemClock, SystemClock>()
@ -86,6 +85,10 @@ public class ElsaOptionsConfigurator
// Pipelines.
.AddSingleton<IActivityExecutionPipeline, ActivityExecutionPipeline>()
.AddSingleton<IWorkflowExecutionPipeline, WorkflowExecutionPipeline>()
// Built-in activity services.
.AddSingleton<IActivityNodeResolver, OutboundActivityNodeResolver>()
.AddSingleton<IActivityNodeResolver, SwitchActivityNodeResolver>()
// Logging
.AddLogging();

View file

@ -1,20 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Elsa.Options;
namespace Elsa.Services;
public interface IConfigurator
{
void ConfigureServices(IServiceCollection services);
void ConfigureHostedServices(IServiceCollection services);
void ConfigureServices(ElsaOptionsConfigurator configurator);
void ConfigureHostedServices(ElsaOptionsConfigurator configurator);
}
public abstract class ConfiguratorBase : IConfigurator
{
public virtual void ConfigureServices(IServiceCollection services)
public virtual void ConfigureServices(ElsaOptionsConfigurator configurator)
{
}
public virtual void ConfigureHostedServices(IServiceCollection services)
public virtual void ConfigureHostedServices(ElsaOptionsConfigurator configurator)
{
}
}

View file

@ -5,7 +5,7 @@ namespace Elsa.Persistence.Extensions;
public static class ServiceCollectionExtensions
{
public static ElsaOptionsConfigurator UsePersistence(this ElsaOptionsConfigurator configurator, Action<PersistenceOptions>? configure = default)
public static ElsaOptionsConfigurator ConfigurePersistence(this ElsaOptionsConfigurator configurator, Action<PersistenceOptions>? configure = default)
{
configurator.Configure(() => new PersistenceOptions(configurator), configure);
return configurator;

View file

@ -51,9 +51,9 @@ public class PersistenceOptions : ConfiguratorBase
return this;
}
public override void ConfigureServices(IServiceCollection services)
public override void ConfigureServices(ElsaOptionsConfigurator configurator)
{
services
configurator.Services
.AddSingleton(WorkflowDefinitionStore)
.AddSingleton(WorkflowInstanceStore)
.AddSingleton(WorkflowBookmarkStore)

View file

@ -1,71 +1,20 @@
using System.Threading.Channels;
using Elsa.Expressions;
using Elsa.Mediator.Extensions;
using Elsa.Options;
using Elsa.Runtime.HostedServices;
using Elsa.Runtime.Implementations;
using Elsa.Runtime.Interpreters;
using Elsa.Runtime.Models;
using Elsa.Runtime.Options;
using Elsa.Runtime.Services;
using Elsa.Runtime.Stimuli.Handlers;
using Elsa.Runtime.WorkflowProviders;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Elsa.Runtime.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddElsaRuntime(this ElsaOptionsConfigurator configurator)
public static ElsaOptionsConfigurator ConfigureElsaRuntime(this ElsaOptionsConfigurator configurator, Action<ElsaRuntimeOptions>? configure = default)
{
var services = configurator.Services;
services.AddOptions<WorkflowRuntimeOptions>();
// Add ordered hosted services.
configurator
.AddHostedService<RegisterDescriptors>()
.AddHostedService<RegisterExpressionSyntaxDescriptors>()
.AddHostedService<DispatchedWorkflowDefinitionWorker>()
.AddHostedService<DispatchedWorkflowInstanceWorker>()
.AddHostedService<PopulateWorkflowDefinitionStore>();
return services
// Core.
.AddSingleton<IWorkflowRegistry, WorkflowRegistry>()
.AddSingleton<IStimulusInterpreter, StimulusInterpreter>()
.AddSingleton<IWorkflowInstructionExecutor, WorkflowInstructionExecutor>()
.AddSingleton<ITriggerIndexer, TriggerIndexer>()
.AddSingleton<IBookmarkManager, BookmarkManager>()
.AddSingleton<IWorkflowInstanceFactory, WorkflowInstanceFactory>()
.AddSingleton<IWorkflowDefinitionService, WorkflowDefinitionService>()
.AddSingleton(sp => sp.GetRequiredService<IOptions<WorkflowRuntimeOptions>>().Value.WorkflowInvokerFactory(sp))
.AddSingleton(sp => sp.GetRequiredService<IOptions<WorkflowRuntimeOptions>>().Value.WorkflowDispatcherFactory(sp))
// Stimulus handlers.
.AddStimulusHandler<TriggerWorkflowsStimulusHandler>()
.AddStimulusHandler<ResumeWorkflowsStimulusHandler>()
// Instruction interpreters.
.AddInstructionInterpreter<TriggerWorkflowInstructionInterpreter>()
.AddInstructionInterpreter<ResumeWorkflowInstructionInterpreter>()
// Workflow definition providers.
.AddWorkflowDefinitionProvider<ClrWorkflowDefinitionProvider>()
// Workflow engine.
.AddSingleton<IWorkflowService, WorkflowService>()
// Domain event handlers.
.AddNotificationHandlersFrom(typeof(ServiceCollectionExtensions))
// Channels for dispatching workflows in-memory.
.CreateChannel<DispatchWorkflowDefinitionRequest>()
.CreateChannel<DispatchWorkflowInstanceRequest>()
;
configurator.Configure(configure);
return configurator;
}
private static IServiceCollection CreateChannel<T>(this IServiceCollection services) =>
public static IServiceCollection CreateChannel<T>(this IServiceCollection services) =>
services
.AddSingleton(CreateChannel<T>())
.AddSingleton(CreateChannelReader<T>)
@ -78,5 +27,5 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddWorkflowDefinitionProvider<T>(this IServiceCollection services) where T : class, IWorkflowDefinitionProvider => services.AddSingleton<IWorkflowDefinitionProvider, T>();
public static IServiceCollection AddStimulusHandler<T>(this IServiceCollection services) where T : class, IStimulusHandler => services.AddSingleton<IStimulusHandler, T>();
public static IServiceCollection AddInstructionInterpreter<T>(this IServiceCollection services) where T : class, IWorkflowInstructionInterpreter => services.AddSingleton<IWorkflowInstructionInterpreter, T>();
public static IServiceCollection ConfigureWorkflowRuntime(this IServiceCollection services, Action<WorkflowRuntimeOptions> configure) => services.Configure(configure);
public static IServiceCollection ConfigureWorkflowRuntime(this IServiceCollection services, Action<ElsaRuntimeOptions> configure) => services.Configure(configure);
}

View file

@ -0,0 +1,83 @@
using Elsa.Mediator.Extensions;
using Elsa.Options;
using Elsa.Runtime.Extensions;
using Elsa.Runtime.HostedServices;
using Elsa.Runtime.Implementations;
using Elsa.Runtime.Interpreters;
using Elsa.Runtime.Models;
using Elsa.Runtime.Services;
using Elsa.Runtime.Stimuli.Handlers;
using Elsa.Runtime.WorkflowProviders;
using Elsa.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Elsa.Runtime.Options;
public class ElsaRuntimeOptions : ConfiguratorBase
{
/// <summary>
/// A list of workflow builders configured during application startup.
/// </summary>
public IDictionary<string, Func<IServiceProvider, ValueTask<IWorkflow>>> Workflows { get; set; } = new Dictionary<string, Func<IServiceProvider, ValueTask<IWorkflow>>>();
/// <summary>
/// A factory that instantiates a concrete <see cref="IWorkflowInvoker"/>.
/// </summary>
public Func<IServiceProvider, IWorkflowInvoker> WorkflowInvokerFactory { get; set; } = sp => ActivatorUtilities.CreateInstance<DefaultWorkflowInvoker>(sp);
/// <summary>
/// A factory that instantiates a concrete <see cref="IWorkflowDispatcher"/>.
/// </summary>
public Func<IServiceProvider, IWorkflowDispatcher> WorkflowDispatcherFactory { get; set; } = sp => ActivatorUtilities.CreateInstance<TaskBasedWorkflowDispatcher>(sp);
public override void ConfigureServices(ElsaOptionsConfigurator configurator)
{
var services = configurator.Services;
services.AddOptions<ElsaRuntimeOptions>();
services
// Core.
.AddSingleton<IWorkflowRegistry, WorkflowRegistry>()
.AddSingleton<IStimulusInterpreter, StimulusInterpreter>()
.AddSingleton<IWorkflowInstructionExecutor, WorkflowInstructionExecutor>()
.AddSingleton<ITriggerIndexer, TriggerIndexer>()
.AddSingleton<IBookmarkManager, BookmarkManager>()
.AddSingleton<IWorkflowInstanceFactory, WorkflowInstanceFactory>()
.AddSingleton<IWorkflowDefinitionService, WorkflowDefinitionService>()
.AddSingleton(sp => sp.GetRequiredService<IOptions<ElsaRuntimeOptions>>().Value.WorkflowInvokerFactory(sp))
.AddSingleton(sp => sp.GetRequiredService<IOptions<ElsaRuntimeOptions>>().Value.WorkflowDispatcherFactory(sp))
// Stimulus handlers.
.AddStimulusHandler<TriggerWorkflowsStimulusHandler>()
.AddStimulusHandler<ResumeWorkflowsStimulusHandler>()
// Instruction interpreters.
.AddInstructionInterpreter<TriggerWorkflowInstructionInterpreter>()
.AddInstructionInterpreter<ResumeWorkflowInstructionInterpreter>()
// Workflow definition providers.
.AddWorkflowDefinitionProvider<ClrWorkflowDefinitionProvider>()
// Workflow engine.
.AddSingleton<IWorkflowService, WorkflowService>()
// Domain event handlers.
.AddNotificationHandlersFrom(typeof(ServiceCollectionExtensions))
// Channels for dispatching workflows in-memory.
.CreateChannel<DispatchWorkflowDefinitionRequest>()
.CreateChannel<DispatchWorkflowInstanceRequest>()
;
}
public override void ConfigureHostedServices(ElsaOptionsConfigurator configurator)
{
configurator
.AddHostedService<RegisterDescriptors>()
.AddHostedService<RegisterExpressionSyntaxDescriptors>()
.AddHostedService<DispatchedWorkflowDefinitionWorker>()
.AddHostedService<DispatchedWorkflowInstanceWorker>()
.AddHostedService<PopulateWorkflowDefinitionStore>();
}
}

View file

@ -1,24 +0,0 @@
using Elsa.Runtime.Implementations;
using Elsa.Runtime.Services;
using Elsa.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Runtime.Options;
public class WorkflowRuntimeOptions
{
/// <summary>
/// A list of workflow builders configured during application startup.
/// </summary>
public IDictionary<string, Func<IServiceProvider, ValueTask<IWorkflow>>> Workflows { get; set; } = new Dictionary<string, Func<IServiceProvider, ValueTask<IWorkflow>>>();
/// <summary>
/// A factory that instantiates a concrete <see cref="IWorkflowInvoker"/>.
/// </summary>
public Func<IServiceProvider, IWorkflowInvoker> WorkflowInvokerFactory { get; set; } = sp => ActivatorUtilities.CreateInstance<DefaultWorkflowInvoker>(sp);
/// <summary>
/// A factory that instantiates a concrete <see cref="IWorkflowDispatcher"/>.
/// </summary>
public Func<IServiceProvider, IWorkflowDispatcher> WorkflowDispatcherFactory { get; set; } = sp => ActivatorUtilities.CreateInstance<TaskBasedWorkflowDispatcher>(sp);
}

View file

@ -11,17 +11,17 @@ using Microsoft.Extensions.Options;
namespace Elsa.Runtime.WorkflowProviders;
/// <summary>
/// Provides workflows to the system that are registered with <see cref="WorkflowRuntimeOptions"/>
/// Provides workflows to the system that are registered with <see cref="ElsaRuntimeOptions"/>
/// </summary>
public class ClrWorkflowDefinitionProvider : IWorkflowDefinitionProvider
{
private readonly IIdentityGraphService _identityGraphService;
private readonly WorkflowSerializerOptionsProvider _workflowSerializerOptionsProvider;
private readonly IServiceProvider _serviceProvider;
private readonly WorkflowRuntimeOptions _options;
private readonly ElsaRuntimeOptions _options;
public ClrWorkflowDefinitionProvider(
IOptions<WorkflowRuntimeOptions> options,
IOptions<ElsaRuntimeOptions> options,
IIdentityGraphService identityGraphService,
WorkflowSerializerOptionsProvider workflowSerializerOptionsProvider,
IServiceProvider serviceProvider

View file

@ -19,9 +19,9 @@ public static class ServiceCollectionExtensions
return services
.AddElsaCore(elsa =>
{
elsa.UsePersistence(persistence => persistence.UseInMemoryProvider());
elsa.UseActivityServices();
elsa.AddElsaRuntime();
elsa.ConfigurePersistence(persistence => persistence.UseInMemoryProvider());
elsa.ConfigureCoreActivityServices();
elsa.ConfigureElsaRuntime();
elsa.AddElsaManagement();
configure?.Invoke(elsa);
});

View file

@ -1,5 +1,6 @@
using Elsa.Attributes;
using Elsa.Models;
using Elsa.Modules.Activities.Providers;
using Elsa.Modules.Activities.Services;
namespace Elsa.Modules.Activities.Console;
@ -15,7 +16,7 @@ public class ReadLine : Activity<string>
protected override void Execute(ActivityExecutionContext context)
{
var provider = context.GetRequiredService<IStandardInStreamProvider>();
var provider = context.GetService<IStandardInStreamProvider>() ?? new StandardInStreamProvider(System.Console.In);
var reader = provider.GetTextReader();
var text = reader.ReadLine();
context.Set(Result, text);

View file

@ -1,6 +1,7 @@
using System.ComponentModel;
using Elsa.Attributes;
using Elsa.Models;
using Elsa.Modules.Activities.Providers;
using Elsa.Modules.Activities.Services;
namespace Elsa.Modules.Activities.Console;
@ -35,7 +36,7 @@ public class WriteLine : Activity
protected override void Execute(ActivityExecutionContext context)
{
var text = context.Get(Text);
var provider = context.GetRequiredService<IStandardOutStreamProvider>();
var provider = context.GetService<IStandardOutStreamProvider>() ?? new StandardOutStreamProvider(System.Console.Out);
var textWriter = provider.GetTextWriter();
textWriter.WriteLine(text);
}

View file

@ -8,7 +8,7 @@ public static class ServiceCollectionExtensions
/// <summary>
/// Registers required services for activities provided by this package.
/// </summary>
public static ActivityOptions UseActivityServices(this ElsaOptionsConfigurator configurator)
public static ActivityOptions ConfigureCoreActivityServices(this ElsaOptionsConfigurator configurator)
{
return configurator.Configure<ActivityOptions>();
}

View file

@ -1,7 +1,7 @@
using Elsa.Modules.Activities.Configurators;
using Elsa.Modules.Activities.Providers;
using Elsa.Modules.Activities.Resolvers;
using Elsa.Modules.Activities.Services;
using Elsa.Options;
using Elsa.Services;
using Microsoft.Extensions.DependencyInjection;
@ -24,10 +24,9 @@ public class ActivityOptions : ConfiguratorBase
return this;
}
public override void ConfigureServices(IServiceCollection services)
public override void ConfigureServices(ElsaOptionsConfigurator configurator)
{
services
.AddSingleton<IActivityNodeResolver, SwitchActivityNodeResolver>()
configurator.Services
.AddSingleton<ISerializationOptionsConfigurator, CustomSerializationOptionConfigurator>()
.AddSingleton(StandardInStreamProvider)
.AddSingleton(StandardOutStreamProvider);

View file

@ -1,3 +1,4 @@
using Elsa.Options;
using Elsa.Persistence.Entities;
using Elsa.Persistence.EntityFrameworkCore.Handlers;
using Elsa.Persistence.EntityFrameworkCore.HostedServices;
@ -49,8 +50,10 @@ public class EFCorePersistenceOptions : IConfigurator
return this;
}
public void ConfigureServices(IServiceCollection services)
public void ConfigureServices(ElsaOptionsConfigurator configurator)
{
var services = configurator.Services;
if (ContextPoolingIsEnabled)
services.AddPooledDbContextFactory<ElsaDbContext>(DbContextOptionsBuilderAction);
else
@ -73,9 +76,9 @@ public class EFCorePersistenceOptions : IConfigurator
;
}
public void ConfigureHostedServices(IServiceCollection services)
public void ConfigureHostedServices(ElsaOptionsConfigurator configurator)
{
if (AutoRunMigrationsIsEnabled)
PersistenceOptions.ElsaOptionsConfigurator.AddHostedService<RunMigrations>(-1); // Migrations need to run before other hosted services that depend on DB access.
configurator.AddHostedService<RunMigrations>(-1); // Migrations need to run before other hosted services that depend on DB access.
}
}

View file

@ -40,7 +40,7 @@ var sqlServerConnectionString = configuration.GetConnectionString("SqlServer");
// Add services.
services
.AddElsa(elsa => elsa.UsePersistence(p => p.UseEntityFrameworkCoreProvider(ef => ef.UseSqlite())))
.AddElsa(elsa => elsa.ConfigurePersistence(p => p.UseEntityFrameworkCoreProvider(ef => ef.UseSqlite())))
//.AddProtoActorWorkflowHost()
.AddJobServices(new QuartzJobSchedulerProvider(), new HangfireJobQueueProvider())
.AddSchedulingServices()