Simplify Proto.Actor bookmarks and triggers (#3917)
* Refactor HTTP route table population * Update workflow definition EF store to use abstract IWorkflowInstanceStore This decoupling is necessary, because the application might not be using EF Core, but e.g. Elasticsearch * Move workflow state persistence to the default workflow runtime feature * Update JS handler to provide access to variables during trigger indexing * Configure Proto.Actor runtime with different workflow pipeline * Add sample using Proto.Actor runtime * Replace BookmarkGrain with DB based persistence
This commit is contained in:
parent
998fafa75c
commit
c8d87cd763
7
Elsa.sln
7
Elsa.sln
|
|
@ -172,6 +172,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.QuartzIntegrat
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.HangfireIntegration", "src\samples\aspnet\Elsa.Samples.HangfireIntegration\Elsa.Samples.HangfireIntegration.csproj", "{D2614FC7-102F-4F78-BB06-7C87304A10BA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.ProtoActorRuntime", "src\samples\aspnet\Elsa.Samples.ProtoActorRuntime\Elsa.Samples.ProtoActorRuntime.csproj", "{A41E24C6-B16D-4C6A-A9A9-2C5AF424F20F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -426,6 +428,10 @@ Global
|
|||
{D2614FC7-102F-4F78-BB06-7C87304A10BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D2614FC7-102F-4F78-BB06-7C87304A10BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D2614FC7-102F-4F78-BB06-7C87304A10BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A41E24C6-B16D-4C6A-A9A9-2C5AF424F20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A41E24C6-B16D-4C6A-A9A9-2C5AF424F20F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A41E24C6-B16D-4C6A-A9A9-2C5AF424F20F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A41E24C6-B16D-4C6A-A9A9-2C5AF424F20F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -501,5 +507,6 @@ Global
|
|||
{1FCB2200-28B8-4703-8E89-73241AAED047} = {89608AA5-5ADE-4832-AC7B-871C4AE64210}
|
||||
{B0312D9E-FA30-43E9-B666-40A8782D6E1C} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{D2614FC7-102F-4F78-BB06-7C87304A10BA} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{A41E24C6-B16D-4C6A-A9A9-2C5AF424F20F} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -70,9 +70,13 @@ app.MapHealthChecks("/");
|
|||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
// Register Elsa middleware.
|
||||
// Elsa API endpoints for designer.
|
||||
app.UseWorkflowsApi();
|
||||
|
||||
// Captures unhandled exceptions and returns a JSON response.
|
||||
app.UseJsonSerializationErrorHandler();
|
||||
|
||||
// Elsa HTTP Endpoint activities
|
||||
app.UseWorkflows();
|
||||
|
||||
// Run.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Elsa.Features.Abstractions;
|
||||
|
||||
|
|
@ -46,4 +47,14 @@ public abstract class FeatureBase : IFeature
|
|||
public virtual void Apply()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the specified hosted service using an optional priority to control in which order it will be registered with the service container.
|
||||
/// </summary>
|
||||
/// <param name="priority">The priority.</param>
|
||||
/// <typeparam name="T">The type of hosted service to configure.</typeparam>
|
||||
protected void ConfigureHostedService<T>(int priority = 0) where T : class, IHostedService
|
||||
{
|
||||
Module.ConfigureHostedService<T>(priority);
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public class Module : IModule
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModule ConfigureHostedService<T>(int priority = 0)
|
||||
public IModule ConfigureHostedService<T>(int priority = 0) where T : class, IHostedService
|
||||
{
|
||||
_hostedServiceDescriptors.Add(new HostedServiceDescriptor(priority, typeof(T)));
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public interface IModule
|
|||
/// <summary>
|
||||
/// Configures a <see cref="IHostedService"/> using an optional priority to control in which order it will be registered with the service container.
|
||||
/// </summary>
|
||||
IModule ConfigureHostedService<T>(int priority = 0);
|
||||
IModule ConfigureHostedService<T>(int priority = 0) where T : class, IHostedService;
|
||||
|
||||
/// <summary>
|
||||
/// Will apply all configured features, causing the <see cref="Services"/> collection to be populated.
|
||||
|
|
|
|||
|
|
@ -118,9 +118,15 @@ public class ElasticWorkflowInstanceStore : IWorkflowInstanceStore
|
|||
|
||||
private static QueryDescriptor<WorkflowInstance> Filter(QueryDescriptor<WorkflowInstance> descriptor, WorkflowInstanceFilter filter)
|
||||
{
|
||||
// TODO: Implement remaining filters.
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Id)) descriptor = descriptor.Match(m => m.Field(f => f.Id).Query(filter.Id));
|
||||
if (!string.IsNullOrWhiteSpace(filter.DefinitionId)) descriptor = descriptor.Match(m => m.Field(f => f.DefinitionId).Query(filter.DefinitionId));
|
||||
if (!string.IsNullOrWhiteSpace(filter.DefinitionVersionId)) descriptor = descriptor.Match(m => m.Field(f => f.DefinitionVersionId).Query(filter.DefinitionVersionId));
|
||||
|
||||
// TODO: filter by IDs
|
||||
// TODO: filter by DefinitionIDs
|
||||
// TODO: filter by DefinitionVersionIDs
|
||||
// TODO: filter by CorrelationIDs
|
||||
|
||||
if (filter.Version != null) descriptor = descriptor.Match(m => m.Field(f => f.Version).Query(filter.Version.ToString()!));
|
||||
if (!string.IsNullOrWhiteSpace(filter.CorrelationId)) descriptor = descriptor.Match(m => m.Field(f => f.CorrelationId).Query(filter.CorrelationId));
|
||||
if (filter.WorkflowStatus != null) descriptor = descriptor.Match(m => m.Field(f => f.Status).Query(filter.WorkflowStatus.ToString()!));
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Elsa.EntityFrameworkCore.Extensions;
|
|||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static EFCoreDefaultRuntimePersistenceFeature UsePostgreSql(this EFCoreDefaultRuntimePersistenceFeature feature, string connectionString)
|
||||
public static EFCoreDefaultWorkflowRuntimePersistenceFeature UsePostgreSql(this EFCoreDefaultWorkflowRuntimePersistenceFeature feature, string connectionString)
|
||||
{
|
||||
feature.DbContextOptionsBuilder = (_, db) => db.UseElsaPostgreSql(connectionString);
|
||||
return feature;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Elsa.EntityFrameworkCore.Extensions;
|
|||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static EFCoreDefaultRuntimePersistenceFeature UseSqlServer(this EFCoreDefaultRuntimePersistenceFeature feature, string connectionString)
|
||||
public static EFCoreDefaultWorkflowRuntimePersistenceFeature UseSqlServer(this EFCoreDefaultWorkflowRuntimePersistenceFeature feature, string connectionString)
|
||||
{
|
||||
feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(connectionString);
|
||||
return feature;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ namespace Elsa.EntityFrameworkCore.Extensions;
|
|||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static EFCoreDefaultRuntimePersistenceFeature UseSqlite(this EFCoreDefaultRuntimePersistenceFeature feature, string connectionString = Constants.DefaultConnectionString)
|
||||
public static EFCoreWorkflowRuntimePersistenceFeature UseSqlite(this EFCoreWorkflowRuntimePersistenceFeature feature, string connectionString = Constants.DefaultConnectionString)
|
||||
{
|
||||
feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlite(connectionString);
|
||||
return feature;
|
||||
|
||||
}
|
||||
public static EFCoreDefaultWorkflowRuntimePersistenceFeature UseSqlite(this EFCoreDefaultWorkflowRuntimePersistenceFeature feature, string connectionString = Constants.DefaultConnectionString)
|
||||
{
|
||||
feature.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlite(connectionString);
|
||||
return feature;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Elsa.EntityFrameworkCore.Modules.Management;
|
|||
public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
||||
{
|
||||
private readonly EntityStore<ManagementElsaDbContext, WorkflowDefinition> _store;
|
||||
private readonly EntityStore<ManagementElsaDbContext, WorkflowInstance> _workflowInstanceStore;
|
||||
private readonly IWorkflowInstanceStore _workflowInstanceStore;
|
||||
private readonly IActivitySerializer _serializer;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -23,7 +23,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
/// </summary>
|
||||
public EFCoreWorkflowDefinitionStore(
|
||||
EntityStore<ManagementElsaDbContext, WorkflowDefinition> store,
|
||||
EntityStore<ManagementElsaDbContext, WorkflowInstance> workflowInstanceStore,
|
||||
IWorkflowInstanceStore workflowInstanceStore,
|
||||
IActivitySerializer serializer)
|
||||
{
|
||||
_store = store;
|
||||
|
|
@ -130,7 +130,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
|
|||
var set = dbContext.WorkflowDefinitions;
|
||||
var queryable = set.AsQueryable();
|
||||
var ids = await Filter(queryable, filter).Select(x => x.Id).Distinct().ToListAsync(cancellationToken);
|
||||
await _workflowInstanceStore.DeleteWhereAsync(x => ids.Contains(x.DefinitionVersionId), cancellationToken);
|
||||
await _workflowInstanceStore.DeleteManyAsync(new WorkflowInstanceFilter { DefinitionVersionIds = ids }, cancellationToken);
|
||||
return await _store.DeleteWhereAsync(x => ids.Contains(x.Id), cancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ public class EFCoreWorkflowInstancePersistenceFeature : PersistenceFeatureBase<M
|
|||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Configure<WorkflowInstancesFeature>(feature =>
|
||||
{
|
||||
feature.WorkflowInstanceStore = sp => sp.GetRequiredService<EFCoreWorkflowInstanceStore>();
|
||||
});
|
||||
Module.Configure<WorkflowInstancesFeature>(feature => feature.WorkflowInstanceStore = sp => sp.GetRequiredService<EFCoreWorkflowInstanceStore>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
using Elsa.EntityFrameworkCore.Common;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Runtime.Entities;
|
||||
using Elsa.Workflows.Runtime.Features;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the default workflow runtime to use EF Core persistence providers.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(WorkflowRuntimeFeature))]
|
||||
[DependsOn(typeof(DefaultWorkflowRuntimeFeature))]
|
||||
public class EFCoreDefaultWorkflowRuntimePersistenceFeature : PersistenceFeatureBase<RuntimeElsaDbContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public EFCoreDefaultWorkflowRuntimePersistenceFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Configure<WorkflowRuntimeFeature>(feature =>
|
||||
{
|
||||
feature.WorkflowTriggerStore = sp => sp.GetRequiredService<EFCoreTriggerStore>();
|
||||
feature.BookmarkStore = sp => sp.GetRequiredService<EFCoreBookmarkStore>();
|
||||
});
|
||||
|
||||
Module.Configure<DefaultWorkflowRuntimeFeature>(feature => { feature.WorkflowStateStore = sp => sp.GetRequiredService<EFCoreWorkflowStateStore>(); });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
base.Apply();
|
||||
|
||||
AddEntityStore<WorkflowState, EFCoreWorkflowStateStore>();
|
||||
AddEntityStore<StoredTrigger, EFCoreTriggerStore>();
|
||||
AddStore<StoredBookmark, EFCoreBookmarkStore>();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,18 @@ namespace Elsa.EntityFrameworkCore.Modules.Runtime;
|
|||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the <see cref="DefaultRuntimeFeature"/> to use the <see cref="EFCoreDefaultRuntimePersistenceFeature"/>.
|
||||
/// Configures the <see cref="WorkflowRuntimeFeature"/> to use the <see cref="EFCoreWorkflowRuntimePersistenceFeature"/>.
|
||||
/// </summary>
|
||||
public static DefaultRuntimeFeature UseEntityFrameworkCore(this DefaultRuntimeFeature feature, Action<EFCoreDefaultRuntimePersistenceFeature>? configure = default)
|
||||
public static WorkflowRuntimeFeature UseEntityFrameworkCore(this WorkflowRuntimeFeature feature, Action<EFCoreWorkflowRuntimePersistenceFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the <see cref="DefaultWorkflowRuntimeFeature"/> to use the <see cref="EFCoreDefaultWorkflowRuntimePersistenceFeature"/>.
|
||||
/// </summary>
|
||||
public static DefaultWorkflowRuntimeFeature UseEntityFrameworkCore(this DefaultWorkflowRuntimeFeature feature, Action<EFCoreDefaultWorkflowRuntimePersistenceFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
|
|
|
|||
|
|
@ -8,28 +8,32 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
namespace Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the default workflow runtime to use EF Core persistence providers.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(WorkflowRuntimeFeature))]
|
||||
public class EFCoreDefaultRuntimePersistenceFeature : PersistenceFeatureBase<RuntimeElsaDbContext>
|
||||
public class EFCoreWorkflowRuntimePersistenceFeature : PersistenceFeatureBase<RuntimeElsaDbContext>
|
||||
{
|
||||
public EFCoreDefaultRuntimePersistenceFeature(IModule module) : base(module)
|
||||
/// <inheritdoc />
|
||||
public EFCoreWorkflowRuntimePersistenceFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Configure<WorkflowRuntimeFeature>(feature =>
|
||||
{
|
||||
feature.WorkflowStateStore = sp => sp.GetRequiredService<EFCoreWorkflowStateStore>();
|
||||
feature.WorkflowTriggerStore = sp => sp.GetRequiredService<EFCoreTriggerStore>();
|
||||
feature.BookmarkStore = sp => sp.GetRequiredService<EFCoreBookmarkStore>();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
base.Apply();
|
||||
|
||||
AddEntityStore<WorkflowState, EFCoreWorkflowStateStore>();
|
||||
|
||||
AddEntityStore<StoredTrigger, EFCoreTriggerStore>();
|
||||
AddStore<StoredBookmark, EFCoreBookmarkStore>();
|
||||
}
|
||||
|
|
@ -22,6 +22,15 @@ public static class RouteTableExtensions
|
|||
routeTable.AddRange(paths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds routes from the specified set of bookmarks.
|
||||
/// </summary>
|
||||
public static void AddRoutes(this IRouteTable routeTable, IEnumerable<StoredBookmark> bookmarks)
|
||||
{
|
||||
var paths = Filter(bookmarks).Select(x => x.GetPayload<HttpEndpointBookmarkPayload>().Path).ToList();
|
||||
routeTable.AddRange(paths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds routes from the specified set of bookmarks.
|
||||
/// </summary>
|
||||
|
|
@ -49,6 +58,21 @@ public static class RouteTableExtensions
|
|||
routeTable.RemoveRange(paths);
|
||||
}
|
||||
|
||||
private static IEnumerable<StoredTrigger> Filter(IEnumerable<StoredTrigger> triggers) => triggers.Where(x => x.Name == ActivityTypeNameHelper.GenerateTypeName<HttpEndpoint>() && x.Payload != null);
|
||||
private static IEnumerable<Bookmark> Filter(IEnumerable<Bookmark> triggers) => triggers.Where(x => x.Name == ActivityTypeNameHelper.GenerateTypeName<HttpEndpoint>() && x.Payload != null);
|
||||
private static IEnumerable<StoredTrigger> Filter(IEnumerable<StoredTrigger> triggers)
|
||||
{
|
||||
var triggerName = ActivityTypeNameHelper.GenerateTypeName<HttpEndpoint>();
|
||||
return triggers.Where(x => x.Name == triggerName && x.Payload != null);
|
||||
}
|
||||
|
||||
private static IEnumerable<StoredBookmark> Filter(IEnumerable<StoredBookmark> bookmarks)
|
||||
{
|
||||
var activityTypeName = ActivityTypeNameHelper.GenerateTypeName<HttpEndpoint>();
|
||||
return bookmarks.Where(x => x.ActivityTypeName == activityTypeName && x.Payload != null);
|
||||
}
|
||||
|
||||
private static IEnumerable<Bookmark> Filter(IEnumerable<Bookmark> bookmarks)
|
||||
{
|
||||
var bookmarkName = ActivityTypeNameHelper.GenerateTypeName<HttpEndpoint>();
|
||||
return bookmarks.Where(x => x.Name == bookmarkName && x.Payload != null);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using Elsa.Features.Services;
|
|||
using Elsa.Http.ContentWriters;
|
||||
using Elsa.Http.Contracts;
|
||||
using Elsa.Http.Handlers;
|
||||
using Elsa.Http.HostedServices;
|
||||
using Elsa.Http.Models;
|
||||
using Elsa.Http.Options;
|
||||
using Elsa.Http.Parsers;
|
||||
|
|
@ -77,6 +78,12 @@ public class HttpFeature : FeatureBase
|
|||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
ConfigureHostedService<UpdateRouteTableHostedService>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Elsa.Workflows.Runtime.Notifications;
|
|||
namespace Elsa.Http.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// A handler that updates the route table.
|
||||
/// A handler that updates the route table when workflow triggers and bookmarks are indexed.
|
||||
/// </summary>
|
||||
public class UpdateRouteTable :
|
||||
INotificationHandler<WorkflowTriggersIndexed>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Http.Contracts;
|
||||
using Elsa.Workflows.Core.Helpers;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Elsa.Http.HostedServices;
|
||||
|
||||
/// <summary>
|
||||
/// Update the route table based on workflow triggers and bookmarks.
|
||||
/// </summary>
|
||||
public class UpdateRouteTableHostedService : BackgroundService
|
||||
{
|
||||
private readonly IRouteTable _routeTable;
|
||||
private readonly ITriggerStore _triggerStore;
|
||||
private readonly IBookmarkStore _bookmarkStore;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UpdateRouteTableHostedService"/> class.
|
||||
/// </summary>
|
||||
public UpdateRouteTableHostedService(IRouteTable routeTable, ITriggerStore triggerStore, IBookmarkStore bookmarkStore)
|
||||
{
|
||||
_routeTable = routeTable;
|
||||
_triggerStore = triggerStore;
|
||||
_bookmarkStore = bookmarkStore;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var bookmarkName = ActivityTypeNameHelper.GenerateTypeName<HttpEndpoint>();
|
||||
var triggerFilter = new TriggerFilter { Name = bookmarkName };
|
||||
var bookmarkFilter = new BookmarkFilter { ActivityTypeName = bookmarkName};
|
||||
var triggers = (await _triggerStore.FindManyAsync(triggerFilter, stoppingToken)).ToList();
|
||||
var bookmarks = (await _bookmarkStore.FindManyAsync(bookmarkFilter, stoppingToken)).ToList();
|
||||
|
||||
_routeTable.AddRoutes(triggers);
|
||||
_routeTable.AddRoutes(bookmarks);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ using Elsa.Extensions;
|
|||
using Elsa.JavaScript.Notifications;
|
||||
using Elsa.Mediator.Contracts;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using Jint;
|
||||
|
|
@ -32,7 +33,7 @@ public class WorkflowDefinitionActivityJavaScriptHandler : INotificationHandler<
|
|||
var engine = notification.Engine;
|
||||
var context = notification.Context;
|
||||
|
||||
// Always create workflow input accessors.
|
||||
// Create workflow input accessors.
|
||||
CreateWorkflowInputAccessors(engine, context);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
|
@ -40,13 +41,30 @@ public class WorkflowDefinitionActivityJavaScriptHandler : INotificationHandler<
|
|||
|
||||
private void CreateWorkflowInputAccessors(Engine engine, ExpressionExecutionContext context)
|
||||
{
|
||||
var input = context.GetWorkflowExecutionContext().Input;
|
||||
|
||||
foreach (var inputEntry in input)
|
||||
if(context.TryGetWorkflowExecutionContext(out var workflowExecutionContext))
|
||||
{
|
||||
var inputPascalName = inputEntry.Key.Pascalize();
|
||||
var inputValue = inputEntry.Value;
|
||||
engine.SetValue($"get{inputPascalName}", (Func<object?>)(() => inputValue));
|
||||
var input = workflowExecutionContext.Input;
|
||||
|
||||
foreach (var inputEntry in input)
|
||||
{
|
||||
var inputPascalName = inputEntry.Key.Pascalize();
|
||||
var inputValue = inputEntry.Value;
|
||||
engine.SetValue($"get{inputPascalName}", (Func<object?>)(() => inputValue));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We end up here when we are evaluating an expression during trigger indexing.
|
||||
// Typically, a workflow definition might have variables declared, that we want to be able to access from JavaScript expressions.
|
||||
foreach(var block in context.Memory.Blocks.Values)
|
||||
{
|
||||
if(block.Metadata is not VariableBlockMetadata variableBlockMetadata)
|
||||
continue;
|
||||
|
||||
var variable = variableBlockMetadata.Variable;
|
||||
var variablePascaleName = variable.Name.Pascalize();
|
||||
engine.SetValue($"get{variablePascaleName}", (Func<object?>)(() => block.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
using Elsa.ProtoActor.Protos;
|
||||
|
||||
namespace Elsa.ProtoActor;
|
||||
|
||||
internal record BookmarksStored(ICollection<StoredBookmark> Bookmarks);
|
||||
|
||||
internal record BookmarksRemovedByWorkflow(string WorkflowInstanceId);
|
||||
|
|
@ -9,5 +9,4 @@ internal static class ClusterExtensions
|
|||
{
|
||||
public static RunningWorkflowsGrainClient GetNamedRunningWorkflowsGrain(this Cluster cluster) => cluster.GetRunningWorkflowsGrain(nameof(RunningWorkflowsGrain));
|
||||
public static WorkflowGrainClient GetNamedWorkflowGrain(this Cluster cluster, string workflowInstanceId) => cluster.GetWorkflowGrain($"{nameof(WorkflowGrain)}-{workflowInstanceId}");
|
||||
public static BookmarkGrainClient GetNamedBookmarkGrain(this Cluster cluster, string hash) => cluster.GetBookmarkGrain($"{nameof(BookmarkGrain)}-{hash}");
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Middleware.Workflows;
|
||||
using Elsa.Workflows.Core.Pipelines.WorkflowExecution;
|
||||
using Elsa.Workflows.Runtime.Middleware.Workflows;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extensions to <see cref="IWorkflowExecutionPipelineBuilder"/> that add various middleware components.
|
||||
/// </summary>
|
||||
public static class WorkflowExecutionPipelineBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the workflow execution pipeline with commonly used components for Proto Actor.
|
||||
/// </summary>
|
||||
public static IWorkflowExecutionPipelineBuilder UseProtoActorRuntimePipeline(this IWorkflowExecutionPipelineBuilder pipelineBuilder) =>
|
||||
pipelineBuilder
|
||||
.Reset()
|
||||
.UsePersistentVariables()
|
||||
.UseBookmarkPersistence()
|
||||
.UseWorkflowExecutionLogPersistence()
|
||||
.UseDefaultActivityScheduler();
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using Elsa.Workflows.Core.Features;
|
||||
using Elsa.Workflows.Core.Middleware.Activities;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Adds an extension method to the <see cref="WorkflowsFeature"/> that installs a default workflow runtime execution pipeline.
|
||||
/// </summary>
|
||||
public static class WorkflowsFeatureExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Installs a default workflow runtime execution pipeline.
|
||||
/// </summary>
|
||||
public static WorkflowsFeature WithProtoActorRuntimeWorkflowExecutionPipeline(this WorkflowsFeature workflowsFeature) =>
|
||||
workflowsFeature.WithWorkflowExecutionPipeline(pipeline =>
|
||||
pipeline.UseProtoActorRuntimePipeline());
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
|
|
@ -5,6 +6,7 @@ using Elsa.ProtoActor.Grains;
|
|||
using Elsa.ProtoActor.HostedServices;
|
||||
using Elsa.ProtoActor.Protos;
|
||||
using Elsa.ProtoActor.Services;
|
||||
using Elsa.Workflows.Core.Features;
|
||||
using Elsa.Workflows.Runtime.Features;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -23,6 +25,7 @@ namespace Elsa.ProtoActor.Features;
|
|||
/// <summary>
|
||||
/// Installs the Proto Actor feature to host & execute workflow instances.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(WorkflowsFeature))]
|
||||
[DependsOn(typeof(WorkflowRuntimeFeature))]
|
||||
public class ProtoActorFeature : FeatureBase
|
||||
{
|
||||
|
|
@ -34,6 +37,9 @@ public class ProtoActorFeature : FeatureBase
|
|||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
// Configure default workflow execution pipeline suitable for Proto Actor.
|
||||
Module.UseWorkflows(workflows => workflows.WithProtoActorRuntimeWorkflowExecutionPipeline());
|
||||
|
||||
// Configure runtime with ProtoActor workflow runtime.
|
||||
Module.Configure<WorkflowRuntimeFeature>().WorkflowRuntime = sp => ActivatorUtilities.CreateInstance<ProtoActorWorkflowRuntime>(sp);
|
||||
}
|
||||
|
|
@ -89,7 +95,6 @@ public class ProtoActorFeature : FeatureBase
|
|||
var clusterProvider = ClusterProvider(sp);
|
||||
var system = new ActorSystem(systemConfig).WithServiceProvider(sp);
|
||||
var workflowGrainProps = system.DI().PropsFor<WorkflowGrainActor>();
|
||||
var bookmarkGrainProps = system.DI().PropsFor<BookmarkGrainActor>();
|
||||
var workflowRegistryGrainProps = system.DI().PropsFor<RunningWorkflowsGrainActor>();
|
||||
|
||||
var clusterConfig = ClusterConfig
|
||||
|
|
@ -99,7 +104,6 @@ public class ProtoActorFeature : FeatureBase
|
|||
.WithActorActivationTimeout(TimeSpan.FromHours(1))
|
||||
.WithActorSpawnVerificationTimeout(TimeSpan.FromHours(1))
|
||||
.WithClusterKind(WorkflowGrainActor.Kind, workflowGrainProps)
|
||||
.WithClusterKind(BookmarkGrainActor.Kind, bookmarkGrainProps)
|
||||
.WithClusterKind(RunningWorkflowsGrainActor.Kind, workflowRegistryGrainProps)
|
||||
;
|
||||
|
||||
|
|
@ -130,7 +134,6 @@ public class ProtoActorFeature : FeatureBase
|
|||
// Actors.
|
||||
services
|
||||
.AddTransient(sp => new WorkflowGrainActor((context, _) => ActivatorUtilities.CreateInstance<WorkflowGrain>(sp, context)))
|
||||
.AddTransient(sp => new BookmarkGrainActor((context, _) => ActivatorUtilities.CreateInstance<BookmarkGrain>(sp, context)))
|
||||
.AddTransient(sp => new RunningWorkflowsGrainActor((context, _) => ActivatorUtilities.CreateInstance<RunningWorkflowsGrain>(sp, context)))
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.ProtoActor.Extensions;
|
||||
using Elsa.ProtoActor.Protos;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Proto;
|
||||
using Proto.Cluster;
|
||||
using Proto.Persistence;
|
||||
using Proto.Persistence.SnapshotStrategies;
|
||||
|
||||
namespace Elsa.ProtoActor.Grains;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a registry of bookmarks.
|
||||
/// </summary>
|
||||
public class BookmarkGrain : BookmarkGrainBase
|
||||
{
|
||||
private const int EventsPerSnapshot = 100;
|
||||
private ICollection<StoredBookmark> _bookmarks = new List<StoredBookmark>();
|
||||
private readonly Persistence _persistence;
|
||||
|
||||
/// <inheritdoc />
|
||||
public BookmarkGrain(IProvider provider, IContext context) : base(context)
|
||||
{
|
||||
_persistence = Persistence.WithEventSourcingAndSnapshotting(
|
||||
provider,
|
||||
provider,
|
||||
BookmarkHash,
|
||||
ApplyEvent,
|
||||
ApplySnapshot,
|
||||
new IntervalStrategy(EventsPerSnapshot),
|
||||
GetState);
|
||||
}
|
||||
|
||||
private string BookmarkHash => Context.ClusterIdentity()!.Identity;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task OnStarted() => await _persistence.RecoverStateAsync();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Empty> Store(StoreBookmarksRequest request)
|
||||
{
|
||||
var bookmarks = request.BookmarkIds.Select(x => new StoredBookmark
|
||||
{
|
||||
WorkflowInstanceId = request.WorkflowInstanceId,
|
||||
CorrelationId = request.CorrelationId,
|
||||
BookmarkId = x
|
||||
}).ToList();
|
||||
|
||||
await _persistence.PersistRollingEventAsync(new BookmarksStored(bookmarks), EventsPerSnapshot);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Empty> RemoveByWorkflow(RemoveBookmarksByWorkflowRequest request)
|
||||
{
|
||||
await _persistence.PersistRollingEventAsync(new BookmarksRemovedByWorkflow(request.WorkflowInstanceId), EventsPerSnapshot);
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<ResolveBookmarksResponse> Resolve(ResolveBookmarksRequest request)
|
||||
{
|
||||
var response = new ResolveBookmarksResponse();
|
||||
var query = _bookmarks.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(request.CorrelationId))
|
||||
query = query.Where(x => x.CorrelationId == request.CorrelationId);
|
||||
|
||||
response.Bookmarks.AddRange(query);
|
||||
|
||||
return Task.FromResult(response);
|
||||
}
|
||||
|
||||
private void ApplySnapshot(Snapshot snapshot)
|
||||
{
|
||||
var bookmarkSnapshot = (BookmarkSnapshot)snapshot.State;
|
||||
_bookmarks = bookmarkSnapshot.Bookmarks;
|
||||
}
|
||||
|
||||
private void ApplyEvent(Event @event)
|
||||
{
|
||||
switch (@event.Data)
|
||||
{
|
||||
case BookmarksStored bookmarksStored:
|
||||
_bookmarks.AddRange(bookmarksStored.Bookmarks);
|
||||
break;
|
||||
case BookmarksRemovedByWorkflow bookmarksRemovedByWorkflow:
|
||||
_bookmarks.RemoveWhere(x => x.WorkflowInstanceId == bookmarksRemovedByWorkflow.WorkflowInstanceId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private object GetState() => new BookmarkSnapshot(_bookmarks);
|
||||
}
|
||||
|
|
@ -13,12 +13,6 @@ service WorkflowGrain {
|
|||
rpc ImportState(ImportWorkflowStateRequest) returns (ImportWorkflowStateResponse);
|
||||
}
|
||||
|
||||
service BookmarkGrain {
|
||||
rpc Store(StoreBookmarksRequest) returns (google.protobuf.Empty);
|
||||
rpc RemoveByWorkflow(RemoveBookmarksByWorkflowRequest) returns (google.protobuf.Empty);
|
||||
rpc Resolve (ResolveBookmarksRequest) returns (ResolveBookmarksResponse);
|
||||
}
|
||||
|
||||
service RunningWorkflowsGrain {
|
||||
rpc Register(RegisterRunningWorkflowRequest) returns (google.protobuf.Empty);
|
||||
rpc Unregister(UnregisterRunningWorkflowRequest) returns (google.protobuf.Empty);
|
||||
|
|
|
|||
|
|
@ -76,32 +76,6 @@ message BookmarkDto {
|
|||
optional string CallbackMethodName = 8;
|
||||
}
|
||||
|
||||
// BookmarkGrain.
|
||||
message StoreBookmarksRequest {
|
||||
string WorkflowInstanceId = 1;
|
||||
string CorrelationId = 2;
|
||||
repeated string BookmarkIds = 3;
|
||||
}
|
||||
|
||||
message RemoveBookmarksByWorkflowRequest {
|
||||
string WorkflowInstanceId = 1;
|
||||
}
|
||||
|
||||
message ResolveBookmarksRequest {
|
||||
string ActivityTypeName = 1;
|
||||
string CorrelationId = 2;
|
||||
}
|
||||
|
||||
message ResolveBookmarksResponse {
|
||||
repeated StoredBookmark Bookmarks = 1;
|
||||
}
|
||||
|
||||
message StoredBookmark {
|
||||
string WorkflowInstanceId = 1;
|
||||
string CorrelationId = 2;
|
||||
string BookmarkId = 3;
|
||||
}
|
||||
|
||||
// RunningWorkflowsGrain.
|
||||
message RegisterRunningWorkflowRequest {
|
||||
string DefinitionId = 1;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Elsa.Workflows.Core.Contracts;
|
|||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Entities;
|
||||
using Proto.Cluster;
|
||||
|
||||
namespace Elsa.ProtoActor.Services;
|
||||
|
|
@ -18,6 +19,7 @@ public class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
private readonly Cluster _cluster;
|
||||
private readonly IWorkflowStateSerializer _workflowStateSerializer;
|
||||
private readonly ITriggerStore _triggerStore;
|
||||
private readonly IBookmarkStore _bookmarkStore;
|
||||
private readonly IIdentityGenerator _identityGenerator;
|
||||
private readonly IBookmarkHasher _hasher;
|
||||
private readonly IWorkflowDefinitionService _workflowDefinitionService;
|
||||
|
|
@ -30,6 +32,7 @@ public class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
Cluster cluster,
|
||||
IWorkflowStateSerializer workflowStateSerializer,
|
||||
ITriggerStore triggerStore,
|
||||
IBookmarkStore bookmarkStore,
|
||||
IIdentityGenerator identityGenerator,
|
||||
IBookmarkHasher hasher,
|
||||
IWorkflowDefinitionService workflowDefinitionService,
|
||||
|
|
@ -38,6 +41,7 @@ public class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
_cluster = cluster;
|
||||
_workflowStateSerializer = workflowStateSerializer;
|
||||
_triggerStore = triggerStore;
|
||||
_bookmarkStore = bookmarkStore;
|
||||
_identityGenerator = identityGenerator;
|
||||
_hasher = hasher;
|
||||
_workflowDefinitionService = workflowDefinitionService;
|
||||
|
|
@ -154,19 +158,13 @@ public class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
public async Task<ICollection<WorkflowExecutionResult>> ResumeWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsRuntimeOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var hash = _hasher.Hash(activityTypeName, bookmarkPayload);
|
||||
var client = _cluster.GetNamedBookmarkGrain(hash);
|
||||
|
||||
var request = new ResolveBookmarksRequest
|
||||
{
|
||||
ActivityTypeName = activityTypeName,
|
||||
CorrelationId = options.CorrelationId.EmptyIfNull(),
|
||||
};
|
||||
|
||||
var bookmarksResponse = await client.Resolve(request, cancellationToken);
|
||||
var bookmarks = bookmarksResponse!.Bookmarks;
|
||||
return await ResumeWorkflowsAsync(bookmarks, new ResumeWorkflowRuntimeOptions(options.CorrelationId, options.WorkflowInstanceId, Input: options.Input), cancellationToken);
|
||||
var correlationId = options.CorrelationId;
|
||||
var workflowInstanceId = options.WorkflowInstanceId;
|
||||
var filter = new BookmarkFilter { Hash = hash, CorrelationId = correlationId, WorkflowInstanceId = workflowInstanceId };
|
||||
var bookmarks = await _bookmarkStore.FindManyAsync(filter, cancellationToken);
|
||||
return await ResumeWorkflowsAsync(bookmarks, new ResumeWorkflowRuntimeOptions(correlationId, Input: options.Input), cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TriggerWorkflowsResult> TriggerWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsRuntimeOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
@ -243,18 +241,9 @@ public class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateBookmarkAsync(Workflows.Runtime.Entities.StoredBookmark bookmark, CancellationToken cancellationToken = default)
|
||||
public async Task UpdateBookmarkAsync(StoredBookmark bookmark, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bookmarkClient = _cluster.GetNamedBookmarkGrain(bookmark.Hash);
|
||||
|
||||
var storeBookmarkRequest = new StoreBookmarksRequest
|
||||
{
|
||||
WorkflowInstanceId = bookmark.WorkflowInstanceId,
|
||||
CorrelationId = bookmark.CorrelationId.EmptyIfNull()
|
||||
};
|
||||
|
||||
storeBookmarkRequest.BookmarkIds.Add(bookmark.BookmarkId);
|
||||
await bookmarkClient.Store(storeBookmarkRequest, cancellationToken);
|
||||
await _bookmarkStore.SaveAsync(bookmark, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -292,36 +281,21 @@ public class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
return resumedWorkflows;
|
||||
}
|
||||
|
||||
private async Task StoreBookmarksAsync(string instanceId, ICollection<Bookmark> bookmarks, string? correlationId, CancellationToken cancellationToken = default)
|
||||
private async Task StoreBookmarksAsync(string workflowInstanceId, ICollection<Bookmark> bookmarks, string? correlationId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var groupedBookmarks = bookmarks.GroupBy(x => x.Hash);
|
||||
|
||||
foreach (var groupedBookmark in groupedBookmarks)
|
||||
foreach (var bookmark in bookmarks)
|
||||
{
|
||||
var bookmarkClient = _cluster.GetNamedBookmarkGrain(groupedBookmark.Key);
|
||||
|
||||
var storeBookmarkRequest = new StoreBookmarksRequest
|
||||
{
|
||||
WorkflowInstanceId = instanceId,
|
||||
CorrelationId = correlationId.EmptyIfNull()
|
||||
};
|
||||
|
||||
storeBookmarkRequest.BookmarkIds.AddRange(groupedBookmark.Select(x => x.Id));
|
||||
await bookmarkClient.Store(storeBookmarkRequest, cancellationToken);
|
||||
var storedBookmark = new StoredBookmark(bookmark.Name, bookmark.Hash, workflowInstanceId, bookmark.Id, correlationId, bookmark.Payload);
|
||||
await _bookmarkStore.SaveAsync(storedBookmark, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveBookmarksAsync(string instanceId, IEnumerable<Bookmark> bookmarks, CancellationToken cancellationToken = default)
|
||||
private async Task RemoveBookmarksAsync(string workflowInstanceId, IEnumerable<Bookmark> bookmarks, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var groupedBookmarks = bookmarks.GroupBy(x => x.Hash);
|
||||
|
||||
foreach (var groupedBookmark in groupedBookmarks)
|
||||
foreach (var bookmark in bookmarks)
|
||||
{
|
||||
var bookmarkClient = _cluster.GetNamedBookmarkGrain(groupedBookmark.Key);
|
||||
await bookmarkClient.RemoveByWorkflow(new RemoveBookmarksByWorkflowRequest
|
||||
{
|
||||
WorkflowInstanceId = instanceId
|
||||
}, cancellationToken);
|
||||
var filter = new BookmarkFilter { Hash = bookmark.Hash, WorkflowInstanceId = workflowInstanceId };
|
||||
await _bookmarkStore.DeleteAsync(filter, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,18 +338,11 @@ public class ProtoActorWorkflowRuntime : IWorkflowRuntime
|
|||
private async Task<IEnumerable<WorkflowMatch>> FindResumableWorkflowsAsync(WorkflowsFilter workflowsFilter, CancellationToken cancellationToken)
|
||||
{
|
||||
var hash = _hasher.Hash(workflowsFilter.ActivityTypeName, workflowsFilter.BookmarkPayload);
|
||||
var client = _cluster.GetNamedBookmarkGrain(hash);
|
||||
|
||||
var request = new ResolveBookmarksRequest
|
||||
{
|
||||
ActivityTypeName = workflowsFilter.ActivityTypeName,
|
||||
CorrelationId = workflowsFilter.Options.CorrelationId.EmptyIfNull()
|
||||
};
|
||||
|
||||
var bookmarksResponse = await client.Resolve(request, cancellationToken);
|
||||
var bookmarks = bookmarksResponse!.Bookmarks;
|
||||
|
||||
var collectedWorkflows = bookmarks.Select(b => new ResumableWorkflowMatch(b.WorkflowInstanceId, default, workflowsFilter.Options.CorrelationId, b.BookmarkId)).ToList();
|
||||
var correlationId = workflowsFilter.Options.CorrelationId;
|
||||
var workflowInstanceId = workflowsFilter.Options.WorkflowInstanceId;
|
||||
var filter = new BookmarkFilter { Hash = hash, CorrelationId = correlationId, WorkflowInstanceId = workflowInstanceId };
|
||||
var bookmarks = await _bookmarkStore.FindManyAsync(filter, cancellationToken);
|
||||
var collectedWorkflows = bookmarks.Select(b => new ResumableWorkflowMatch(b.WorkflowInstanceId, default, correlationId, b.BookmarkId)).ToList();
|
||||
return collectedWorkflows;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,4 @@ namespace Elsa.ProtoActor;
|
|||
|
||||
internal record WorkflowSnapshot(string DefinitionId, string InstanceId, int Version, WorkflowState WorkflowState, IDictionary<string, object>? Input);
|
||||
|
||||
internal record BookmarkSnapshot(ICollection<StoredBookmark> Bookmarks);
|
||||
|
||||
internal record WorkflowRegistrySnapshot(ICollection<WorkflowInstanceEntry> Entries);
|
||||
|
|
@ -27,6 +27,9 @@ public static class ExpressionExecutionContextExtensions
|
|||
[InputKey] = input
|
||||
};
|
||||
|
||||
public static bool TryGetWorkflowExecutionContext(this ExpressionExecutionContext context, out WorkflowExecutionContext workflowExecutionContext) =>
|
||||
context.TransientProperties.TryGetValue(WorkflowExecutionContextKey, out workflowExecutionContext!);
|
||||
|
||||
public static WorkflowExecutionContext GetWorkflowExecutionContext(this ExpressionExecutionContext context) => (WorkflowExecutionContext)context.TransientProperties[WorkflowExecutionContextKey];
|
||||
public static ActivityExecutionContext GetActivityExecutionContext(this ExpressionExecutionContext context) => (ActivityExecutionContext)context.TransientProperties[ActivityExecutionContextKey];
|
||||
public static bool TryGetActivityExecutionContext(this ExpressionExecutionContext context, out ActivityExecutionContext activityExecutionContext) => context.TransientProperties.TryGetValue(ActivityExecutionContextKey, out activityExecutionContext!);
|
||||
|
|
|
|||
|
|
@ -6,13 +6,38 @@ using Elsa.Workflows.Management.Models;
|
|||
|
||||
namespace Elsa.Workflows.Management.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a store of workflow instances.
|
||||
/// </summary>
|
||||
public interface IWorkflowInstanceStore
|
||||
{
|
||||
Task<WorkflowInstance?> FindAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstance>> FindManyAsync(WorkflowInstanceFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstance>> FindManyAsync<TOrderBy>(WorkflowInstanceFilter filter, PageArgs pageArgs, WorkflowInstanceOrder<TOrderBy> order, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstance>> FindManyAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstance>> FindManyAsync<TOrderBy>(WorkflowInstanceFilter filter, WorkflowInstanceOrder<TOrderBy> order, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstanceSummary>> SummarizeManyAsync(WorkflowInstanceFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstanceSummary>> SummarizeManyAsync<TOrderBy>(WorkflowInstanceFilter filter, PageArgs pageArgs, WorkflowInstanceOrder<TOrderBy> order, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstanceSummary>> SummarizeManyAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstanceSummary>> SummarizeManyAsync<TOrder>(WorkflowInstanceFilter filter, WorkflowInstanceOrder<TOrder> order, CancellationToken cancellationToken = default);
|
||||
Task SaveAsync(WorkflowInstance record, CancellationToken cancellationToken = default);
|
||||
Task SaveManyAsync(IEnumerable<WorkflowInstance> records, CancellationToken cancellationToken = default);
|
||||
Task<bool> DeleteAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<int> DeleteManyAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A filter for querying workflow instances.
|
||||
/// </summary>
|
||||
public class WorkflowInstanceFilter
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public ICollection<string>? Ids { get; set; }
|
||||
public string? SearchTerm { get; set; }
|
||||
public string? DefinitionId { get; set; }
|
||||
public string? DefinitionVersionId { get; set; }
|
||||
public ICollection<string>? DefinitionIds { get; set; }
|
||||
public ICollection<string>? DefinitionVersionIds { get; set; }
|
||||
public int? Version { get; set; }
|
||||
public string? CorrelationId { get; set; }
|
||||
public ICollection<string>? CorrelationIds { get; set; }
|
||||
|
|
@ -25,7 +50,9 @@ public class WorkflowInstanceFilter
|
|||
if (!string.IsNullOrWhiteSpace(filter.Id)) query = query.Where(x => x.Id == filter.Id);
|
||||
if (filter.Ids != null) query = query.Where(x => filter.Ids.Contains(x.Id));
|
||||
if (!string.IsNullOrWhiteSpace(filter.DefinitionId)) query = query.Where(x => x.DefinitionId == filter.DefinitionId);
|
||||
if (!string.IsNullOrWhiteSpace(filter.DefinitionVersionId)) query = query.Where(x => x.DefinitionVersionId == filter.DefinitionVersionId);
|
||||
if (filter.DefinitionIds != null) query = query.Where(x => filter.DefinitionIds.Contains(x.DefinitionId));
|
||||
if (filter.DefinitionVersionIds != null) query = query.Where(x => filter.DefinitionVersionIds.Contains(x.DefinitionVersionId));
|
||||
if (filter.Version != null) query = query.Where(x => x.Version == filter.Version);
|
||||
if (!string.IsNullOrWhiteSpace(filter.CorrelationId)) query = query.Where(x => x.CorrelationId == filter.CorrelationId);
|
||||
if (filter.CorrelationIds != null) query = query.Where(x => filter.CorrelationIds.Contains(x.CorrelationId!));
|
||||
|
|
@ -53,24 +80,4 @@ public class WorkflowInstanceFilter
|
|||
/// </summary>
|
||||
public class WorkflowInstanceOrder<TProp> : OrderDefinition<WorkflowInstance, TProp>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a store of workflow instances.
|
||||
/// </summary>
|
||||
public interface IWorkflowInstanceStore
|
||||
{
|
||||
Task<WorkflowInstance?> FindAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstance>> FindManyAsync(WorkflowInstanceFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstance>> FindManyAsync<TOrderBy>(WorkflowInstanceFilter filter, PageArgs pageArgs, WorkflowInstanceOrder<TOrderBy> order, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstance>> FindManyAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstance>> FindManyAsync<TOrderBy>(WorkflowInstanceFilter filter, WorkflowInstanceOrder<TOrderBy> order, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstanceSummary>> SummarizeManyAsync(WorkflowInstanceFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default);
|
||||
Task<Page<WorkflowInstanceSummary>> SummarizeManyAsync<TOrderBy>(WorkflowInstanceFilter filter, PageArgs pageArgs, WorkflowInstanceOrder<TOrderBy> order, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstanceSummary>> SummarizeManyAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<WorkflowInstanceSummary>> SummarizeManyAsync<TOrder>(WorkflowInstanceFilter filter, WorkflowInstanceOrder<TOrder> order, CancellationToken cancellationToken = default);
|
||||
Task SaveAsync(WorkflowInstance record, CancellationToken cancellationToken = default);
|
||||
Task SaveManyAsync(IEnumerable<WorkflowInstance> records, CancellationToken cancellationToken = default);
|
||||
Task<bool> DeleteAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
Task<int> DeleteManyAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ using Elsa.Workflows.Runtime.Models.Notifications;
|
|||
|
||||
namespace Elsa.Workflows.Runtime.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Extracts triggers from workflow definitions.
|
||||
/// </summary>
|
||||
public interface ITriggerIndexer
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -20,9 +23,4 @@ public interface ITriggerIndexer
|
|||
/// Indexes triggers of the specified workflow.
|
||||
/// </summary>
|
||||
Task<IndexedWorkflowTriggers> IndexTriggersAsync(Workflow workflow, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Send message of all triggers in the trigger store to populate the routetable on startup
|
||||
/// </summary>
|
||||
Task<IndexedWorkflowTriggers> IndexAllTriggersAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -28,18 +28,36 @@ public static class ModuleExtensions
|
|||
return module;
|
||||
}
|
||||
|
||||
public static WorkflowRuntimeFeature UseDefaultRuntime(this WorkflowRuntimeFeature feature, Action<DefaultRuntimeFeature>? configure = default)
|
||||
/// <summary>
|
||||
/// Configures the default workflow runtime.
|
||||
/// </summary>
|
||||
/// <param name="feature">The workflow runtime feature.</param>
|
||||
/// <param name="configure">A callback that configures the default workflow runtime.</param>
|
||||
/// <returns>The workflow runtime feature.</returns>
|
||||
public static WorkflowRuntimeFeature UseDefaultRuntime(this WorkflowRuntimeFeature feature, Action<DefaultWorkflowRuntimeFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the execution log records feature.
|
||||
/// </summary>
|
||||
/// <param name="feature">The workflow runtime feature.</param>
|
||||
/// <param name="configure">A callback that configures the execution log records feature.</param>
|
||||
/// <returns>The workflow runtime feature.</returns>
|
||||
public static WorkflowRuntimeFeature UseExecutionLogRecords(this WorkflowRuntimeFeature feature, Action<ExecutionLogRecordFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
return feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the workflow state exporter feature.
|
||||
/// </summary>
|
||||
/// <param name="feature">The workflow runtime feature.</param>
|
||||
/// <param name="configure">A callback that configures the workflow state exporter feature.</param>
|
||||
/// <returns>The workflow runtime feature.</returns>
|
||||
public static WorkflowRuntimeFeature UseAsyncWorkflowStateExporter(this WorkflowRuntimeFeature feature, Action<AsyncWorkflowStateExporterFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Configure(configure);
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Features;
|
||||
|
||||
public class DefaultRuntimeFeature : FeatureBase
|
||||
{
|
||||
public DefaultRuntimeFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Installs the default runtime services.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(WorkflowRuntimeFeature))]
|
||||
public class DefaultWorkflowRuntimeFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public DefaultWorkflowRuntimeFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A factory that instantiates an <see cref="IWorkflowStateStore"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IWorkflowStateStore> WorkflowStateStore { get; set; } = sp => ActivatorUtilities.CreateInstance<MemoryWorkflowStateStore>(sp);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services
|
||||
// Replaceable factories
|
||||
.AddSingleton(WorkflowStateStore)
|
||||
|
||||
// Memory stores.
|
||||
.AddMemoryStore<WorkflowState, MemoryWorkflowStateStore>();
|
||||
}
|
||||
}
|
||||
|
|
@ -47,12 +47,7 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
/// A factory that instantiates an <see cref="IWorkflowDispatcher"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IWorkflowDispatcher> WorkflowDispatcher { get; set; } = sp => ActivatorUtilities.CreateInstance<TaskBasedWorkflowDispatcher>(sp);
|
||||
|
||||
/// <summary>
|
||||
/// A factory that instantiates an <see cref="IWorkflowStateStore"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IWorkflowStateStore> WorkflowStateStore { get; set; } = sp => ActivatorUtilities.CreateInstance<MemoryWorkflowStateStore>(sp);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A factory that instantiates an <see cref="IBookmarkStore"/>.
|
||||
/// </summary>
|
||||
|
|
@ -114,8 +109,7 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
Module
|
||||
.ConfigureHostedService<RegisterDescriptors>()
|
||||
.ConfigureHostedService<RegisterExpressionSyntaxDescriptors>()
|
||||
.ConfigureHostedService<PopulateWorkflowDefinitionStore>()
|
||||
.ConfigureHostedService<PopulateRouteTable>();
|
||||
.ConfigureHostedService<PopulateWorkflowDefinitionStore>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
|
|
@ -133,7 +127,6 @@ public class WorkflowRuntimeFeature : FeatureBase
|
|||
.AddSingleton<IBackgroundActivityInvoker, DefaultBackgroundActivityInvoker>()
|
||||
.AddSingleton(WorkflowRuntime)
|
||||
.AddSingleton(WorkflowDispatcher)
|
||||
.AddSingleton(WorkflowStateStore)
|
||||
.AddSingleton(BookmarkStore)
|
||||
.AddSingleton(WorkflowTriggerStore)
|
||||
.AddSingleton(WorkflowExecutionLogStore)
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.HostedServices;
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously updates the routetable from the triggers.
|
||||
/// </summary>
|
||||
public class PopulateRouteTable : IHostedService
|
||||
{
|
||||
private readonly ITriggerIndexer _triggerIndexer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public PopulateRouteTable(ITriggerIndexer triggerIndexer)
|
||||
{
|
||||
_triggerIndexer = triggerIndexer;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _triggerIndexer.IndexAllTriggersAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -62,27 +62,6 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
return await IndexTriggersAsync(workflow, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IndexedWorkflowTriggers> IndexAllTriggersAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var emptyTriggerList = new List<StoredTrigger>(0);
|
||||
|
||||
// Get current triggers
|
||||
var filter = new TriggerFilter();
|
||||
var allTriggersTriggers = await _triggerStore.FindManyAsync(filter, cancellationToken);
|
||||
|
||||
|
||||
//workflow definition already deleted so you do not have one
|
||||
var workflow = new Workflow();
|
||||
|
||||
var indexedWorkflow = new IndexedWorkflowTriggers(workflow, allTriggersTriggers.ToList(), emptyTriggerList, emptyTriggerList);
|
||||
|
||||
// Publish event.
|
||||
await _eventPublisher.PublishAsync(new WorkflowTriggersIndexed(indexedWorkflow), cancellationToken);
|
||||
return indexedWorkflow;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IndexedWorkflowTriggers> IndexTriggersAsync(Workflow workflow, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
|
|||
20
src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/Dockerfile
Normal file
20
src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/Elsa.Samples.ProtoActorRuntime.csproj", "src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/"]
|
||||
RUN dotnet restore "src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/Elsa.Samples.ProtoActorRuntime.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/src/samples/aspnet/Elsa.Samples.ProtoActorRuntime"
|
||||
RUN dotnet build "Elsa.Samples.ProtoActorRuntime.csproj" -c Release -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "Elsa.Samples.ProtoActorRuntime.csproj" -c Release -o /app/publish
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "Elsa.Samples.ProtoActorRuntime.dll"]
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\bundles\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.JavaScript\Elsa.JavaScript.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.ProtoActor\Elsa.ProtoActor.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Scheduling\Elsa.Scheduling.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Proto.Persistence.Sqlite" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
91
src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/Program.cs
Normal file
91
src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/Program.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using Elsa.EntityFrameworkCore.Modules.Labels;
|
||||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.JavaScript.Options;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Proto.Persistence.Sqlite;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!;
|
||||
var identitySection = configuration.GetSection("Identity");
|
||||
var identityTokenSection = identitySection.GetSection("Tokens");
|
||||
|
||||
// Add Elsa services.
|
||||
services
|
||||
.AddElsa(elsa => elsa
|
||||
.AddActivitiesFrom<Program>()
|
||||
.UseIdentity(identity =>
|
||||
{
|
||||
identity.IdentityOptions = options => identitySection.Bind(options);
|
||||
identity.TokenOptions = options => identityTokenSection.Bind(options);
|
||||
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
|
||||
})
|
||||
.UseDefaultAuthentication()
|
||||
.UseWorkflowManagement(management =>
|
||||
{
|
||||
// Use EF core for workflow definitions and instances.
|
||||
management.UseEntityFrameworkCore(m => m.UseSqlite(sqliteConnectionString));
|
||||
})
|
||||
.UseWorkflowRuntime(runtime =>
|
||||
{
|
||||
// Use EF core for triggers and bookmarks.
|
||||
runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString));
|
||||
|
||||
// Use EF core for execution log records.
|
||||
runtime.UseExecutionLogRecords(log => log.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)));
|
||||
|
||||
// Install a workflow state exporter to capture workflow states and store them in IWorkflowInstanceStore.
|
||||
runtime.UseAsyncWorkflowStateExporter();
|
||||
|
||||
// Use Proto.Actor for workflow execution.
|
||||
runtime.UseProtoActor(protoActor =>
|
||||
{
|
||||
protoActor.PersistenceProvider = _ => new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString));
|
||||
});
|
||||
})
|
||||
.UseLabels(labels => labels.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseScheduling()
|
||||
.UseWorkflowsApi(api => api.AddFastEndpointsAssembly<Program>())
|
||||
.UseJavaScript()
|
||||
.UseLiquid()
|
||||
.UseHttp()
|
||||
);
|
||||
|
||||
services.Configure<JintOptions>(options => options.AllowClrAccess = true);
|
||||
services.AddHandlersFrom<Program>();
|
||||
services.AddHealthChecks();
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
// Configure middleware pipeline.
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
||||
// CORS.
|
||||
app.UseCors();
|
||||
|
||||
// Health checks.
|
||||
app.MapHealthChecks("/");
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
// Elsa API endpoints for designer.
|
||||
app.UseWorkflowsApi();
|
||||
|
||||
// Captures unhandled exceptions and returns a JSON response.
|
||||
app.UseJsonSerializationErrorHandler();
|
||||
|
||||
// Elsa HTTP Endpoint activities
|
||||
app.UseWorkflows();
|
||||
|
||||
// Run.
|
||||
app.Run();
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:54982",
|
||||
"sslPort": 44376
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5117",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7180;http://localhost:5117",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/README.md
Normal file
10
src/samples/aspnet/Elsa.Samples.ProtoActorRuntime/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Server
|
||||
|
||||
This project represents an Elsa application that hosts workflows and exposes API endpoints to manage & execute workflows.
|
||||
|
||||
## Secrets
|
||||
The following are the secrets stored in hashed form in appsettings.json:
|
||||
|
||||
**API key**: `4E753976726458745954355043687772-e54d5a2c-33a3-4c05-a216-b09569062aed`
|
||||
**Admin user**: `admin`
|
||||
**Admin password**: `password`
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Elsa.Mediator": "Warning",
|
||||
"MassTransit": "Warning",
|
||||
"Microsoft.Extensions.Http": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"Microsoft.EntityFrameworkCore": "Warning",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"System.Net.Http": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;"
|
||||
},
|
||||
"Identity": {
|
||||
"Tokens": {
|
||||
"SigningKey": "secret-signing-key",
|
||||
"AccessTokenLifetime": "1:00:00:00",
|
||||
"RefreshTokenLifetime": "1:00:10:00"
|
||||
},
|
||||
"Roles": [{
|
||||
"Id": "admin",
|
||||
"Name": "Administrator",
|
||||
"Permissions": ["*"]
|
||||
}],
|
||||
"Users": [
|
||||
{
|
||||
"Id": "a2323f46-42db-4e15-af8b-94238717d817",
|
||||
"Name": "admin",
|
||||
"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=",
|
||||
"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
|
||||
"Roles": ["admin"]
|
||||
}
|
||||
],
|
||||
"Applications": [{
|
||||
"id": "529572c2df854b13807b8bf23f1784cd",
|
||||
"name": "Postman",
|
||||
"roles": [
|
||||
"admin"
|
||||
],
|
||||
"clientId": "Nu9vrdXtYT5PChwr",
|
||||
"clientSecret": "011pp2C$|j01-qrMZpC9VC0F00XCJq(5",
|
||||
"hashedApiKey": "d0rDld3A+ugKmdctGtMzOLTYjQFkOlUWN+kt0VyW9D0=",
|
||||
"hashedApiKeySalt": "EnutGOyy5MuJWV0fF5jCQiciK7a8PU/DRF+fr6nekSY=",
|
||||
"hashedClientSecret": "ERia2zBcCSWb/9dvB0grQ9yf7fWgFrClNeR8A5RMTzk=",
|
||||
"hashedClientSecretSalt": "z3z8KmzHt+xkAj/zYTXcB8I7y0xAkLm95v4Er/oNqiY="
|
||||
}]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue