Add workflow registry

This commit introduces a workflow registry for dynamically creating and registering workflows. It also includes a typed workflow materializer and extends existing services to support the new functionality. Changes are made across multiple files to integrate these new components and enhance the overall workflow functionalities.
This commit is contained in:
Sipke Schoorstra 2024-01-04 22:07:55 +01:00
parent 0785fa6e42
commit fa21cba51f
11 changed files with 127 additions and 6 deletions

View file

@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>

View file

@ -0,0 +1,38 @@
using System.Net;
using Elsa.Abstractions;
using Elsa.Http;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Options;
namespace Elsa.Server.Web.Endpoints.DynamicWorkflows.Post;
public class Post(IWorkflowRegistry workflowRegistry, IWorkflowRuntime workflowRuntime) : ElsaEndpointWithoutRequest
{
public override void Configure()
{
Post("/dynamic-workflows");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
{
var workflow = new Workflow
{
Identity = new WorkflowIdentity("DynamicWorkflow1", 1, "DynamicWorkflow1:v1"),
Root = new Sequence
{
Activities =
{
new WriteLine("Step 1"),
new WriteLine("Step 2"),
new WriteLine("Step 3")
}
}
};
await workflowRegistry.RegisterAsync(workflow, ct);
await workflowRuntime.StartWorkflowAsync("DynamicWorkflow1", new StartWorkflowRuntimeOptions());
}
}

View file

@ -228,6 +228,7 @@ services
elsa.InstallDropIns(options => options.DropInRootDirectory = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "DropIns"));
elsa.AddSwagger();
elsa.AddFastEndpointsAssembly<Program>();
});
services.AddHealthChecks();

View file

@ -167,6 +167,7 @@ public class WorkflowManagementFeature : FeatureBase
.AddScoped<IExpressionDescriptorRegistry, ExpressionDescriptorRegistry>()
.AddScoped<IExpressionDescriptorProvider, DefaultExpressionDescriptorProvider>()
.AddScoped<ISerializationOptionsConfigurator, SerializationOptionsConfigurator>()
.AddScoped<IWorkflowMaterializer, TypedWorkflowMaterializer>()
.AddScoped<IWorkflowMaterializer, ClrWorkflowMaterializer>()
.AddScoped<IWorkflowMaterializer, JsonWorkflowMaterializer>()
.AddScoped<IActivityResolver, WorkflowDefinitionActivityResolver>()

View file

@ -0,0 +1,32 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Management.Mappers;
using Elsa.Workflows.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.Management.Materializers;
/// <summary>
/// A workflow materializer that deserializes workflows created in C# code.
/// </summary>
public class TypedWorkflowMaterializer(WorkflowDefinitionMapper workflowDefinitionMapper) : IWorkflowMaterializer
{
/// <summary>
/// The name of the materializer.
/// </summary>
public const string MaterializerName = "Typed";
/// <inheritdoc />
public string Name => MaterializerName;
/// <inheritdoc />
public ValueTask<Workflow> MaterializeAsync(WorkflowDefinition definition, CancellationToken cancellationToken)
{
var workflow = ToWorkflow(definition);
return new ValueTask<Workflow>(workflow);
}
private Workflow ToWorkflow(WorkflowDefinition definition) => workflowDefinitionMapper.Map(definition);
}

View file

@ -35,12 +35,12 @@ public class WorkflowDefinitionService : IWorkflowDefinitionService
public async Task<Workflow> MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default)
{
var materializers = _materializers();
var provider = materializers.FirstOrDefault(x => x.Name == definition.MaterializerName);
var materializer = materializers.FirstOrDefault(x => x.Name == definition.MaterializerName);
if (provider == null)
if (materializer == null)
throw new Exception("Provider not found");
var workflow = await provider.MaterializeAsync(definition, cancellationToken);
var workflow = await materializer.MaterializeAsync(definition, cancellationToken);
var graph = (await _activityVisitor.VisitAsync(workflow, cancellationToken)).Flatten().ToList();
// Assign identities.

View file

@ -1,4 +1,5 @@
using Elsa.Workflows.Management.Contracts;
using Elsa.Workflows.Runtime.Models;
namespace Elsa.Workflows.Runtime.Contracts;
@ -12,4 +13,11 @@ public interface IWorkflowDefinitionStorePopulator
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
Task PopulateStoreAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Adds a workflow definition to the store.
/// </summary>
/// <param name="materializedWorkflow">A materialized workflow.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
Task AddAsync(MaterializedWorkflow materializedWorkflow, CancellationToken cancellationToken = default);
}

View file

@ -0,0 +1,16 @@
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.Runtime.Contracts;
/// <summary>
/// Registers workflows.
/// </summary>
public interface IWorkflowRegistry
{
/// <summary>
/// Registers a workflow.
/// </summary>
/// <param name="workflow">The workflow to register.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
Task RegisterAsync(Workflow workflow, CancellationToken cancellationToken = default);
}

View file

@ -165,6 +165,7 @@ public class WorkflowRuntimeFeature : FeatureBase
.AddScoped<IActivityExecutionMapper, DefaultActivityExecutionMapper>()
.AddScoped<IWorkflowDefinitionStorePopulator, DefaultWorkflowDefinitionStorePopulator>()
.AddScoped<IRegistriesPopulator, DefaultRegistriesPopulator>()
.AddScoped<IWorkflowRegistry, DefaultWorkflowRegistry>()
.AddScoped<ITaskReporter, TaskReporter>()
.AddScoped<SynchronousTaskDispatcher>()
.AddScoped<BackgroundTaskDispatcher>()

View file

@ -53,12 +53,18 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
foreach (var result in results)
{
await AssignIdentities(result.Workflow, cancellationToken);
await AddOrUpdateAsync(result, cancellationToken);
await IndexTriggersAsync(result, cancellationToken);
await AddAsync(result, cancellationToken);
}
}
}
/// <inheritdoc />
public async Task AddAsync(MaterializedWorkflow materializedWorkflow, CancellationToken cancellationToken = default)
{
await AssignIdentities(materializedWorkflow.Workflow, cancellationToken);
await AddOrUpdateAsync(materializedWorkflow, cancellationToken);
await IndexTriggersAsync(materializedWorkflow, cancellationToken);
}
private async Task AssignIdentities(Workflow workflow, CancellationToken cancellationToken)
{

View file

@ -0,0 +1,17 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Management.Materializers;
using Elsa.Workflows.Runtime.Contracts;
using Elsa.Workflows.Runtime.Models;
namespace Elsa.Workflows.Runtime.Services;
/// <inheritdoc />
public class DefaultWorkflowRegistry(IWorkflowDefinitionStorePopulator populator) : IWorkflowRegistry
{
/// <inheritdoc />
public async Task RegisterAsync(Workflow workflow, CancellationToken cancellationToken = default)
{
var materializedWorkflow = new MaterializedWorkflow(workflow, nameof(DefaultWorkflowRegistry), TypedWorkflowMaterializer.MaterializerName);
await populator.AddAsync(materializedWorkflow, cancellationToken);
}
}