From e2ffafc736c35c32762534e8c31c11bac1bdc118 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 2 Sep 2022 13:18:53 +0200 Subject: [PATCH] Refactor proto actor runtime with snapshot support (#3283) * Initial working grain * Incremental work on actor bookmark management & resuming wokflows * Upgrade Proto Actor package references * Fix lifetime of actors * Implement bookmark and resumption * Update Program.cs * Update bookmark event publishing * Refactor proto actor features API * Implement snapshotting * Update snapshotting * Add in-memory provider and use it by default * Update server bundle to use proto actor sqlite persistence by default --- common.props | 2 +- .../Elsa.WorkflowServer.Web.csproj | 4 + .../Elsa.WorkflowServer.Web/Program.cs | 10 +- .../Elsa.WorkflowServer.Web/appsettings.json | 3 + .../Elsa.Api.Common/Abstractions/Endpoints.cs | 8 +- .../src/modules/login/plugin.tsx | 18 +- .../Implementations/Worker.cs | 9 +- .../Extensions/RouteTableExtensions.cs | 9 +- .../Middleware/HttpTriggerMiddleware.cs | 22 +- .../Handlers/JobExecutedHandler.cs | 14 +- .../Features/MassTransitDispatchersFeature.cs | 2 +- .../ProtoActorSystem.cs | 28 ++- .../DependencyInjectionExtensions.cs | 12 +- .../KubernetesProtoActorFeature.cs | 55 +++++ .../KubernetesProviderOptions.cs | 11 - .../ProtoActorBuilderExtensions.cs | 47 ---- .../Elsa.ProtoActor/Elsa.ProtoActor.csproj | 19 +- src/modules/Elsa.ProtoActor/Events.cs | 7 + .../Extensions/CollectionExtensions.cs | 19 ++ .../DependencyInjectionExtensions.cs | 14 +- .../Extensions/ProtoInputExtensions.cs | 2 +- .../ProtoActorFeature.cs | 75 +++--- .../Elsa.ProtoActor/Grains/BookmarkGrain.cs | 82 +++++++ .../Grains/WorkflowDefinitionGrain.cs | 50 ---- .../Elsa.ProtoActor/Grains/WorkflowGrain.cs | 187 +++++++++++++++ .../Grains/WorkflowInstanceGrain.cs | 173 -------------- .../Implementations/GrainClientFactory.cs | 18 -- .../Implementations/InMemoryProvider.cs | 89 +++++++ .../ProtoActorWorkflowInvoker.cs | 176 -------------- .../ProtoActorWorkflowRuntime.cs | 76 ++++++ .../Elsa.ProtoActor/Protos/Grains.proto | 13 +- .../Elsa.ProtoActor/Protos/Messages.proto | 63 +++-- src/modules/Elsa.ProtoActor/Snapshots.cs | 9 + .../ScheduleWorkflowsHostedService.cs | 3 +- .../WorkflowBookmarkScheduler.cs | 9 +- .../Elsa.Scheduling/Jobs/ResumeWorkflowJob.cs | 8 +- .../Services/IWorkflowBookmarkScheduler.cs | 5 +- .../ExecuteWorkflowDefinitionResult.cs | 37 --- .../Endpoints/Events/Trigger/Endpoint.cs | 22 +- .../BulkPublish/Endpoint.cs | 2 +- .../BulkRetract/Endpoint.cs | 2 +- .../WorkflowDefinitions/Execute/Endpoint.cs | 17 +- .../WorkflowDefinitions/Execute/Models.cs | 10 +- .../WorkflowDefinitions/Export/Endpoint.cs | 2 +- .../WorkflowDefinitions/Get/Endpoint.cs | 2 +- .../WorkflowDefinitions/Publish/Endpoint.cs | 2 +- .../WorkflowDefinitions/Retract/Endpoint.cs | 2 +- .../WorkflowDefinitions/Version/List.cs | 2 +- .../Implementations/WorkflowRunner.cs | 40 +++- .../Elsa.Workflows.Core/Models/Bookmark.cs | 2 +- .../Models/InvokeWorkflowResult.cs | 5 + .../Models/RunWorkflowResult.cs | 2 +- .../Models/WorkflowExecutionContext.cs | 4 +- .../Services/IWorkflowRunner.cs | 5 +- .../WorkflowDefinitionPublisher.cs | 14 +- .../EFCoreWorkflowDefinitionStore.cs | 9 +- .../Extensions/WorkflowBookmarkExtensions.cs | 3 +- .../MemoryWorkflowDefinitionStore.cs | 8 +- .../Services/IWorkflowDefinitionStore.cs | 9 +- .../Abstractions/StimulusHandler.cs | 34 +-- .../WorkflowInstructionInterpreter.cs | 54 ++--- .../Activities/DispatchWorkflow.cs | 26 +- .../DependencyInjectionExtensions.cs | 4 +- .../Features/WorkflowRuntimeFeature.cs | 27 +-- .../DispatchedWorkflowDefinitionWorker.cs | 9 +- .../DispatchedWorkflowInstanceWorker.cs | 14 +- .../PopulateWorkflowDefinitionStore.cs | 2 +- .../Implementations/BookmarkManager.cs | 14 +- .../Implementations/DefaultWorkflowInvoker.cs | 173 +++++++------- .../Implementations/DefaultWorkflowRuntime.cs | 21 ++ .../Implementations/StimulusInterpreter.cs | 40 ++-- .../WorkflowDefinitionService.cs | 11 +- .../WorkflowInstanceFactory.cs | 2 +- .../WorkflowInstructionExecutor.cs | 84 +++---- .../Implementations/WorkflowService.cs | 184 +++++++------- .../ResumeWorkflowInstructionInterpreter.cs | 224 +++++++++--------- .../TriggerWorkflowInstructionInterpreter.cs | 138 +++++------ .../PersistWorkflowInstanceMiddleware.cs | 8 +- .../Models/DispatchWorkflowInstanceRequest.cs | 4 +- .../Models/ExecuteWorkflowResult.cs | 6 + .../Models/IndexedWorkflowBookmarks.cs | 4 +- .../Elsa.Workflows.Runtime/Models/Stimulus.cs | 44 ++-- .../Notifications/WorkflowBookmarksDeleted.cs | 3 +- .../Notifications/WorkflowBookmarksSaved.cs | 3 +- .../Services/IBookmarkManager.cs | 4 +- .../Services/IStimulus.cs | 10 +- .../Services/IStimulusHandler.cs | 14 +- .../Services/IStimulusInterpreter.cs | 12 +- .../Services/IWorkflowDefinitionService.cs | 2 + .../Services/IWorkflowInstructionExecutor.cs | 22 +- .../Services/IWorkflowInvoker.cs | 37 --- .../Services/IWorkflowRuntime.cs | 19 ++ .../Services/IWorkflowService.cs | 44 ++-- .../ResumeWorkflowsStimulusHandler.cs | 38 +-- .../TriggerWorkflowsStimulusHandler.cs | 44 ++-- .../Stimuli/StandardStimulus.cs | 16 +- .../aspnet/Elsa.Samples.Web2/Program.cs | 5 +- .../WorkflowInvoker/Tests.cs | 45 ---- 98 files changed, 1510 insertions(+), 1487 deletions(-) create mode 100644 src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProtoActorFeature.cs delete mode 100644 src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProviderOptions.cs delete mode 100644 src/modules/Elsa.ProtoActor.Kubernetes/ProtoActorBuilderExtensions.cs create mode 100644 src/modules/Elsa.ProtoActor/Events.cs create mode 100644 src/modules/Elsa.ProtoActor/Extensions/CollectionExtensions.cs rename src/modules/Elsa.ProtoActor/{Configuration => Features}/ProtoActorFeature.cs (53%) create mode 100644 src/modules/Elsa.ProtoActor/Grains/BookmarkGrain.cs delete mode 100644 src/modules/Elsa.ProtoActor/Grains/WorkflowDefinitionGrain.cs create mode 100644 src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs delete mode 100644 src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs delete mode 100644 src/modules/Elsa.ProtoActor/Implementations/GrainClientFactory.cs create mode 100644 src/modules/Elsa.ProtoActor/Implementations/InMemoryProvider.cs delete mode 100644 src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowInvoker.cs create mode 100644 src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowRuntime.cs create mode 100644 src/modules/Elsa.ProtoActor/Snapshots.cs delete mode 100644 src/modules/Elsa.Workflows.Api/ApiResults/ExecuteWorkflowDefinitionResult.cs create mode 100644 src/modules/Elsa.Workflows.Core/Models/InvokeWorkflowResult.cs create mode 100644 src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowRuntime.cs create mode 100644 src/modules/Elsa.Workflows.Runtime/Models/ExecuteWorkflowResult.cs delete mode 100644 src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInvoker.cs create mode 100644 src/modules/Elsa.Workflows.Runtime/Services/IWorkflowRuntime.cs delete mode 100644 test/Elsa.IntegrationTests/WorkflowInvoker/Tests.cs diff --git a/common.props b/common.props index 5c1d5687e7..6b49f82550 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - Elsa Contributors + Elsa Workflows Team 2022 https://github.com/elsa-workflows/elsa-core https://github.com/elsa-workflows/elsa-core diff --git a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj index 97cf5b1148..c6d5f6a8b1 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj +++ b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj @@ -28,4 +28,8 @@ + + + + diff --git a/src/bundles/Elsa.WorkflowServer.Web/Program.cs b/src/bundles/Elsa.WorkflowServer.Web/Program.cs index 55f06c9c0b..dad0af7b3d 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/Program.cs +++ b/src/bundles/Elsa.WorkflowServer.Web/Program.cs @@ -15,6 +15,7 @@ using Elsa.Labels.EntityFrameworkCore.Sqlite; using Elsa.Labels.Extensions; using Elsa.Liquid.Extensions; +using Elsa.ProtoActor.Extensions; using Elsa.Scheduling.Extensions; using Elsa.WorkflowContexts.Extensions; using Elsa.Workflows.Api.Extensions; @@ -31,10 +32,14 @@ using FastEndpoints; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.Data.Sqlite; +using Proto.Persistence.Sqlite; +using Event = Elsa.Workflows.Core.Activities.Event; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; var configuration = builder.Configuration; +var dbConnectionString = configuration.GetConnectionString("Sqlite"); var identityOptions = new IdentityOptions(); var identitySection = configuration.GetSection("Identity"); identitySection.Bind(identityOptions); @@ -63,9 +68,10 @@ identity.CreateDefaultUser = true; identity.IdentityOptions = options => identitySection.Bind(options); }) + .UseRuntime(runtime => runtime.UseProtoActor(proto => proto.PersistenceProvider = _ => new SqliteProvider(new SqliteConnectionStringBuilder(dbConnectionString)))) .UseJobActivities() .UseScheduling() - .UseWorkflowPersistence(p => p.UseEntityFrameworkCore(ef => ef.UseSqlite())) + .UseWorkflowPersistence(p => p.UseEntityFrameworkCore(ef => ef.UseSqlite(dbConnectionString))) .UseWorkflowApiEndpoints() .UseJavaScript() .UseLiquid() @@ -103,8 +109,6 @@ serviceProvider.ConfigureDefaultWorkflowExecutionPipeline(pipeline => pipeline .UseWorkflowExecutionEvents() - .UseWorkflowExecutionLogPersistence() - .UsePersistence() .UseWorkflowContexts() .UseStackBasedActivityScheduler() ); diff --git a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json index e1673f88ec..a61810ff82 100644 --- a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json +++ b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json @@ -7,6 +7,9 @@ } }, "AllowedHosts": "*", + "ConnectionStrings": { + "Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;" + }, "Identity": { "SigningKey": "secret-signing-key" } diff --git a/src/common/Elsa.Api.Common/Abstractions/Endpoints.cs b/src/common/Elsa.Api.Common/Abstractions/Endpoints.cs index 38dfe2275d..806de81386 100644 --- a/src/common/Elsa.Api.Common/Abstractions/Endpoints.cs +++ b/src/common/Elsa.Api.Common/Abstractions/Endpoints.cs @@ -9,7 +9,7 @@ protected void ConfigurePermissions(params string[] permissions) if (!EndpointSecurityOptions.SecurityIsEnabled) AllowAnonymous(); else - Permissions((new[] { PermissionNames.All }).Concat(permissions).ToArray()); + Permissions(new[] { PermissionNames.All }.Concat(permissions).ToArray()); } } @@ -20,7 +20,7 @@ protected void ConfigurePermissions(params string[] permissions) if (!EndpointSecurityOptions.SecurityIsEnabled) AllowAnonymous(); else - Permissions((new[] { PermissionNames.All }).Concat(permissions).ToArray()); + Permissions(new[] { PermissionNames.All }.Concat(permissions).ToArray()); } } @@ -31,7 +31,7 @@ protected void ConfigurePermissions(params string[] permissions) if (!EndpointSecurityOptions.SecurityIsEnabled) AllowAnonymous(); else - Permissions((new[] { PermissionNames.All }).Concat(permissions).ToArray()); + Permissions(new[] { PermissionNames.All }.Concat(permissions).ToArray()); } } @@ -42,6 +42,6 @@ protected void ConfigurePermissions(params string[] permissions) if (!EndpointSecurityOptions.SecurityIsEnabled) AllowAnonymous(); else - Permissions((new[] { PermissionNames.All }).Concat(permissions).ToArray()); + Permissions(new[] { PermissionNames.All }.Concat(permissions).ToArray()); } } \ No newline at end of file diff --git a/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx b/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx index 2d3c266c74..0ece07ce47 100644 --- a/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx @@ -7,14 +7,13 @@ import {StudioService, AuthContext, EventBus} from "../../services"; @Service() export class LoginPlugin implements Plugin { - private eventBus: EventBus; - private studioService: StudioService; + private readonly eventBus: EventBus; + private readonly studioService: StudioService; constructor() { this.eventBus = Container.get(EventBus); this.studioService = Container.get(StudioService); this.eventBus.on(EventTypes.HttpClient.ClientCreated, this.onHttpClientCreated); - this.eventBus.on(EventTypes.HttpClient.Unauthorized, this.onUnauthorized) } async initialize(): Promise { @@ -27,6 +26,7 @@ export class LoginPlugin implements Plugin { private onHttpClientCreated = async (e) => { const service: MiddlewareService = e.service; + const studioService = this.studioService; service.register({ async onRequest(request) { @@ -37,12 +37,16 @@ export class LoginPlugin implements Plugin { request.headers = {...request.headers, 'Authorization': `Bearer ${token}`}; return request; + }, + + async onResponseError(error) { + debugger; + if (error.response.status !== 401) + return; + + studioService.show(() => ); } }); }; - private onUnauthorized = async () => { - this.studioService.show(() => ); - }; - } diff --git a/src/modules/Elsa.AzureServiceBus/Implementations/Worker.cs b/src/modules/Elsa.AzureServiceBus/Implementations/Worker.cs index 338d81b901..d242d293f3 100644 --- a/src/modules/Elsa.AzureServiceBus/Implementations/Worker.cs +++ b/src/modules/Elsa.AzureServiceBus/Implementations/Worker.cs @@ -17,16 +17,15 @@ public class Worker : IAsyncDisposable private static readonly string BookmarkName = TypeNameHelper.GenerateTypeName(); private readonly ServiceBusProcessor _processor; private readonly IHasher _hasher; - private readonly IWorkflowService _workflowService; + //private readonly IWorkflowService _workflowService; private readonly ILogger _logger; private int _refCount = 1; - public Worker(string queueOrTopic, string? subscription, ServiceBusClient client, IHasher hasher, IWorkflowService workflowService, ILogger logger) + public Worker(string queueOrTopic, string? subscription, ServiceBusClient client, IHasher hasher, ILogger logger) { QueueOrTopic = queueOrTopic; Subscription = subscription == "" ? default : subscription; _hasher = hasher; - _workflowService = workflowService; _logger = logger; var options = new ServiceBusProcessorOptions(); @@ -73,9 +72,9 @@ private async Task InvokeWorkflowsAsync(ServiceBusReceivedMessage message, Cance var correlationId = message.CorrelationId; var messageModel = CreateMessageModel(message); var input = new Dictionary { [MessageReceived.InputKey] = messageModel }; - var executionResults = (await _workflowService.DispatchStimulusAsync(BookmarkName, payload, input, correlationId, cancellationToken)).ToList(); + //var executionResults = (await _workflowService.DispatchStimulusAsync(BookmarkName, payload, input, correlationId, cancellationToken)).ToList(); - _logger.LogInformation("Triggered {WorkflowCount} workflows", executionResults.Count); + //_logger.LogInformation("Triggered {WorkflowCount} workflows", executionResults.Count); } private ReceivedServiceBusMessageModel CreateMessageModel(ServiceBusReceivedMessage message) => diff --git a/src/modules/Elsa.Http/Extensions/RouteTableExtensions.cs b/src/modules/Elsa.Http/Extensions/RouteTableExtensions.cs index 3fba1c363c..c6103efd84 100644 --- a/src/modules/Elsa.Http/Extensions/RouteTableExtensions.cs +++ b/src/modules/Elsa.Http/Extensions/RouteTableExtensions.cs @@ -4,6 +4,7 @@ using Elsa.Http.Models; using Elsa.Http.Services; using Elsa.Workflows.Core.Helpers; +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Persistence.Entities; namespace Elsa.Http.Extensions; @@ -17,7 +18,7 @@ public static void AddRoutes(this IRouteTable routeTable, IEnumerable bookmarks) + public static void AddRoutes(this IRouteTable routeTable, IEnumerable bookmarks) { var paths = Filter(bookmarks).Select(Deserialize).Select(x => x.Path).ToList(); routeTable.AddRange(paths); @@ -29,15 +30,15 @@ public static void RemoveRoutes(this IRouteTable routeTable, IEnumerable bookmarks) + public static void RemoveRoutes(this IRouteTable routeTable, IEnumerable bookmarks) { var paths = Filter(bookmarks).Select(Deserialize).Select(x => x.Path).ToList(); routeTable.RemoveRange(paths); } private static IEnumerable Filter(IEnumerable triggers) => triggers.Where(x => x.Name == ActivityTypeNameHelper.GenerateTypeName()); - private static IEnumerable Filter(IEnumerable triggers) => triggers.Where(x => x.Name == ActivityTypeNameHelper.GenerateTypeName()); + private static IEnumerable Filter(IEnumerable triggers) => triggers.Where(x => x.Name == ActivityTypeNameHelper.GenerateTypeName()); private static HttpEndpointBookmarkData Deserialize(WorkflowTrigger trigger) => Deserialize(trigger.Data!); - private static HttpEndpointBookmarkData Deserialize(WorkflowBookmark bookmark) => Deserialize(bookmark.Data!); + private static HttpEndpointBookmarkData Deserialize(Bookmark bookmark) => Deserialize(bookmark.Data!); private static HttpEndpointBookmarkData Deserialize(string model) => JsonSerializer.Deserialize(model)!; } \ No newline at end of file diff --git a/src/modules/Elsa.Http/Middleware/HttpTriggerMiddleware.cs b/src/modules/Elsa.Http/Middleware/HttpTriggerMiddleware.cs index 5d4a8083db..f0f847629d 100644 --- a/src/modules/Elsa.Http/Middleware/HttpTriggerMiddleware.cs +++ b/src/modules/Elsa.Http/Middleware/HttpTriggerMiddleware.cs @@ -32,7 +32,7 @@ public HttpTriggerMiddleware(RequestDelegate next, IHasher hasher, IOptions() { [HttpEndpoint.InputKey] = requestModel }; - var stimulus = Stimulus.Standard(hash, input); - var executionResults = (await workflowService.ExecuteStimulusAsync(stimulus, abortToken)).ToList(); - - if (!executionResults.Any()) - { - await _next(httpContext); - return; - } - - await WriteResponseAsync(httpContext, executionResults, abortToken); + // var stimulus = Stimulus.Standard(hash, input); + // var executionResults = (await workflowService.ExecuteStimulusAsync(stimulus, abortToken)).ToList(); + // + // if (!executionResults.Any()) + // { + // await _next(httpContext); + // return; + // } + + //await WriteResponseAsync(httpContext, executionResults, abortToken); } private static async Task WriteResponseAsync(HttpContext httpContext, IEnumerable executionResults, CancellationToken cancellationToken) diff --git a/src/modules/Elsa.Jobs.Activities/Handlers/JobExecutedHandler.cs b/src/modules/Elsa.Jobs.Activities/Handlers/JobExecutedHandler.cs index 0ea592a718..2eeaa77180 100644 --- a/src/modules/Elsa.Jobs.Activities/Handlers/JobExecutedHandler.cs +++ b/src/modules/Elsa.Jobs.Activities/Handlers/JobExecutedHandler.cs @@ -8,12 +8,12 @@ namespace Elsa.Jobs.Activities.Handlers; public class JobExecutedHandler : INotificationHandler { - private readonly IWorkflowService _workflowService; - - public JobExecutedHandler(IWorkflowService workflowService) - { - _workflowService = workflowService; - } + // private readonly IWorkflowService _workflowService; + // + // public JobExecutedHandler(IWorkflowService workflowService) + // { + // _workflowService = workflowService; + // } public async Task HandleAsync(JobExecuted notification, CancellationToken cancellationToken) { @@ -21,6 +21,6 @@ public async Task HandleAsync(JobExecuted notification, CancellationToken cancel var jobType = notification.Job.GetType(); var jobTypeName = JobTypeNameHelper.GenerateTypeName(jobType); var bookmarkName = jobTypeName; - await _workflowService.DispatchStimulusAsync(bookmarkName, payload, cancellationToken: cancellationToken); + //await _workflowService.DispatchStimulusAsync(bookmarkName, payload, cancellationToken: cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.MassTransit/Features/MassTransitDispatchersFeature.cs b/src/modules/Elsa.MassTransit/Features/MassTransitDispatchersFeature.cs index 7becd7fabf..6450e665aa 100644 --- a/src/modules/Elsa.MassTransit/Features/MassTransitDispatchersFeature.cs +++ b/src/modules/Elsa.MassTransit/Features/MassTransitDispatchersFeature.cs @@ -17,6 +17,6 @@ public MassTransitDispatchersFeature(IModule module) : base(module) public override void Configure() { - Module.Configure(f => f.WorkflowDispatcherFactory = ActivatorUtilities.GetServiceOrCreateInstance); + Module.Configure(f => f.WorkflowDispatcher = ActivatorUtilities.GetServiceOrCreateInstance); } } \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor.Common/ProtoActorSystem.cs b/src/modules/Elsa.ProtoActor.Common/ProtoActorSystem.cs index 981db6eafc..1367f8e15c 100644 --- a/src/modules/Elsa.ProtoActor.Common/ProtoActorSystem.cs +++ b/src/modules/Elsa.ProtoActor.Common/ProtoActorSystem.cs @@ -8,16 +8,17 @@ namespace Elsa.ProtoActor.Common; public class ProtoActorSystem { - public IClusterProvider ClusterProvider { get; set; } - public GrpcNetRemoteConfig RemoteConfig { get; set; } - public ActorSystemConfig ActorSystemConfig { get; set; } = ActorSystemConfig.Setup(); - public IIdentityLookup IdentityLookup { get; set; } - - public ClusterConfigurationSettings ClusterConfigurationSettings { get; set; } = new(); + public ProtoActorSystem() + { + } - public string Name { get; set; } - - public ProtoActorSystem(IClusterProvider clusterProvider, GrpcNetRemoteConfig remoteConfig, ActorSystemConfig actorSystemConfig, IIdentityLookup identityLookup, string name, ClusterConfigurationSettings clusterConfigurationSettings) + public ProtoActorSystem( + IClusterProvider clusterProvider, + GrpcNetRemoteConfig remoteConfig, + ActorSystemConfig actorSystemConfig, + IIdentityLookup identityLookup, + string name, + ClusterConfigurationSettings clusterConfigurationSettings) { ClusterProvider = clusterProvider; RemoteConfig = remoteConfig; @@ -27,7 +28,10 @@ public ProtoActorSystem(IClusterProvider clusterProvider, GrpcNetRemoteConfig re ClusterConfigurationSettings = clusterConfigurationSettings; } - public ProtoActorSystem() - { - } + public IClusterProvider ClusterProvider { get; set; } + public GrpcNetRemoteConfig RemoteConfig { get; set; } + public ActorSystemConfig ActorSystemConfig { get; set; } = ActorSystemConfig.Setup(); + public IIdentityLookup IdentityLookup { get; set; } + public ClusterConfigurationSettings ClusterConfigurationSettings { get; set; } = new(); + public string Name { get; set; } } \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor.Kubernetes/DependencyInjectionExtensions.cs b/src/modules/Elsa.ProtoActor.Kubernetes/DependencyInjectionExtensions.cs index c28ce9ad40..59f2c00e9b 100644 --- a/src/modules/Elsa.ProtoActor.Kubernetes/DependencyInjectionExtensions.cs +++ b/src/modules/Elsa.ProtoActor.Kubernetes/DependencyInjectionExtensions.cs @@ -1,18 +1,14 @@ using System; -using Elsa.ProtoActor.Common; -using Elsa.ProtoActor.Configuration; +using Elsa.Features.Extensions; +using Elsa.ProtoActor.Features; namespace Elsa.ProtoActor.Kubernetes; public static class DependencyInjectionExtensions { - public static ProtoActorFeature WithKubernetesProvider(this ProtoActorFeature protoActorFeature, Action providerOptions) + public static ProtoActorFeature UseKubernetes(this ProtoActorFeature protoActorFeature, Action? kubernetesFeature = default) { - var options = new KubernetesProviderOptions(); - providerOptions?.Invoke(options); - - protoActorFeature.ConfigureProtoActorBuilder(sp => - new ProtoActorBuilder().UseKubernetesProvider(options).Build()); + protoActorFeature.Module.Use(feature => kubernetesFeature?.Invoke(feature)); return protoActorFeature; } } \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProtoActorFeature.cs b/src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProtoActorFeature.cs new file mode 100644 index 0000000000..3f8335f922 --- /dev/null +++ b/src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProtoActorFeature.cs @@ -0,0 +1,55 @@ +using System; +using Elsa.Features.Abstractions; +using Elsa.Features.Attributes; +using Elsa.Features.Services; +using Elsa.ProtoActor.Common; +using Elsa.ProtoActor.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Proto; +using Proto.Cluster; +using Proto.Cluster.Identity; +using Proto.Cluster.Kubernetes; +using Proto.Cluster.Partition; +using Proto.Remote; +using Proto.Remote.GrpcNet; + +namespace Elsa.ProtoActor.Kubernetes; + +[DependsOn(typeof(ProtoActorFeature))] +public class KubernetesProtoActorFeature : FeatureBase +{ + public KubernetesProtoActorFeature(IModule module) : base(module) + { + } + + public string HostAddress { get; set; } = default!; + public IIdentityLookup IdentityLookup { get; set; } = new PartitionIdentityLookup(); + + public override void Configure() + { + Module.Configure().ConfigureActorBuilder = (sp, builder) => + { + var (remoteConfig, clusterProvider) = ConfigureForKubernetes(HostAddress); + var actorSystemConfig = ActorSystemConfig.Setup(); + + builder.WithClusterProvider(clusterProvider) + .WithRemoteConfig(remoteConfig) + .WithIdentity(IdentityLookup) + .WithActorSystemConfig(actorSystemConfig); + }; + } + + private static (GrpcNetRemoteConfig, IClusterProvider) ConfigureForKubernetes(string host) + { + var clusterProvider = new KubernetesProvider(); + + var remoteConfig = GrpcNetRemoteConfig + .BindToAllInterfaces(host) + .WithLogLevelForDeserializationErrors(LogLevel.Critical) + .WithRemoteDiagnostics(true); + + return (remoteConfig, clusterProvider); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProviderOptions.cs b/src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProviderOptions.cs deleted file mode 100644 index 105e74e13f..0000000000 --- a/src/modules/Elsa.ProtoActor.Kubernetes/KubernetesProviderOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Elsa.ProtoActor.Common.Options; -using Proto.Cluster.Identity; -using Proto.Cluster.Partition; - -namespace Elsa.ProtoActor.Kubernetes; - -public class KubernetesProviderOptions : ProviderOptions -{ - public string HostAddress { get; set; } = default!; - public IIdentityLookup IdentityLookup { get; set; } = new PartitionIdentityLookup(); -} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor.Kubernetes/ProtoActorBuilderExtensions.cs b/src/modules/Elsa.ProtoActor.Kubernetes/ProtoActorBuilderExtensions.cs deleted file mode 100644 index 874504f431..0000000000 --- a/src/modules/Elsa.ProtoActor.Kubernetes/ProtoActorBuilderExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Elsa.ProtoActor.Common; -using Microsoft.Extensions.Logging; -using Proto; -using Proto.Cluster; -using Proto.Cluster.Kubernetes; -using Proto.Remote; -using Proto.Remote.GrpcNet; - -namespace Elsa.ProtoActor.Kubernetes; - -public static class ProtoActorBuilderExtensions -{ - public static ProtoActorBuilder UseKubernetesProvider(this ProtoActorBuilder builder, KubernetesProviderOptions options) - { - var (remoteConfig, clusterProvider) = ConfigureForKubernetes(options.HostAddress); - - var actorSystemConfig = ActorSystemConfig.Setup(); - if (options.WithDeveloperLogging) - { - actorSystemConfig.WithDeveloperLogging(); - } - - if (options.WithMetrics) - { - actorSystemConfig.WithMetrics(); - } - - builder.WithClusterProvider(clusterProvider) - .WithRemoteConfig(remoteConfig) - .WithClusterName(options.Name) - .WithIdentity(options.IdentityLookup) - .WithActorSystemConfig(actorSystemConfig); - return builder; - } - - private static (GrpcNetRemoteConfig, IClusterProvider) ConfigureForKubernetes(string host) - { - var clusterProvider = new KubernetesProvider(); - - var remoteConfig = GrpcNetRemoteConfig - .BindToAllInterfaces(advertisedHost: host) - .WithLogLevelForDeserializationErrors(LogLevel.Critical) - .WithRemoteDiagnostics(true); - - return (remoteConfig, clusterProvider); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Elsa.ProtoActor.csproj b/src/modules/Elsa.ProtoActor/Elsa.ProtoActor.csproj index 0ed5fa9445..d1c7084ebf 100644 --- a/src/modules/Elsa.ProtoActor/Elsa.ProtoActor.csproj +++ b/src/modules/Elsa.ProtoActor/Elsa.ProtoActor.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -16,14 +16,15 @@ - - - - - - - - + + + + + + + + + diff --git a/src/modules/Elsa.ProtoActor/Events.cs b/src/modules/Elsa.ProtoActor/Events.cs new file mode 100644 index 0000000000..50c768e6da --- /dev/null +++ b/src/modules/Elsa.ProtoActor/Events.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; +using Elsa.Runtime.Protos; + +namespace Elsa.ProtoActor; + +public record BookmarksStored(ICollection Bookmarks); +public record BookmarksRemovedByWorkflow(string WorkflowInstanceId); \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Extensions/CollectionExtensions.cs b/src/modules/Elsa.ProtoActor/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000000..9a6b620390 --- /dev/null +++ b/src/modules/Elsa.ProtoActor/Extensions/CollectionExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Elsa.ProtoActor.Extensions; + +public static class CollectionExtensions +{ + public static void AddRange(this ICollection target, IEnumerable source) + { + foreach (var item in source) target.Add(item); + } + + public static void RemoveWhere(this ICollection collection, Func predicate) + { + var itemsToRemove = collection.Where(predicate).ToList(); + foreach (var item in itemsToRemove) collection.Remove(item); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Extensions/DependencyInjectionExtensions.cs b/src/modules/Elsa.ProtoActor/Extensions/DependencyInjectionExtensions.cs index 3ed53d81d7..f6316b0c4b 100644 --- a/src/modules/Elsa.ProtoActor/Extensions/DependencyInjectionExtensions.cs +++ b/src/modules/Elsa.ProtoActor/Extensions/DependencyInjectionExtensions.cs @@ -1,7 +1,5 @@ using System; -using Elsa.ProtoActor.Common; -using Elsa.ProtoActor.Common.Options; -using Elsa.ProtoActor.Configuration; +using Elsa.ProtoActor.Features; using Elsa.Workflows.Runtime.Features; namespace Elsa.ProtoActor.Extensions; @@ -13,14 +11,4 @@ public static WorkflowRuntimeFeature UseProtoActor(this WorkflowRuntimeFeature f feature.Module.Configure(configure); return feature; } - - public static ProtoActorFeature WithLocalhostProvider(this ProtoActorFeature protoActorFeature, Action? providerOptions = null) - { - var options = new ProviderOptions(); - providerOptions?.Invoke(options); - - protoActorFeature.ConfigureProtoActorBuilder(sp => - new ProtoActorBuilder().UseLocalhostProvider(options.Name, options.WithDeveloperLogging).Build()); - return protoActorFeature; - } } diff --git a/src/modules/Elsa.ProtoActor/Extensions/ProtoInputExtensions.cs b/src/modules/Elsa.ProtoActor/Extensions/ProtoInputExtensions.cs index 5d9fa8b8ad..664b9834cb 100644 --- a/src/modules/Elsa.ProtoActor/Extensions/ProtoInputExtensions.cs +++ b/src/modules/Elsa.ProtoActor/Extensions/ProtoInputExtensions.cs @@ -16,7 +16,7 @@ public static Input Serialize(this IDictionary input) foreach (var (key, value) in input) { - data[key] = new Runtime.Protos.Json + data[key] = new Json { Text = JsonSerializer.Serialize(value) }; diff --git a/src/modules/Elsa.ProtoActor/Configuration/ProtoActorFeature.cs b/src/modules/Elsa.ProtoActor/Features/ProtoActorFeature.cs similarity index 53% rename from src/modules/Elsa.ProtoActor/Configuration/ProtoActorFeature.cs rename to src/modules/Elsa.ProtoActor/Features/ProtoActorFeature.cs index 4a5b223463..5315d4ed21 100644 --- a/src/modules/Elsa.ProtoActor/Configuration/ProtoActorFeature.cs +++ b/src/modules/Elsa.ProtoActor/Features/ProtoActorFeature.cs @@ -15,55 +15,80 @@ using Proto; using Proto.Cluster; using Proto.DependencyInjection; +using Proto.Persistence; using Proto.Remote; using Proto.Remote.GrpcNet; -namespace Elsa.ProtoActor.Configuration; +namespace Elsa.ProtoActor.Features; [DependsOn(typeof(WorkflowRuntimeFeature))] public class ProtoActorFeature : FeatureBase { public ProtoActorFeature(IModule module) : base(module) { + ConfigureActorSystem = (sp, config) => + { + if (WithDeveloperLogging) config.WithDeveloperLogging(); + if (WithMetrics) config.WithMetrics(); + }; + + ConfigureActorBuilder = (_, builder) => builder + .WithClusterName(ClusterName) + .UseLocalhostProvider(ClusterName, true) + ; } public override void Configure() { - // Configure runtime with ProtoActor workflow invoker. - Module.Configure().WorkflowInvokerFactory = - sp => ActivatorUtilities.CreateInstance(sp); + // Configure runtime with ProtoActor workflow runtime. + Module.Configure().WorkflowRuntime = sp => ActivatorUtilities.CreateInstance(sp); } - public ProtoActorFeature ConfigureProtoActorBuilder(Func factory) + public string ClusterName { get; set; } = "elsa-cluster"; + public bool WithDeveloperLogging { get; set; } = true; + public bool WithMetrics { get; set; } = true; + public Action ConfigureActorSystem { get; set; } + public Action ConfigureActorBuilder { get; set; } + public Func PersistenceProvider { get; set; } = _ => new InMemoryProvider(); + + public override void ConfigureHostedServices() { - ProtoActorBuilderFactory = factory; - return this; + Services.AddHostedService(); } - //configure the default one - public Func ProtoActorBuilderFactory { get; set; } = - _ => new ProtoActorBuilder().UseLocalhostProvider("elsa-cluster", true) .Build(); - public override void Apply() { var services = Services; - services.AddSingleton(ProtoActorBuilderFactory); + + // Register ProtoActorSystem. + services.AddSingleton(sp => + { + var actorSystemConfig = ActorSystemConfig.Setup(); + var actorBuilder = new ProtoActorBuilder(); + + ConfigureActorSystem(sp, actorSystemConfig); + ConfigureActorBuilder(sp, actorBuilder); + + return actorBuilder.Build(); + }); // Logging. Log.SetLoggerFactory(LoggerFactory.Create(l => l.AddConsole().SetMinimumLevel(LogLevel.Warning))); + + // Persistence. + services.AddSingleton(PersistenceProvider); services.AddSingleton(sp => { var protoActorSystem = sp.GetService(); - var system = new ActorSystem(protoActorSystem!.ActorSystemConfig).WithServiceProvider(sp); var remoteConfig = protoActorSystem.RemoteConfig .WithProtoMessages(MessagesReflection.Descriptor) .WithProtoMessages(EmptyReflection.Descriptor); - var workflowDefinitionProps = system.DI().PropsFor(); - var workflowInstanceProps = system.DI().PropsFor(); + var workflowGrainProps = system.DI().PropsFor(); + var bookmarkGrainProps = system.DI().PropsFor(); var clusterConfig = ClusterConfig @@ -72,8 +97,9 @@ public override void Apply() .WithActorRequestTimeout(protoActorSystem.ClusterConfigurationSettings.ActorRequestTimeout) .WithActorActivationTimeout(protoActorSystem.ClusterConfigurationSettings.ActorActivationTimeout) .WithActorSpawnTimeout(protoActorSystem.ClusterConfigurationSettings.ActorSpawnTimeout) - .WithClusterKind(WorkflowDefinitionGrainActor.Kind, workflowDefinitionProps) - .WithClusterKind(WorkflowInstanceGrainActor.Kind, workflowInstanceProps); + .WithClusterKind(WorkflowGrainActor.Kind, workflowGrainProps) + .WithClusterKind(BookmarkGrainActor.Kind, bookmarkGrainProps) + ; system .WithRemote(remoteConfig) @@ -87,17 +113,8 @@ public override void Apply() // Actors. services - .AddSingleton(sp => new WorkflowDefinitionGrainActor((context, _) => - ActivatorUtilities.CreateInstance(sp, context))) - .AddSingleton(sp => new WorkflowInstanceGrainActor((context, _) => - ActivatorUtilities.CreateInstance(sp, context))); - - // Client factory. - services.AddSingleton(); - } - - public override void ConfigureHostedServices() - { - Services.AddHostedService(); + .AddTransient(sp => new WorkflowGrainActor((context, _) => ActivatorUtilities.CreateInstance(sp, context))) + .AddTransient(sp => new BookmarkGrainActor((context, _) => ActivatorUtilities.CreateInstance(sp, context))) + ; } } \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Grains/BookmarkGrain.cs b/src/modules/Elsa.ProtoActor/Grains/BookmarkGrain.cs new file mode 100644 index 0000000000..1b6dd72453 --- /dev/null +++ b/src/modules/Elsa.ProtoActor/Grains/BookmarkGrain.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Elsa.ProtoActor.Extensions; +using Elsa.Runtime.Protos; +using Proto; +using Proto.Cluster; +using Proto.Persistence; +using Proto.Persistence.SnapshotStrategies; + +namespace Elsa.ProtoActor.Grains; + +using Persistence = Proto.Persistence.Persistence; + +public class BookmarkGrain : BookmarkGrainBase +{ + private ICollection _bookmarks = new List(); + private readonly Persistence _persistence; + + public BookmarkGrain(IProvider provider, IContext context) : base(context) + { + _persistence = Persistence.WithEventSourcingAndSnapshotting( + provider, + provider, + BookmarkHash, + ApplyEvent, + ApplySnapshot, + new TimeStrategy(TimeSpan.FromSeconds(10)), + GetState); + } + + private string BookmarkHash => Context.ClusterIdentity()!.Identity; + public override async Task OnStarted() => await _persistence.RecoverStateAsync(); + + public override async Task Store(StoreBookmarksRequest request) + { + var bookmarks = request.BookmarkIds.Select(x => new StoredBookmark + { + WorkflowInstanceId = request.WorkflowInstanceId, + BookmarkId = x + }).ToList(); + + await _persistence.PersistEventAsync(new BookmarksStored(bookmarks)); + return new Unit(); + } + + public override async Task RemoveByWorkflow(RemoveBookmarksByWorkflowRequest request) + { + await _persistence.PersistEventAsync(new BookmarksRemovedByWorkflow(request.WorkflowInstanceId)); + return new Unit(); + } + + public override Task Resolve(ResolveBookmarksRequest request) + { + var response = new ResolveBookmarksResponse(); + response.Bookmarks.AddRange(_bookmarks); + + 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); +} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Grains/WorkflowDefinitionGrain.cs b/src/modules/Elsa.ProtoActor/Grains/WorkflowDefinitionGrain.cs deleted file mode 100644 index f71a88cf38..0000000000 --- a/src/modules/Elsa.ProtoActor/Grains/WorkflowDefinitionGrain.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Threading.Tasks; -using Elsa.Runtime.Protos; -using Elsa.Workflows.Core.Services; -using Proto; - -namespace Elsa.ProtoActor.Grains; - -/// -/// Instantiates and executes a workflow instance for execution. -/// -public class WorkflowDefinitionGrain : WorkflowDefinitionGrainBase -{ - private readonly IIdentityGenerator _identityGenerator; - - public WorkflowDefinitionGrain(IIdentityGenerator identityGenerator, IContext context) : base(context) - { - _identityGenerator = identityGenerator; - } - - public override async Task Execute(ExecuteWorkflowDefinitionRequest request) - { - var definitionId = request.Id; - var correlationId = request.CorrelationId == "" ? default : request.CorrelationId; - var cancellationToken = Context.CancellationToken; - var workflowInstanceId = _identityGenerator.GenerateId(); - - var executeWorkflowRequest = new ExecuteNewWorkflowInstanceRequest - { - DefinitionId = definitionId, - VersionOptions = request.VersionOptions, - CorrelationId = correlationId ?? "", - Input = request.Input - }; - - var workflowInstanceGrainClient = Context.GetWorkflowInstanceGrain(workflowInstanceId); - var workflowInstanceResponse = await workflowInstanceGrainClient.ExecuteNewInstance(executeWorkflowRequest, cancellationToken); - - if (workflowInstanceResponse == null) - throw new TimeoutException("Did not receive a response from the WorkflowInstance actor within the configured amount of time."); - - var response = new ExecuteWorkflowDefinitionResponse - { - WorkflowState = workflowInstanceResponse.WorkflowState, - }; - - response.Bookmarks.AddRange(workflowInstanceResponse.Bookmarks); - return response; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs b/src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs new file mode 100644 index 0000000000..c4ff6a469b --- /dev/null +++ b/src/modules/Elsa.ProtoActor/Grains/WorkflowGrain.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Mediator.Services; +using Elsa.Models; +using Elsa.ProtoActor.Extensions; +using Elsa.Runtime.Protos; +using Elsa.Workflows.Core.Helpers; +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Core.Services; +using Elsa.Workflows.Core.State; +using Elsa.Workflows.Runtime.Models; +using Elsa.Workflows.Runtime.Notifications; +using Elsa.Workflows.Runtime.Services; +using Proto; +using Proto.Cluster; +using Proto.Persistence; +using RunWorkflowResult = Elsa.Runtime.Protos.RunWorkflowResult; + +namespace Elsa.ProtoActor.Grains; + +using Persistence = Proto.Persistence.Persistence; + +/// +/// Executes a workflow. +/// +public class WorkflowGrain : WorkflowGrainBase +{ + private readonly IWorkflowDefinitionService _workflowDefinitionService; + private readonly IWorkflowRunner _workflowRunner; + private readonly IEventPublisher _eventPublisher; + private readonly Persistence _persistence; + + private string _definitionId = default!; + private int _version; + private IDictionary? _input; + private Workflow _workflow = default!; + private WorkflowState _workflowState = default!; + private ICollection _bookmarks = new List(); + + public WorkflowGrain( + IWorkflowDefinitionService workflowDefinitionService, + IWorkflowRunner workflowRunner, + IEventPublisher eventPublisher, + IProvider provider, + IContext context) : base(context) + { + _workflowDefinitionService = workflowDefinitionService; + _workflowRunner = workflowRunner; + _eventPublisher = eventPublisher; + _persistence = Persistence.WithSnapshotting(provider, WorkflowInstanceId, ApplySnapshot); + } + + private string WorkflowInstanceId => Context.ClusterIdentity()!.Identity; + + public override async Task OnStarted() + { + await _persistence.RecoverStateAsync(); + + if (string.IsNullOrWhiteSpace(_definitionId)) + return; // No state yet to recover from. + + var cancellationToken = Context.CancellationToken; + + // Load the workflow definition. + var workflowDefinition = await _workflowDefinitionService.FindAsync(_definitionId, VersionOptions.SpecificVersion(_version), cancellationToken); + + if (workflowDefinition == null) + throw new Exception("Workflow definition is no longer available"); + + // Materialize the workflow. + _workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); + } + + public override async Task Start(StartWorkflowRequest request) + { + var definitionId = request.DefinitionId; + var correlationId = request.CorrelationId == "" ? default : request.CorrelationId; + var input = request.Input?.Deserialize(); + var versionOptions = VersionOptions.FromString(request.VersionOptions); + var cancellationToken = Context.CancellationToken; + var workflowDefinition = await _workflowDefinitionService.FindAsync(definitionId, versionOptions, cancellationToken); + + if (workflowDefinition == null) + throw new Exception("Specified workflow definition and version does not exist"); + + _workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); + var version = workflowDefinition.Version; + var workflowResult = await _workflowRunner.RunAsync(_workflow, WorkflowInstanceId, _input, cancellationToken); + var finished = workflowResult.WorkflowState.Status == WorkflowStatus.Finished; + + _workflowState = workflowResult.WorkflowState; + _version = version; + _definitionId = definitionId; + _input = input; + + await UpdateBookmarksAsync(workflowResult.Bookmarks, cancellationToken); + await SaveSnapshotAsync(); + + return new StartWorkflowResponse + { + Result = finished ? RunWorkflowResult.Finished : RunWorkflowResult.Suspended + }; + } + + public override async Task Resume(ResumeWorkflowRequest request) + { + var input = request.Input?.Deserialize(); + var bookmarkId = request.BookmarkId; + + var cancellationToken = Context.CancellationToken; + var bookmark = _bookmarks.FirstOrDefault(x => x.Id == bookmarkId); + + if (bookmark == null) + throw new Exception("Bookmark not found"); + + var workflowResult = await _workflowRunner.RunAsync(_workflow, _workflowState, bookmark, input, cancellationToken); + var finished = workflowResult.WorkflowState.Status == WorkflowStatus.Finished; + + _workflowState = workflowResult.WorkflowState; + _input = input; + + await UpdateBookmarksAsync(workflowResult.Bookmarks, cancellationToken); + await SaveSnapshotAsync(); + + return new ResumeWorkflowResponse + { + Result = finished ? RunWorkflowResult.Finished : RunWorkflowResult.Suspended + }; + } + + private async Task UpdateBookmarksAsync(ICollection bookmarks, CancellationToken cancellationToken) + { + var originalBookmarks = _bookmarks; + _bookmarks = bookmarks; + + await RemoveBookmarksAsync(originalBookmarks, cancellationToken); + await StoreBookmarksAsync(bookmarks, cancellationToken); + await PublishChangedBookmarksAsync(originalBookmarks, bookmarks, cancellationToken); + } + + private async Task StoreBookmarksAsync(ICollection bookmarks, CancellationToken cancellationToken) + { + var groupedBookmarks = bookmarks.GroupBy(x => x.Hash); + + foreach (var groupedBookmark in groupedBookmarks) + { + var bookmarkClient = Context.GetBookmarkGrain(groupedBookmark.Key); + + var storeBookmarkRequest = new StoreBookmarksRequest + { + WorkflowInstanceId = WorkflowInstanceId + }; + + storeBookmarkRequest.BookmarkIds.AddRange(groupedBookmark.Select(x => x.Id)); + await bookmarkClient.Store(storeBookmarkRequest, cancellationToken); + } + } + + private async Task RemoveBookmarksAsync(IEnumerable bookmarks, CancellationToken cancellationToken) + { + var groupedBookmarks = bookmarks.GroupBy(x => x.Hash); + + foreach (var groupedBookmark in groupedBookmarks) + { + var bookmarkClient = Context.GetBookmarkGrain(groupedBookmark.Key); + await bookmarkClient.RemoveByWorkflow(new RemoveBookmarksByWorkflowRequest + { + WorkflowInstanceId = WorkflowInstanceId + }, cancellationToken); + } + } + + private async Task PublishChangedBookmarksAsync(ICollection originalBookmarks, ICollection updatedBookmarks, CancellationToken cancellationToken) + { + var diff = Diff.For(originalBookmarks, updatedBookmarks); + var removedBookmarks = diff.Removed; + var createdBookmarks = diff.Added; + await _eventPublisher.PublishAsync(new WorkflowBookmarksIndexed(new IndexedWorkflowBookmarks(_workflowState, createdBookmarks, removedBookmarks)), cancellationToken); + } + + private void ApplySnapshot(Snapshot snapshot) => (_definitionId, _version, _workflowState, _bookmarks, _input) = (WorkflowSnapshot)snapshot.State; + private async Task SaveSnapshotAsync() => await _persistence.PersistSnapshotAsync(GetState()); + private object GetState() => new WorkflowSnapshot(_definitionId, _version, _workflowState, _bookmarks, _input); +} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs b/src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs deleted file mode 100644 index 3c960b7193..0000000000 --- a/src/modules/Elsa.ProtoActor/Grains/WorkflowInstanceGrain.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Elsa.Models; -using Elsa.ProtoActor.Extensions; -using Elsa.Runtime.Protos; -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Core.Serialization; -using Elsa.Workflows.Core.Services; -using Elsa.Workflows.Core.State; -using Elsa.Workflows.Persistence.Entities; -using Elsa.Workflows.Persistence.Services; -using Elsa.Workflows.Runtime.Services; -using Proto; -using Bookmark = Elsa.Runtime.Protos.Bookmark; - -namespace Elsa.ProtoActor.Grains; - -/// -/// Executes a workflow instance. -/// -public class WorkflowInstanceGrain : WorkflowInstanceGrainBase -{ - private readonly IWorkflowInstanceStore _workflowInstanceStore; - private readonly IWorkflowDefinitionStore _workflowDefinitionStore; - private readonly IWorkflowDefinitionService _workflowDefinitionService; - private readonly IWorkflowRunner _workflowRunner; - private readonly IWorkflowInstanceFactory _workflowInstanceFactory; - private readonly SerializerOptionsProvider _serializerOptionsProvider; - - public WorkflowInstanceGrain( - IWorkflowInstanceStore workflowInstanceStore, - IWorkflowDefinitionStore workflowDefinitionStore, - IWorkflowDefinitionService workflowDefinitionService, - IWorkflowRunner workflowRunner, - IWorkflowInstanceFactory workflowInstanceFactory, - SerializerOptionsProvider serializerOptionsProvider, - IContext context) : base(context) - { - _workflowInstanceStore = workflowInstanceStore; - _workflowDefinitionStore = workflowDefinitionStore; - _workflowDefinitionService = workflowDefinitionService; - _workflowRunner = workflowRunner; - _workflowInstanceFactory = workflowInstanceFactory; - _serializerOptionsProvider = serializerOptionsProvider; - } - - public override async Task ExecuteExistingInstance(ExecuteExistingWorkflowInstanceRequest request) - { - var workflowInstanceId = request.InstanceId; - var cancellationToken = Context.CancellationToken; - var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); - - if (workflowInstance == null) - throw new Exception($"No workflow instance found with ID {workflowInstanceId}"); - - var workflowDefinitionId = workflowInstance.DefinitionId; - var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); - - if (workflow == null) - throw new Exception($"No workflow definition found with ID {workflowDefinitionId}"); - - var workflowState = workflowInstance.WorkflowState; - var bookmarkMessage = request.Bookmark; - var input = request.Input?.Deserialize(); - var executionResult = await ExecuteAsync(workflow, workflowState, bookmarkMessage, input!, cancellationToken); - var response = MapResult(executionResult); - - return response; - } - - public override async Task ExecuteNewInstance(ExecuteNewWorkflowInstanceRequest request) - { - var cancellationToken = Context.CancellationToken; - var versionOptions = VersionOptions.FromString(request.VersionOptions); - var workflowDefinitionId = request.DefinitionId; - var correlationId = request.CorrelationId == "" ? default : request.CorrelationId; - var workflowInstance = await _workflowInstanceFactory.CreateAsync(workflowDefinitionId, versionOptions, correlationId, cancellationToken); - var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); - - if (workflow == null) - throw new Exception($"No workflow definition found with ID {workflowDefinitionId}"); - - var workflowState = workflowInstance.WorkflowState; - var input = request.Input?.Deserialize(); - var executionResult = await ExecuteAsync(workflow, workflowState, input: input!, cancellationToken: cancellationToken); - var response = MapResult(executionResult); - - return response; - } - - public override async Task Execute(ExecuteWorkflowRequest request) - { - var cancellationToken = Context.CancellationToken; - var workflowState = JsonSerializer.Deserialize(request.WorkflowState, _serializerOptionsProvider.CreatePersistenceOptions())!; - var versionOptions = VersionOptions.FromString(request.VersionOptions); - var workflowDefinitionId = request.DefinitionId; - var bookmark = request.Bookmark; - var input = request.Input?.Deserialize(); - var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken)).FirstOrDefault(); - - if (workflow == null) - throw new Exception($"No workflow definition found with ID {workflowDefinitionId}"); - - var result = await ExecuteAsync(workflow, workflowState, bookmark, input, cancellationToken); - return MapResult(result); - } - - private ExecuteWorkflowInstanceResponse MapResult(RunWorkflowResult result) - { - var bookmarks = result.Bookmarks.Select(x => new Bookmark - { - Id = x.Id, - Hash = x.Hash, - Payload = x.Data, - Name = x.Name, - ActivityId = x.ActivityId, - ActivityInstanceId = x.ActivityInstanceId, - CallbackMethodName = x.CallbackMethodName - }); - - var options = _serializerOptionsProvider.CreatePersistenceOptions(); - - var response = new ExecuteWorkflowInstanceResponse - { - WorkflowState = new Json - { - Text = JsonSerializer.Serialize(result.WorkflowState, options) - } - }; - - response.Bookmarks.Add(bookmarks); - - return response; - } - - private async Task ExecuteAsync( - WorkflowDefinition workflowDefinition, - WorkflowState workflowState, - Bookmark? bookmarkMessage = default, - IDictionary? input = default, - CancellationToken cancellationToken = default) - { - var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); - return await ExecuteAsync(workflow, workflowState, bookmarkMessage, input, cancellationToken); - } - - private async Task ExecuteAsync( - Workflow workflow, - WorkflowState workflowState, - Bookmark? bookmarkMessage = default, - IDictionary? input = default, - CancellationToken cancellationToken = default) - { - if (bookmarkMessage == null) - return await _workflowRunner.RunAsync(workflow, workflowState, input, cancellationToken); - - var bookmark = - new Workflows.Core.Models.Bookmark( - bookmarkMessage.Id, - bookmarkMessage.Name, - bookmarkMessage.Hash, - bookmarkMessage.Payload, - bookmarkMessage.ActivityId, - bookmarkMessage.ActivityInstanceId, - bookmarkMessage.CallbackMethodName); - - return await _workflowRunner.RunAsync(workflow, workflowState, bookmark, input, cancellationToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Implementations/GrainClientFactory.cs b/src/modules/Elsa.ProtoActor/Implementations/GrainClientFactory.cs deleted file mode 100644 index b9f02d82c2..0000000000 --- a/src/modules/Elsa.ProtoActor/Implementations/GrainClientFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Elsa.Models; -using Elsa.Runtime.Protos; -using Proto.Cluster; - -namespace Elsa.ProtoActor.Implementations; - -public class GrainClientFactory -{ - private readonly Cluster _cluster; - - public GrainClientFactory(Cluster cluster) - { - _cluster = cluster; - } - - public WorkflowDefinitionGrainClient CreateWorkflowDefinitionGrainClient(string workflowDefinitionId, VersionOptions versionOptions) => _cluster.GetWorkflowDefinitionGrain($"workflow-definition:{workflowDefinitionId}:{versionOptions.ToString()}"); - public WorkflowInstanceGrainClient CreateWorkflowInstanceGrainClient(string workflowInstanceId) => _cluster.GetWorkflowInstanceGrain($"workflow-instance:{workflowInstanceId}"); -} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Implementations/InMemoryProvider.cs b/src/modules/Elsa.ProtoActor/Implementations/InMemoryProvider.cs new file mode 100644 index 0000000000..6a595d29b8 --- /dev/null +++ b/src/modules/Elsa.ProtoActor/Implementations/InMemoryProvider.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Proto.Persistence; + +namespace Elsa.ProtoActor.Implementations; + +public class InMemoryProvider : IProvider +{ + private readonly ConcurrentDictionary> _events = new(); + private readonly ConcurrentDictionary> _snapshots = new(); + + public Task<(object? Snapshot, long Index)> GetSnapshotAsync(string actorName) + { + if (!_snapshots.TryGetValue(actorName, out var snapshots)) + return Task.FromResult<(object, long)>((null, 0)!)!; + + var snapshot = snapshots.OrderBy(ss => ss.Key).LastOrDefault(); + return Task.FromResult((snapshot.Value, snapshot.Key))!; + } + + public Task GetEventsAsync(string actorName, long indexStart, long indexEnd, Action callback) + { + var lastIndex = 0L; + + if (!_events.TryGetValue(actorName, out var events)) return Task.FromResult(lastIndex); + + foreach (var e in events.Where(e => e.Key >= indexStart && e.Key <= indexEnd)) + { + lastIndex = e.Key; + callback(e.Value); + } + + return Task.FromResult(lastIndex); + } + + public Task PersistEventAsync(string actorName, long index, object @event) + { + var events = _events.GetOrAdd(actorName, new Dictionary()); + + events.Add(index, @event); + + var max = events.Max(x => x.Key); + + return Task.FromResult(max); + } + + public Task PersistSnapshotAsync(string actorName, long index, object snapshot) + { + var type = snapshot.GetType(); + var snapshots = _snapshots.GetOrAdd(actorName, new Dictionary()); + var copy = JsonSerializer.Deserialize(JsonSerializer.Serialize(snapshot), type)!; + + snapshots.Add(index, copy); + + return Task.CompletedTask; + } + + public Task DeleteEventsAsync(string actorName, long inclusiveToIndex) + { + if (!_events.TryGetValue(actorName, out var events)) + return Task.CompletedTask; + + var eventsToRemove = events.Where(s => s.Key <= inclusiveToIndex) + .Select(e => e.Key) + .ToList(); + + eventsToRemove.ForEach(key => events.Remove(key)); + + return Task.CompletedTask; + } + + public Task DeleteSnapshotsAsync(string actorName, long inclusiveToIndex) + { + if (!_snapshots.TryGetValue(actorName, out var snapshots)) + return Task.CompletedTask; + + var snapshotsToRemove = snapshots.Where(s => s.Key <= inclusiveToIndex) + .Select(snapshot => snapshot.Key) + .ToList(); + + snapshotsToRemove.ForEach(key => snapshots.Remove(key)); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowInvoker.cs b/src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowInvoker.cs deleted file mode 100644 index c7032ae762..0000000000 --- a/src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowInvoker.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Elsa.Models; -using Elsa.ProtoActor.Extensions; -using Elsa.Runtime.Protos; -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Core.Serialization; -using Elsa.Workflows.Core.State; -using Elsa.Workflows.Persistence.Entities; -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; -using Proto; -using Proto.Cluster; -using Bookmark = Elsa.Workflows.Core.Models.Bookmark; -using ProtoBookmark = Elsa.Runtime.Protos.Bookmark; - -namespace Elsa.ProtoActor.Implementations; - -public class ProtoActorWorkflowInvoker : IWorkflowInvoker -{ - private readonly IWorkflowDefinitionService _workflowDefinitionService; - private readonly Cluster _cluster; - private readonly GrainClientFactory _grainClientFactory; - private readonly SerializerOptionsProvider _serializerOptionsProvider; - - public ProtoActorWorkflowInvoker( - IWorkflowDefinitionService workflowDefinitionService, - Cluster cluster, - GrainClientFactory grainClientFactory, - SerializerOptionsProvider serializerOptionsProvider) - { - _workflowDefinitionService = workflowDefinitionService; - _cluster = cluster; - _grainClientFactory = grainClientFactory; - _serializerOptionsProvider = serializerOptionsProvider; - } - - public async Task InvokeAsync(InvokeWorkflowDefinitionRequest request, CancellationToken cancellationToken = default) - { - var (definitionId, versionOptions, input, correlationId) = request; - - var message = new ExecuteWorkflowDefinitionRequest - { - Id = definitionId, - VersionOptions = versionOptions.ToString(), - Input = input!?.Serialize(), - CorrelationId = correlationId ?? "" - }; - - var client = _grainClientFactory.CreateWorkflowDefinitionGrainClient(definitionId, versionOptions); - var response = await client.Execute(message, CancellationTokens.FromSeconds(10000)); - - if (response == null) - throw new TimeoutException("Did not receive a response from the WorkflowDefinition actor within the configured amount of time."); - - var workflowState = JsonSerializer.Deserialize(response.WorkflowState.Text)!; - var bookmarks = response.Bookmarks.Select(MapBookmark).ToList(); - - return new RunWorkflowResult(workflowState, bookmarks); - } - - public async Task InvokeAsync(InvokeWorkflowInstanceRequest request, CancellationToken cancellationToken = default) - { - var (instanceId, bookmark, input, correlationId) = request; - var bookmarkMessage = MapBookmark(bookmark); - - var message = new ExecuteExistingWorkflowInstanceRequest - { - InstanceId = instanceId, - Bookmark = bookmarkMessage, - Input = input!?.Serialize(), - }; - - var client = _grainClientFactory.CreateWorkflowInstanceGrainClient(instanceId); - var response = await client.ExecuteExistingInstance(message, CancellationTokens.FromSeconds(600)); - - if (response == null) - throw new TimeoutException("Did not receive a response from the WorkflowInstance actor within the configured amount of time."); - - var workflowState = JsonSerializer.Deserialize(response.WorkflowState.Text)!; - var bookmarks = response.Bookmarks.Select(MapBookmark).ToList(); - - return new RunWorkflowResult(workflowState, bookmarks); - } - - public async Task InvokeAsync(WorkflowInstance workflowInstance, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) - { - var bookmarkMessage = MapBookmark(bookmark); - - var message = new ExecuteExistingWorkflowInstanceRequest - { - Bookmark = bookmarkMessage, - Input = input!?.Serialize(), - InstanceId = workflowInstance.Id - }; - - var client = _grainClientFactory.CreateWorkflowInstanceGrainClient(workflowInstance.Id); - var response = await client.ExecuteExistingInstance(message, CancellationTokens.FromSeconds(600)); - - if (response == null) - throw new TimeoutException("Did not receive a response from the WorkflowInstance actor within the configured amount of time."); - - var bookmarks = response.Bookmarks.Select(MapBookmark).ToList(); - var workflowState = JsonSerializer.Deserialize(response.WorkflowState.Text, _serializerOptionsProvider.CreateDefaultOptions())!; - return new RunWorkflowResult(workflowState, bookmarks); - } - - public async Task InvokeAsync( - WorkflowDefinition workflowDefinition, - WorkflowState workflowState, - Bookmark? bookmark = default, - IDictionary? input = default, - CancellationToken cancellationToken = default) - { - var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); - return await InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); - } - - public async Task InvokeAsync(Workflow workflow, WorkflowState workflowState, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) - { - var bookmarkMessage = MapBookmark(bookmark); - - var message = new ExecuteWorkflowRequest - { - Bookmark = bookmarkMessage, - Input = input!?.Serialize(), - DefinitionId = workflow.Identity.DefinitionId, - VersionOptions = VersionOptions.SpecificVersion(workflow.Identity.Version).ToString(), - WorkflowState = JsonSerializer.Serialize(workflowState, _serializerOptionsProvider.CreateDefaultOptions()) - }; - - var client = _grainClientFactory.CreateWorkflowInstanceGrainClient(workflowState.Id); - var response = await client.Execute(message, CancellationTokens.FromSeconds(600)); - - if (response == null) - throw new TimeoutException("Did not receive a response from the WorkflowInstance actor within the configured amount of time."); - - var bookmarks = response.Bookmarks.Select(MapBookmark).ToList(); - - return new RunWorkflowResult(workflowState, bookmarks); - } - - private static ProtoBookmark? MapBookmark(Bookmark? bookmark) - { - if (bookmark == null) - return null; - - return new ProtoBookmark - { - Id = bookmark.Id, - Name = bookmark.Name, - Hash = bookmark.Hash, - Payload = bookmark.Data, - ActivityId = bookmark.ActivityId, - ActivityInstanceId = bookmark.ActivityInstanceId, - CallbackMethodName = bookmark.CallbackMethodName, - }; - } - - private static Bookmark MapBookmark(ProtoBookmark protoBookmark) - { - return new Bookmark( - protoBookmark.Id, - protoBookmark.Name, - protoBookmark.Hash, - protoBookmark.Payload, - protoBookmark.ActivityId, - protoBookmark.ActivityInstanceId, - protoBookmark.CallbackMethodName - ); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowRuntime.cs b/src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowRuntime.cs new file mode 100644 index 0000000000..17ee9d2919 --- /dev/null +++ b/src/modules/Elsa.ProtoActor/Implementations/ProtoActorWorkflowRuntime.cs @@ -0,0 +1,76 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.ProtoActor.Extensions; +using Elsa.Runtime.Protos; +using Elsa.Workflows.Core; +using Elsa.Workflows.Core.Services; +using Elsa.Workflows.Runtime.Services; +using Proto.Cluster; + +namespace Elsa.ProtoActor.Implementations; + +public class ProtoActorWorkflowRuntime : IWorkflowRuntime +{ + private readonly Cluster _cluster; + private readonly IIdentityGenerator _identityGenerator; + private readonly IHasher _hasher; + + public ProtoActorWorkflowRuntime(Cluster cluster, IIdentityGenerator identityGenerator, IHasher hasher) + { + _cluster = cluster; + _identityGenerator = identityGenerator; + _hasher = hasher; + } + + public async Task StartWorkflowAsync(string definitionId, StartWorkflowOptions options, CancellationToken cancellationToken = default) + { + var versionOptions = options.VersionOptions; + var correlationId = options.CorrelationId; + var input = options.Input; + + var request = new StartWorkflowRequest + { + DefinitionId = definitionId, + VersionOptions = versionOptions.ToString(), + CorrelationId = correlationId.WithDefault(""), + Input = input?.Serialize() + }; + + var workflowInstanceId = _identityGenerator.GenerateId(); + var client = _cluster.GetWorkflowGrain(workflowInstanceId); + var response = await client.Start(request, cancellationToken); + + return new StartWorkflowResult(workflowInstanceId); + } + + public async Task ResumeWorkflowAsync(string instanceId, string bookmarkId, ResumeWorkflowOptions options, CancellationToken cancellationToken = default) + { + var request = new ResumeWorkflowRequest + { + InstanceId = instanceId, + BookmarkId = bookmarkId, + Input = options.Input?.Serialize() + }; + + var client = _cluster.GetWorkflowGrain(instanceId); + var response = await client.Resume(request, cancellationToken); + + return new ResumeWorkflowResult(); + } + + public async Task TriggerWorkflowsAsync(object bookmarkPayload, TriggerWorkflowsOptions options, CancellationToken cancellationToken = default) + { + var hash = _hasher.Hash(bookmarkPayload); + var client = _cluster.GetBookmarkGrain(hash); + var bookmarksResponse = await client.Resolve(new ResolveBookmarksRequest(), cancellationToken); + var bookmarks = bookmarksResponse!.Bookmarks; + + foreach (var bookmark in bookmarks) + { + var workflowInstanceId = bookmark.WorkflowInstanceId; + var resumeResult = await ResumeWorkflowAsync(workflowInstanceId, bookmark.BookmarkId, new ResumeWorkflowOptions(options.Input), cancellationToken); + } + + return new TriggerWorkflowsResult(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Protos/Grains.proto b/src/modules/Elsa.ProtoActor/Protos/Grains.proto index 12a4b6f20b..f5252a7b83 100644 --- a/src/modules/Elsa.ProtoActor/Protos/Grains.proto +++ b/src/modules/Elsa.ProtoActor/Protos/Grains.proto @@ -5,12 +5,13 @@ option csharp_namespace = "Elsa.Runtime.Protos"; import "google/protobuf/empty.proto"; import "Messages.proto"; -service WorkflowDefinitionGrain { - rpc Execute (ExecuteWorkflowDefinitionRequest) returns (ExecuteWorkflowDefinitionResponse); +service WorkflowGrain { + rpc Start (StartWorkflowRequest) returns (StartWorkflowResponse); + rpc Resume (ResumeWorkflowRequest) returns (ResumeWorkflowResponse); } -service WorkflowInstanceGrain { - rpc ExecuteExistingInstance (ExecuteExistingWorkflowInstanceRequest) returns (ExecuteWorkflowInstanceResponse); - rpc ExecuteNewInstance (ExecuteNewWorkflowInstanceRequest) returns (ExecuteWorkflowInstanceResponse); - rpc Execute (ExecuteWorkflowRequest) returns (ExecuteWorkflowInstanceResponse); +service BookmarkGrain { + rpc Store(StoreBookmarksRequest) returns (Unit); + rpc RemoveByWorkflow(RemoveBookmarksByWorkflowRequest) returns (Unit); + rpc Resolve (ResolveBookmarksRequest) returns (ResolveBookmarksResponse); } \ No newline at end of file diff --git a/src/modules/Elsa.ProtoActor/Protos/Messages.proto b/src/modules/Elsa.ProtoActor/Protos/Messages.proto index 9352896f55..89db25805c 100644 --- a/src/modules/Elsa.ProtoActor/Protos/Messages.proto +++ b/src/modules/Elsa.ProtoActor/Protos/Messages.proto @@ -6,58 +6,51 @@ option csharp_namespace = "Elsa.Runtime.Protos"; // Empty response type. message Unit {} -message ExecuteWorkflowDefinitionRequest { - string id = 1; +message StartWorkflowRequest { + string definitionId = 1; string versionOptions = 2; - optional Input input = 3; - optional string CorrelationId = 4; + optional string correlationId = 3; + optional Input input = 4; } -message ExecuteWorkflowDefinitionResponse { - Json workflowState = 1; - repeated Bookmark bookmarks = 2; +message StartWorkflowResponse { + RunWorkflowResult Result = 1; } -message ExecuteExistingWorkflowInstanceRequest { +message ResumeWorkflowRequest { string instanceId = 1; - optional Bookmark bookmark = 2; + string bookmarkId = 2; optional Input input = 3; } -message ExecuteNewWorkflowInstanceRequest { - string definitionId = 1; - string versionOptions = 2; - optional Input input = 3; - optional string CorrelationId = 4; +message ResumeWorkflowResponse { + RunWorkflowResult Result = 1; } -message ExecuteWorkflowRequest { - string definitionId = 1; - string versionOptions = 2; - string workflowState = 3; - optional Bookmark bookmark = 4; - optional Input input = 5; +enum RunWorkflowResult { + Finished = 0; + Suspended = 1; } -message ExecuteWorkflowInstanceResponse { - Json workflowState = 1; - repeated Bookmark bookmarks = 2; +message StoreBookmarksRequest { + string WorkflowInstanceId = 1; + repeated string BookmarkIds = 2; } -message HandleStimulusRequest { - string activityTypeName = 1; - optional string hash = 2; - optional Input input = 3; +message RemoveBookmarksByWorkflowRequest { + string WorkflowInstanceId = 1; +} + +message ResolveBookmarksRequest { +} + +message ResolveBookmarksResponse { + repeated StoredBookmark Bookmarks = 1; } -message Bookmark { - string id = 1; - string name = 2; - optional string hash = 3; - optional string payload = 4; - string activityId = 5; - string activityInstanceId = 6; - optional google.protobuf.StringValue callbackMethodName = 7; +message StoredBookmark { + string WorkflowInstanceId = 1; + string BookmarkId = 2; } message Json { diff --git a/src/modules/Elsa.ProtoActor/Snapshots.cs b/src/modules/Elsa.ProtoActor/Snapshots.cs new file mode 100644 index 0000000000..38e768acc2 --- /dev/null +++ b/src/modules/Elsa.ProtoActor/Snapshots.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Elsa.Runtime.Protos; +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Core.State; + +namespace Elsa.ProtoActor; + +public record WorkflowSnapshot(string DefinitionId, int Version, WorkflowState WorkflowState, ICollection Bookmarks, IDictionary? Input); +public record BookmarkSnapshot(ICollection Bookmarks); \ No newline at end of file diff --git a/src/modules/Elsa.Scheduling/HostedServices/ScheduleWorkflowsHostedService.cs b/src/modules/Elsa.Scheduling/HostedServices/ScheduleWorkflowsHostedService.cs index 2d03176446..e529f4aebd 100644 --- a/src/modules/Elsa.Scheduling/HostedServices/ScheduleWorkflowsHostedService.cs +++ b/src/modules/Elsa.Scheduling/HostedServices/ScheduleWorkflowsHostedService.cs @@ -37,7 +37,8 @@ private async Task ScheduleBookmarksAsync(CancellationToken cancellationToken) foreach (var bookmarksGroup in groupedBookmarks) { var workflowInstanceId = bookmarksGroup.Key; - await _workflowBookmarkScheduler.ScheduleBookmarksAsync(workflowInstanceId, bookmarksGroup, cancellationToken); + var bookmarks = bookmarksGroup.Select(x => x.ToBookmark()); + await _workflowBookmarkScheduler.ScheduleBookmarksAsync(workflowInstanceId, bookmarks, cancellationToken); } } } \ No newline at end of file diff --git a/src/modules/Elsa.Scheduling/Implementations/WorkflowBookmarkScheduler.cs b/src/modules/Elsa.Scheduling/Implementations/WorkflowBookmarkScheduler.cs index 843098a660..747fa4e91a 100644 --- a/src/modules/Elsa.Scheduling/Implementations/WorkflowBookmarkScheduler.cs +++ b/src/modules/Elsa.Scheduling/Implementations/WorkflowBookmarkScheduler.cs @@ -8,6 +8,7 @@ using Elsa.Scheduling.Activities; using Elsa.Scheduling.Jobs; using Elsa.Scheduling.Services; +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Persistence.Entities; using Elsa.Workflows.Persistence.Extensions; @@ -23,7 +24,7 @@ public WorkflowBookmarkScheduler(IJobScheduler jobScheduler) _jobScheduler = jobScheduler; } - public async Task ScheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default) + public async Task ScheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default) { var bookmarkList = bookmarks.ToList(); @@ -40,7 +41,7 @@ public async Task ScheduleBookmarksAsync(string workflowInstanceId, IEnumerable< { var payload = JsonSerializer.Deserialize(bookmark.Data!)!; var resumeAt = payload.ResumeAt; - var job = new ResumeWorkflowJob(workflowInstanceId, bookmark.ToBookmark()); + var job = new ResumeWorkflowJob(workflowInstanceId, bookmark.Id); var schedule = new SpecificInstantSchedule(resumeAt); await _jobScheduler.ScheduleAsync(job, bookmark.Id, schedule, groupKeys, cancellationToken); } @@ -50,13 +51,13 @@ public async Task ScheduleBookmarksAsync(string workflowInstanceId, IEnumerable< { var payload = JsonSerializer.Deserialize(bookmark.Data!)!; var executeAt = payload.ExecuteAt; - var job = new ResumeWorkflowJob(workflowInstanceId, bookmark.ToBookmark()); + var job = new ResumeWorkflowJob(workflowInstanceId, bookmark.Id); var schedule = new SpecificInstantSchedule(executeAt); await _jobScheduler.ScheduleAsync(job, bookmark.Id, schedule, groupKeys, cancellationToken); } } - public async Task UnscheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default) + public async Task UnscheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default) { var bookmarkList = bookmarks.ToList(); var delayBookmarks = bookmarkList.Filter().ToList(); diff --git a/src/modules/Elsa.Scheduling/Jobs/ResumeWorkflowJob.cs b/src/modules/Elsa.Scheduling/Jobs/ResumeWorkflowJob.cs index f41b0621ba..a563413d54 100644 --- a/src/modules/Elsa.Scheduling/Jobs/ResumeWorkflowJob.cs +++ b/src/modules/Elsa.Scheduling/Jobs/ResumeWorkflowJob.cs @@ -15,18 +15,18 @@ public ResumeWorkflowJob() { } - public ResumeWorkflowJob(string workflowInstanceId, Bookmark bookmark) + public ResumeWorkflowJob(string workflowInstanceId, string bookmarkId) { WorkflowInstanceId = workflowInstanceId; - Bookmark = bookmark; + BookmarkId = bookmarkId; } public string WorkflowInstanceId { get; set; } = default!; - public Bookmark Bookmark { get; set; } = default!; + public string BookmarkId { get; set; } = default!; protected override async ValueTask ExecuteAsync(JobExecutionContext context) { - var request = new DispatchWorkflowInstanceRequest(WorkflowInstanceId, Bookmark); + var request = new DispatchWorkflowInstanceRequest(WorkflowInstanceId, BookmarkId); var workflowDispatcher = context.GetRequiredService(); await workflowDispatcher.DispatchAsync(request, context.CancellationToken); } diff --git a/src/modules/Elsa.Scheduling/Services/IWorkflowBookmarkScheduler.cs b/src/modules/Elsa.Scheduling/Services/IWorkflowBookmarkScheduler.cs index 86406e2f4d..b96d9244b1 100644 --- a/src/modules/Elsa.Scheduling/Services/IWorkflowBookmarkScheduler.cs +++ b/src/modules/Elsa.Scheduling/Services/IWorkflowBookmarkScheduler.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Persistence.Entities; namespace Elsa.Scheduling.Services; @@ -10,6 +11,6 @@ namespace Elsa.Scheduling.Services; /// public interface IWorkflowBookmarkScheduler { - Task ScheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default); - Task UnscheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default); + Task ScheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default); + Task UnscheduleBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/ApiResults/ExecuteWorkflowDefinitionResult.cs b/src/modules/Elsa.Workflows.Api/ApiResults/ExecuteWorkflowDefinitionResult.cs deleted file mode 100644 index a946193147..0000000000 --- a/src/modules/Elsa.Workflows.Api/ApiResults/ExecuteWorkflowDefinitionResult.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Elsa.Models; -using Elsa.Workflows.Core.Serialization; -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; - -namespace Elsa.Workflows.Api.ApiResults; - -public class ExecuteWorkflowDefinitionResult : IActionResult -{ - public ExecuteWorkflowDefinitionResult(string definitionId, string? correlationId = default) - { - DefinitionId = definitionId; - CorrelationId = correlationId; - } - - public string DefinitionId { get; } - public string? CorrelationId { get; } - - public async Task ExecuteResultAsync(ActionContext context) - { - var httpContext = context.HttpContext; - var response = httpContext.Response; - var workflowInvoker = httpContext.RequestServices.GetRequiredService(); - var serializerOptionsProvider = httpContext.RequestServices.GetRequiredService(); - var executeRequest = new InvokeWorkflowDefinitionRequest(DefinitionId, VersionOptions.Published, CorrelationId: CorrelationId); - var result = await workflowInvoker.InvokeAsync(executeRequest, CancellationToken.None); - var serializerOptions = serializerOptionsProvider.CreateApiOptions(); - - if (!response.HasStarted) - await response.WriteAsJsonAsync(result, serializerOptions, httpContext.RequestAborted); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/Events/Trigger/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/Events/Trigger/Endpoint.cs index 81374585bc..82e0270bca 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/Events/Trigger/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/Events/Trigger/Endpoint.cs @@ -1,27 +1,21 @@ -using System.Linq; using System.Threading; using System.Threading.Tasks; using Elsa.Abstractions; -using Elsa.Workflows.Core.Activities; -using Elsa.Workflows.Core.Helpers; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.Services; -using Elsa.Workflows.Runtime.Models; using Elsa.Workflows.Runtime.Services; namespace Elsa.Workflows.Api.Endpoints.Events.Trigger; public class Trigger : ElsaEndpoint { + private readonly IWorkflowRuntime _workflowRuntime; private readonly IHasher _hasher; - private readonly IStimulusInterpreter _stimulusInterpreter; - private readonly IWorkflowInstructionExecutor _workflowInstructionExecutor; - public Trigger(IHasher hasher, IStimulusInterpreter stimulusInterpreter, IWorkflowInstructionExecutor workflowInstructionExecutor) + public Trigger(IWorkflowRuntime workflowRuntime, IHasher hasher) { + _workflowRuntime = workflowRuntime; _hasher = hasher; - _stimulusInterpreter = stimulusInterpreter; - _workflowInstructionExecutor = workflowInstructionExecutor; } public override void Configure() @@ -33,15 +27,11 @@ public override void Configure() public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { var eventBookmark = new EventBookmarkData(request.EventName); - var hash = _hasher.Hash(eventBookmark); - var stimulus = Stimulus.Standard(ActivityTypeNameHelper.GenerateTypeName(), hash); - var instructions = await _stimulusInterpreter.GetExecutionInstructionsAsync(stimulus, cancellationToken); - var executionResults = (await _workflowInstructionExecutor.ExecuteInstructionsAsync(instructions, CancellationToken.None)).ToList(); - + var result = await _workflowRuntime.TriggerWorkflowsAsync(eventBookmark, new TriggerWorkflowsOptions(), cancellationToken); + if (!HttpContext.Response.HasStarted) { - var response = new Response(executionResults); - await SendOkAsync(response, cancellationToken); + await SendOkAsync(cancellationToken); } } } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs index fd50172176..c0f833dafd 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs @@ -35,7 +35,7 @@ public override async Task ExecuteAsync(Request request, CancellationT foreach (var definitionId in request.DefinitionIds) { - var definition = (await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); + var definition = (await _store.FindManyByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs index 9f08e7ee38..cf7ad4a8a8 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs @@ -34,7 +34,7 @@ public override async Task ExecuteAsync(Request request, CancellationT foreach (var definitionId in request.DefinitionIds) { - var definition = (await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); + var definition = (await _store.FindManyByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Endpoint.cs index 2155c95aaf..7f325774b7 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Endpoint.cs @@ -3,7 +3,6 @@ using Elsa.Abstractions; using Elsa.Models; using Elsa.Workflows.Persistence.Services; -using Elsa.Workflows.Runtime.Models; using Elsa.Workflows.Runtime.Services; namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute; @@ -11,12 +10,12 @@ namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute; public class Execute : ElsaEndpoint { private readonly IWorkflowDefinitionStore _store; - private readonly IWorkflowInvoker _workflowInvoker; + private readonly IWorkflowRuntime _workflowRuntime; - public Execute(IWorkflowDefinitionStore store, IWorkflowInvoker workflowInvoker) + public Execute(IWorkflowDefinitionStore store, IWorkflowRuntime workflowRuntime) { _store = store; - _workflowInvoker = workflowInvoker; + _workflowRuntime = workflowRuntime; } public override void Configure() @@ -27,7 +26,8 @@ public override void Configure() public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { - var exists = await _store.GetExistsAsync(request.DefinitionId, VersionOptions.Published, cancellationToken); + var definitionId = request.DefinitionId; + var exists = await _store.GetExistsAsync(definitionId, VersionOptions.Published, cancellationToken); if (!exists) { @@ -35,10 +35,11 @@ public override async Task HandleAsync(Request request, CancellationToken cancel return; } - var executeRequest = new InvokeWorkflowDefinitionRequest(request.DefinitionId, VersionOptions.Published, CorrelationId: request.CorrelationId); - var result = await _workflowInvoker.InvokeAsync(executeRequest, CancellationToken.None); + var correlationId = request.CorrelationId; + var startWorkflowOptions = new StartWorkflowOptions(correlationId, VersionOptions: VersionOptions.Published); + var result = await _workflowRuntime.StartWorkflowAsync(definitionId, startWorkflowOptions, cancellationToken); if (!HttpContext.Response.HasStarted) - await SendOkAsync(new Response(result.WorkflowState, result.Bookmarks), cancellationToken); + await SendOkAsync(new Response(result.InstanceId), cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs index d757ccfd3e..a7677ddb73 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/Models.cs @@ -12,12 +12,10 @@ public class Request public class Response { - public Response(WorkflowState workflowState, IReadOnlyCollection bookmarks) + public Response(string instanceId) { - WorkflowState = workflowState; - Bookmarks = bookmarks; + InstanceId = instanceId; } - - public WorkflowState WorkflowState { get; } - public IReadOnlyCollection Bookmarks { get; } + + public string InstanceId { get; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs index f11f1f8a15..9afb92cfb4 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs @@ -42,7 +42,7 @@ public override async Task HandleAsync(Request request, CancellationToken cancel { var serializerOptions = _serializerOptionsProvider.CreateApiOptions(); var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest; - var definition = (await _store.FindByDefinitionIdAsync(request.DefinitionId, versionOptions, cancellationToken)).FirstOrDefault(); + var definition = (await _store.FindManyByDefinitionIdAsync(request.DefinitionId, versionOptions, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs index 8845a4ce5a..f32cc299e4 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Get/Endpoint.cs @@ -27,7 +27,7 @@ public override void Configure() public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest; - var definition = (await _store.FindByDefinitionIdAsync(request.DefinitionId, versionOptions, cancellationToken)).FirstOrDefault(); + var definition = (await _store.FindManyByDefinitionIdAsync(request.DefinitionId, versionOptions, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs index bc7f1a6aa9..7c3715f8fc 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs @@ -29,7 +29,7 @@ public override void Configure() public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { - var definition = (await _store.FindByDefinitionIdAsync(request.DefinitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); + var definition = (await _store.FindManyByDefinitionIdAsync(request.DefinitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs index 370f3572b3..e2e8c1cc9d 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs @@ -29,7 +29,7 @@ public override void Configure() public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { - var definition = (await _store.FindByDefinitionIdAsync(request.DefinitionId, VersionOptions.LatestOrPublished, cancellationToken)).FirstOrDefault(); + var definition = (await _store.FindManyByDefinitionIdAsync(request.DefinitionId, VersionOptions.LatestOrPublished, cancellationToken)).FirstOrDefault(); if (definition == null) { diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs index de2e88b3cf..e882592c22 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/List.cs @@ -27,7 +27,7 @@ public override async Task HandleAsync(CancellationToken ct) { var definitionId = Route("definitionId")!; - var result = await _store.FindByDefinitionIdAsync(definitionId, VersionOptions.All, ct); + var result = await _store.FindManyByDefinitionIdAsync(definitionId, VersionOptions.All, ct); if (!result.Any()) { await SendNotFoundAsync(ct); diff --git a/src/modules/Elsa.Workflows.Core/Implementations/WorkflowRunner.cs b/src/modules/Elsa.Workflows.Core/Implementations/WorkflowRunner.cs index dcd1601af6..2715540920 100644 --- a/src/modules/Elsa.Workflows.Core/Implementations/WorkflowRunner.cs +++ b/src/modules/Elsa.Workflows.Core/Implementations/WorkflowRunner.cs @@ -38,31 +38,49 @@ public WorkflowRunner( public async Task RunAsync(IActivity activity, IDictionary? input = default, CancellationToken cancellationToken = default) { - var workflow = Workflow.FromActivity(activity); - return await RunAsync(workflow, input, cancellationToken); + var instanceId = _identityGenerator.GenerateId(); + return await RunAsync(instanceId, activity, input, cancellationToken); } public async Task RunAsync(IWorkflow workflow, IDictionary? input = default, CancellationToken cancellationToken = default) + { + var instanceId = _identityGenerator.GenerateId(); + return await RunAsync(workflow, instanceId, input, cancellationToken); + } + + public async Task RunAsync(IDictionary? input = default, CancellationToken cancellationToken = default) where T : IWorkflow + { + var instanceId = _identityGenerator.GenerateId(); + return await RunAsync(instanceId, input, cancellationToken); + } + + public async Task RunAsync(string instanceId, IActivity activity, IDictionary? input = default, CancellationToken cancellationToken = default) + { + var workflow = Workflow.FromActivity(activity); + return await RunAsync(workflow, instanceId, input, cancellationToken); + } + + public async Task RunAsync(IWorkflow workflow, string instanceId, IDictionary? input = default, CancellationToken cancellationToken = default) { var builder = _workflowBuilderFactory.CreateBuilder(); var workflowDefinition = await builder.BuildWorkflowAsync(workflow, cancellationToken); - return await RunAsync(workflowDefinition, input, cancellationToken); + return await RunAsync(workflowDefinition, instanceId, input, cancellationToken); } - public async Task RunAsync(IDictionary? input = default, CancellationToken cancellationToken = default) where T : IWorkflow + public async Task RunAsync(string instanceId, IDictionary? input = default, CancellationToken cancellationToken = default) where T : IWorkflow { var builder = _workflowBuilderFactory.CreateBuilder(); var workflowDefinition = await builder.BuildWorkflowAsync(cancellationToken); - return await RunAsync(workflowDefinition, input, cancellationToken); + return await RunAsync(workflowDefinition, instanceId, input, cancellationToken); } - public async Task RunAsync(Workflow workflow, IDictionary? input, CancellationToken cancellationToken = default) + public async Task RunAsync(Workflow workflow, string instanceId, IDictionary? input, CancellationToken cancellationToken = default) { // Create a child scope. using var scope = _serviceScopeFactory.CreateScope(); // Setup a workflow execution context. - var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(scope.ServiceProvider, workflow, default, default, input, default, cancellationToken); + var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(scope.ServiceProvider, workflow, instanceId, default, default, input, default, cancellationToken); // Schedule the first activity. workflowExecutionContext.ScheduleRoot(); @@ -76,7 +94,7 @@ public async Task RunAsync(Workflow workflow, WorkflowState w using var scope = _serviceScopeFactory.CreateScope(); // Create workflow execution context. - var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(scope.ServiceProvider, workflow, workflowState, default, input, default, cancellationToken); + var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(scope.ServiceProvider, workflow, workflowState.Id, workflowState, default, input, default, cancellationToken); // Schedule the first node. workflowExecutionContext.ScheduleRoot(); @@ -90,7 +108,7 @@ public async Task RunAsync(Workflow workflow, WorkflowState w using var scope = _serviceScopeFactory.CreateScope(); // Create workflow execution context. - var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(scope.ServiceProvider, workflow, workflowState, bookmark, input, default, cancellationToken); + var workflowExecutionContext = await CreateWorkflowExecutionContextAsync(scope.ServiceProvider, workflow, workflowState.Id, workflowState, bookmark, input, default, cancellationToken); if (bookmark != null) workflowExecutionContext.ScheduleBookmark(bookmark); @@ -116,6 +134,7 @@ public async Task RunAsync(WorkflowExecutionContext workflowE private async Task CreateWorkflowExecutionContextAsync( IServiceProvider serviceProvider, Workflow workflow, + string instanceId, WorkflowState? workflowState, Bookmark? bookmark, IDictionary? input, @@ -134,9 +153,8 @@ private async Task CreateWorkflowExecutionContextAsync var scheduler = _schedulerFactory.CreateScheduler(); // Setup a workflow execution context. - var id = workflowState?.Id ?? _identityGenerator.GenerateId(); var correlationId = workflowState?.CorrelationId; - var workflowExecutionContext = new WorkflowExecutionContext(serviceProvider, id, correlationId, workflow, graph, scheduler, bookmark, input, executeActivityDelegate, cancellationToken); + var workflowExecutionContext = new WorkflowExecutionContext(serviceProvider, instanceId, correlationId, workflow, graph, scheduler, bookmark, input, executeActivityDelegate, cancellationToken); // Restore workflow execution context from state, if provided. if (workflowState != null) _workflowStateSerializer.DeserializeState(workflowExecutionContext, workflowState); diff --git a/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs b/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs index 9fe682928c..a51cc0dfe6 100644 --- a/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs +++ b/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs @@ -3,7 +3,7 @@ namespace Elsa.Workflows.Core.Models; public record Bookmark( string Id, string Name, - string? Hash, + string Hash, string? Data, string ActivityId, string ActivityInstanceId, diff --git a/src/modules/Elsa.Workflows.Core/Models/InvokeWorkflowResult.cs b/src/modules/Elsa.Workflows.Core/Models/InvokeWorkflowResult.cs new file mode 100644 index 0000000000..aa7d934702 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Models/InvokeWorkflowResult.cs @@ -0,0 +1,5 @@ +using Elsa.Workflows.Core.State; + +namespace Elsa.Workflows.Core.Models; + +public record InvokeWorkflowResult(WorkflowState WorkflowState, ICollection Bookmarks); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/RunWorkflowResult.cs b/src/modules/Elsa.Workflows.Core/Models/RunWorkflowResult.cs index 21344e8e2a..a4639c93a6 100644 --- a/src/modules/Elsa.Workflows.Core/Models/RunWorkflowResult.cs +++ b/src/modules/Elsa.Workflows.Core/Models/RunWorkflowResult.cs @@ -2,4 +2,4 @@ namespace Elsa.Workflows.Core.Models; -public record RunWorkflowResult(WorkflowState WorkflowState, IReadOnlyCollection Bookmarks); \ No newline at end of file +public record RunWorkflowResult(WorkflowState WorkflowState, ICollection Bookmarks); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/WorkflowExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Models/WorkflowExecutionContext.cs index ab1e1f3941..39e18a8d56 100644 --- a/src/modules/Elsa.Workflows.Core/Models/WorkflowExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Models/WorkflowExecutionContext.cs @@ -72,8 +72,8 @@ public WorkflowExecutionContext( public ExecuteActivityDelegate? ExecuteDelegate { get; set; } public CancellationToken CancellationToken { get; } - public IReadOnlyCollection Bookmarks => new ReadOnlyCollection(_bookmarks); - public IReadOnlyCollection CompletionCallbacks => new ReadOnlyCollection(_completionCallbackEntries); + public ICollection Bookmarks => new ReadOnlyCollection(_bookmarks); + public ICollection CompletionCallbacks => new ReadOnlyCollection(_completionCallbackEntries); public ICollection ActivityExecutionContexts { get; set; } = new List(); /// diff --git a/src/modules/Elsa.Workflows.Core/Services/IWorkflowRunner.cs b/src/modules/Elsa.Workflows.Core/Services/IWorkflowRunner.cs index d31d4a7dfd..acb0667cce 100644 --- a/src/modules/Elsa.Workflows.Core/Services/IWorkflowRunner.cs +++ b/src/modules/Elsa.Workflows.Core/Services/IWorkflowRunner.cs @@ -8,7 +8,10 @@ public interface IWorkflowRunner Task RunAsync(IActivity activity, IDictionary? input = default, CancellationToken cancellationToken = default); Task RunAsync(IWorkflow workflow, IDictionary? input = default, CancellationToken cancellationToken = default); Task RunAsync(IDictionary? input = default, CancellationToken cancellationToken = default) where T : IWorkflow; - Task RunAsync(Workflow workflow, IDictionary? input = default, CancellationToken cancellationToken = default); + Task RunAsync(string instanceId, IActivity activity, IDictionary? input = default, CancellationToken cancellationToken = default); + Task RunAsync(IWorkflow workflow, string instanceId, IDictionary? input = default, CancellationToken cancellationToken = default); + Task RunAsync(string instanceId, IDictionary? input = default, CancellationToken cancellationToken = default) where T : IWorkflow; + Task RunAsync(Workflow workflow, string instanceId, IDictionary? input = default, CancellationToken cancellationToken = default); Task RunAsync(Workflow workflow, WorkflowState workflowState, IDictionary? input = default, CancellationToken cancellationToken = default); Task RunAsync(Workflow workflow, WorkflowState workflowState, Bookmark? bookmark, IDictionary? input = default, CancellationToken cancellationToken = default); Task RunAsync(WorkflowExecutionContext workflowExecutionContext); diff --git a/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs index 4a90eb2a51..146284e244 100644 --- a/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs +++ b/src/modules/Elsa.Workflows.Management/Implementations/WorkflowDefinitionPublisher.cs @@ -56,7 +56,7 @@ public WorkflowDefinition New() public async Task PublishAsync(string definitionId, CancellationToken cancellationToken = default) { - var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); + var definition = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) return null; @@ -78,11 +78,7 @@ public async Task PublishAsync(WorkflowDefinition definition await _workflowDefinitionStore.SaveAsync(publishedAndOrLatestWorkflow, cancellationToken); } - if (definition.IsPublished) - definition.Version++; - else - definition.IsPublished = true; - + definition.IsPublished = true; definition.IsLatest = true; definition = Initialize(definition); @@ -94,7 +90,7 @@ public async Task PublishAsync(WorkflowDefinition definition public async Task RetractAsync(string definitionId, CancellationToken cancellationToken = default) { - var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Published, cancellationToken)).FirstOrDefault(); + var definition = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(definitionId, VersionOptions.Published, cancellationToken)).FirstOrDefault(); if (definition == null) return null; @@ -118,7 +114,7 @@ public async Task RetractAsync(WorkflowDefinition definition public async Task GetDraftAsync(string definitionId, int? version, CancellationToken cancellationToken = default) { - var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, version.HasValue ? VersionOptions.SpecificVersion(version.Value) : VersionOptions.Latest, cancellationToken)).FirstOrDefault(); + var definition = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(definitionId, version.HasValue ? VersionOptions.SpecificVersion(version.Value) : VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (definition == null) return null; @@ -139,7 +135,7 @@ public async Task SaveDraftAsync(WorkflowDefinition definiti { var draft = definition; var definitionId = definition.DefinitionId; - var latestVersion = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); + var latestVersion = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(definitionId, VersionOptions.Latest, cancellationToken)).FirstOrDefault(); if (latestVersion is { IsPublished: true, IsLatest: true }) { diff --git a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs index d93a725a64..33b8fd86aa 100644 --- a/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Persistence.EntityFrameworkCore/Implementations/EFCoreWorkflowDefinitionStore.cs @@ -18,13 +18,20 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore public async Task FindByIdAsync(string id, CancellationToken cancellationToken = default) => await _store.FindAsync(x => x.Id == id, cancellationToken); - public async Task> FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) + public async Task> FindManyByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { Expression> predicate = x => x.DefinitionId == definitionId; predicate = predicate.WithVersion(versionOptions); return await _store.FindManyAsync(predicate, cancellationToken); } + public async Task FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) + { + Expression> predicate = x => x.DefinitionId == definitionId; + predicate = predicate.WithVersion(versionOptions); + return await _store.FindAsync(predicate, cancellationToken); + } + public async Task FindByNameAsync(string name, VersionOptions versionOptions, CancellationToken cancellationToken = default) { Expression> predicate = x => x.Name == name; diff --git a/src/modules/Elsa.Workflows.Persistence/Extensions/WorkflowBookmarkExtensions.cs b/src/modules/Elsa.Workflows.Persistence/Extensions/WorkflowBookmarkExtensions.cs index 4cd0a02c24..84a9ac9fa4 100644 --- a/src/modules/Elsa.Workflows.Persistence/Extensions/WorkflowBookmarkExtensions.cs +++ b/src/modules/Elsa.Workflows.Persistence/Extensions/WorkflowBookmarkExtensions.cs @@ -1,4 +1,5 @@ using Elsa.Workflows.Core.Helpers; +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.Services; using Elsa.Workflows.Persistence.Entities; @@ -6,7 +7,7 @@ namespace Elsa.Workflows.Persistence.Extensions; public static class WorkflowBookmarkExtensions { - public static IEnumerable Filter(this IEnumerable bookmarks) where T : IActivity + public static IEnumerable Filter(this IEnumerable bookmarks) where T : IActivity { var bookmarkName = ActivityTypeNameHelper.GenerateTypeName(); return bookmarks.Where(x => x.Name == bookmarkName); diff --git a/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs index c3a4f0a4db..4b260be97c 100644 --- a/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Persistence/Implementations/MemoryWorkflowDefinitionStore.cs @@ -32,12 +32,18 @@ public MemoryWorkflowDefinitionStore( return Task.FromResult(definition); } - public Task> FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) + public Task> FindManyByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { var definition = _store.FindMany(x => x.DefinitionId == definitionId && x.WithVersion(versionOptions)); return Task.FromResult(definition); } + public Task FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) + { + var definition = _store.Find(x => x.DefinitionId == definitionId && x.WithVersion(versionOptions)); + return Task.FromResult(definition); + } + public Task FindByNameAsync(string name, VersionOptions versionOptions, CancellationToken cancellationToken = default) { var definition = _store.Find(x => x.Name == name && x.WithVersion(versionOptions)); diff --git a/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs index 2ea60d9725..2a3dbe6957 100644 --- a/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Persistence/Services/IWorkflowDefinitionStore.cs @@ -12,9 +12,14 @@ public interface IWorkflowDefinitionStore Task FindByIdAsync(string id, CancellationToken cancellationToken = default); /// - /// Finds a workflow definition by its logical definition ID and specified version options. + /// Finds all workflow definitions by its logical definition ID and specified version options. /// - Task> FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); + Task> FindManyByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); + + /// + /// Finds the workflow definition by its logical definition ID and specified version options. + /// + Task FindByDefinitionIdAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); /// /// Finds a workflow definition by name and specified version options. diff --git a/src/modules/Elsa.Workflows.Runtime/Abstractions/StimulusHandler.cs b/src/modules/Elsa.Workflows.Runtime/Abstractions/StimulusHandler.cs index 5697b9da6e..d2bb5fc5f5 100644 --- a/src/modules/Elsa.Workflows.Runtime/Abstractions/StimulusHandler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Abstractions/StimulusHandler.cs @@ -1,17 +1,17 @@ -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Abstractions; - -public abstract class StimulusHandler : IStimulusHandler where TStimulus:IStimulus -{ - bool IStimulusHandler.GetSupportsStimulus(IStimulus stimulus) => stimulus is TStimulus; - ValueTask> IStimulusHandler.GetInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken) => GetInstructionsAsync((TStimulus)stimulus, cancellationToken); - - protected virtual ValueTask> GetInstructionsAsync(TStimulus stimulus, CancellationToken cancellationToken = default) - { - var instruction = GetInstructions(stimulus); - return ValueTask.FromResult(instruction); - } - - protected virtual IEnumerable GetInstructions(TStimulus stimulus) => ArraySegment.Empty; -} \ No newline at end of file +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Abstractions; +// +// public abstract class StimulusHandler : IStimulusHandler where TStimulus:IStimulus +// { +// bool IStimulusHandler.GetSupportsStimulus(IStimulus stimulus) => stimulus is TStimulus; +// ValueTask> IStimulusHandler.GetInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken) => GetInstructionsAsync((TStimulus)stimulus, cancellationToken); +// +// protected virtual ValueTask> GetInstructionsAsync(TStimulus stimulus, CancellationToken cancellationToken = default) +// { +// var instruction = GetInstructions(stimulus); +// return ValueTask.FromResult(instruction); +// } +// +// protected virtual IEnumerable GetInstructions(TStimulus stimulus) => ArraySegment.Empty; +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Abstractions/WorkflowInstructionInterpreter.cs b/src/modules/Elsa.Workflows.Runtime/Abstractions/WorkflowInstructionInterpreter.cs index 34978013bb..290dfac0dd 100644 --- a/src/modules/Elsa.Workflows.Runtime/Abstractions/WorkflowInstructionInterpreter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Abstractions/WorkflowInstructionInterpreter.cs @@ -1,27 +1,27 @@ -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Abstractions; - -public abstract class WorkflowInstructionInterpreter : IWorkflowInstructionInterpreter where TInstruction: IWorkflowInstruction -{ - bool IWorkflowInstructionInterpreter.GetSupportsInstruction(IWorkflowInstruction instruction) => instruction is TInstruction; - - ValueTask IWorkflowInstructionInterpreter.ExecuteInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken) => ExecuteInstructionAsync((TInstruction)instruction, cancellationToken); - ValueTask IWorkflowInstructionInterpreter.DispatchInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken) => DispatchInstructionAsync((TInstruction)instruction, cancellationToken); - - protected virtual ValueTask ExecuteInstructionAsync(TInstruction instruction, CancellationToken cancellationToken = default) - { - var result = ExecuteInstruction(instruction); - return ValueTask.FromResult(result); - } - - protected virtual ValueTask DispatchInstructionAsync(TInstruction instruction, CancellationToken cancellationToken = default) - { - var result = DispatchInstruction(instruction); - return ValueTask.FromResult(result); - } - - protected virtual ExecuteWorkflowInstructionResult? ExecuteInstruction(TInstruction instruction) => null; - protected virtual DispatchWorkflowInstructionResult? DispatchInstruction(TInstruction instruction) => null; -} \ No newline at end of file +// using Elsa.Workflows.Runtime.Models; +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Abstractions; +// +// public abstract class WorkflowInstructionInterpreter : IWorkflowInstructionInterpreter where TInstruction: IWorkflowInstruction +// { +// bool IWorkflowInstructionInterpreter.GetSupportsInstruction(IWorkflowInstruction instruction) => instruction is TInstruction; +// +// ValueTask IWorkflowInstructionInterpreter.ExecuteInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken) => ExecuteInstructionAsync((TInstruction)instruction, cancellationToken); +// ValueTask IWorkflowInstructionInterpreter.DispatchInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken) => DispatchInstructionAsync((TInstruction)instruction, cancellationToken); +// +// protected virtual ValueTask ExecuteInstructionAsync(TInstruction instruction, CancellationToken cancellationToken = default) +// { +// var result = ExecuteInstruction(instruction); +// return ValueTask.FromResult(result); +// } +// +// protected virtual ValueTask DispatchInstructionAsync(TInstruction instruction, CancellationToken cancellationToken = default) +// { +// var result = DispatchInstruction(instruction); +// return ValueTask.FromResult(result); +// } +// +// protected virtual ExecuteWorkflowInstructionResult? ExecuteInstruction(TInstruction instruction) => null; +// protected virtual DispatchWorkflowInstructionResult? DispatchInstruction(TInstruction instruction) => null; +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs b/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs index 000f985044..18c125bb8c 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs @@ -19,18 +19,18 @@ public class DispatchWorkflow : Activity protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { - var workflowDefinitionId = context.Get(WorkflowDefinitionId); - var logger = context.GetRequiredService>(); - - if (string.IsNullOrWhiteSpace(workflowDefinitionId)) - { - logger.LogWarning("No workflow definition ID specified"); - return; - } - - var workflowService = context.GetRequiredService(); - var input = context.Get(Input); - var correlationId = CorrelationId.Get(context); - await workflowService.DispatchWorkflowAsync(workflowDefinitionId, VersionOptions.Published, input, correlationId, context.CancellationToken); + // var workflowDefinitionId = context.Get(WorkflowDefinitionId); + // var logger = context.GetRequiredService>(); + // + // if (string.IsNullOrWhiteSpace(workflowDefinitionId)) + // { + // logger.LogWarning("No workflow definition ID specified"); + // return; + // } + // + // var workflowService = context.GetRequiredService(); + // var input = context.Get(Input); + // var correlationId = CorrelationId.Get(context); + // await workflowService.DispatchWorkflowAsync(workflowDefinitionId, VersionOptions.Published, input, correlationId, context.CancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs index bd82c7f41e..09550012e6 100644 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/DependencyInjectionExtensions.cs @@ -14,6 +14,6 @@ public static IModule UseRuntime(this IModule module, Action(this IServiceCollection services) where T : class, IWorkflowDefinitionProvider => services.AddSingleton(); - public static IServiceCollection AddStimulusHandler(this IServiceCollection services) where T : class, IStimulusHandler => services.AddSingleton(); - public static IServiceCollection AddInstructionInterpreter(this IServiceCollection services) where T : class, IWorkflowInstructionInterpreter => services.AddSingleton(); + // public static IServiceCollection AddStimulusHandler(this IServiceCollection services) where T : class, IStimulusHandler => services.AddSingleton(); + // public static IServiceCollection AddInstructionInterpreter(this IServiceCollection services) where T : class, IWorkflowInstructionInterpreter => services.AddSingleton(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs index 91f15f0a39..6fdcf07dab 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs @@ -7,11 +7,9 @@ using Elsa.Workflows.Runtime.Extensions; using Elsa.Workflows.Runtime.HostedServices; using Elsa.Workflows.Runtime.Implementations; -using Elsa.Workflows.Runtime.Interpreters; using Elsa.Workflows.Runtime.Models; using Elsa.Workflows.Runtime.Options; using Elsa.Workflows.Runtime.Services; -using Elsa.Workflows.Runtime.Stimuli.Handlers; using Elsa.Workflows.Runtime.WorkflowProviders; using Microsoft.Extensions.DependencyInjection; @@ -32,12 +30,12 @@ public WorkflowRuntimeFeature(IModule module) : base(module) /// /// A factory that instantiates a concrete . /// - public Func WorkflowInvokerFactory { get; set; } = sp => ActivatorUtilities.CreateInstance(sp); + public Func WorkflowRuntime { get; set; } = sp => ActivatorUtilities.CreateInstance(sp); /// /// A factory that instantiates a concrete . /// - public Func WorkflowDispatcherFactory { get; set; } = sp => ActivatorUtilities.CreateInstance(sp); + public Func WorkflowDispatcher { get; set; } = sp => ActivatorUtilities.CreateInstance(sp); public WorkflowRuntimeFeature AddWorkflow() where T : IWorkflow { @@ -57,29 +55,16 @@ public override void Apply() { Services // Core. - .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(WorkflowInvokerFactory) - .AddSingleton(WorkflowDispatcherFactory) - - // Stimulus handlers. - .AddStimulusHandler() - .AddStimulusHandler() - - // Instruction interpreters. - .AddInstructionInterpreter() - .AddInstructionInterpreter() - + .AddSingleton(WorkflowRuntime) + .AddSingleton(WorkflowDispatcher) + // Workflow definition providers. .AddWorkflowDefinitionProvider() - - // Workflow engine. - .AddSingleton() - + // Domain event handlers. .AddNotificationHandlersFrom(typeof(WorkflowRuntimeFeature)) diff --git a/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowDefinitionWorker.cs b/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowDefinitionWorker.cs index 63bd5954ef..13683c4e52 100644 --- a/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowDefinitionWorker.cs +++ b/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowDefinitionWorker.cs @@ -9,16 +9,16 @@ namespace Elsa.Workflows.Runtime.HostedServices; public class DispatchedWorkflowDefinitionWorker : BackgroundService { private readonly ChannelReader _channelReader; - private readonly IWorkflowInvoker _workflowInvoker; + private readonly IWorkflowRuntime _workflowRuntime; private readonly ILogger _logger; public DispatchedWorkflowDefinitionWorker( ChannelReader channelReader, - IWorkflowInvoker workflowInvoker, + IWorkflowRuntime workflowRuntime, ILogger logger) { _channelReader = channelReader; - _workflowInvoker = workflowInvoker; + _workflowRuntime = workflowRuntime; _logger = logger; } @@ -27,10 +27,11 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) await foreach (var (definitionId, versionOptions, input, correlationId) in _channelReader.ReadAllAsync(cancellationToken)) { var request = new InvokeWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId); + var options = new StartWorkflowOptions(correlationId, input, versionOptions); try { - await _workflowInvoker.InvokeAsync(request, cancellationToken); + await _workflowRuntime.StartWorkflowAsync(request.DefinitionId, options, cancellationToken); } catch (Exception e) { diff --git a/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowInstanceWorker.cs b/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowInstanceWorker.cs index 025336ed58..b13c9acdf2 100644 --- a/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowInstanceWorker.cs +++ b/src/modules/Elsa.Workflows.Runtime/HostedServices/DispatchedWorkflowInstanceWorker.cs @@ -9,28 +9,28 @@ namespace Elsa.Workflows.Runtime.HostedServices; public class DispatchedWorkflowInstanceWorker : BackgroundService { private readonly ChannelReader _channelReader; - private readonly IWorkflowInvoker _workflowInvoker; + private readonly IWorkflowRuntime _workflowRuntime; private readonly ILogger _logger; public DispatchedWorkflowInstanceWorker( ChannelReader channelReader, - IWorkflowInvoker workflowInvoker, + IWorkflowRuntime workflowRuntime, ILogger logger) { _channelReader = channelReader; - _workflowInvoker = workflowInvoker; + _workflowRuntime = workflowRuntime; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - await foreach (var (instanceId, bookmark, input, correlationId) in _channelReader.ReadAllAsync(cancellationToken)) + await foreach (var (instanceId, bookmarkId, input, correlationId) in _channelReader.ReadAllAsync(cancellationToken)) { - var request = new InvokeWorkflowInstanceRequest(instanceId, bookmark, input, correlationId); - + var options = new ResumeWorkflowOptions(input); + try { - await _workflowInvoker.InvokeAsync(request, cancellationToken); + await _workflowRuntime.ResumeWorkflowAsync(instanceId, bookmarkId, options, cancellationToken); } catch (Exception e) { diff --git a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs index 358e5e13c7..bad438f697 100644 --- a/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Runtime/HostedServices/PopulateWorkflowDefinitionStore.cs @@ -44,7 +44,7 @@ public async Task StartAsync(CancellationToken cancellationToken) private async Task AddOrUpdateAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { // Check if there's already a workflow definition by the definition ID and version. - var existingDefinition = (await _workflowDefinitionStore.FindByDefinitionIdAsync( + var existingDefinition = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync( definition.DefinitionId, VersionOptions.SpecificVersion(definition.Version), cancellationToken)).FirstOrDefault(); diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/BookmarkManager.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/BookmarkManager.cs index e6ab073d68..017149ac0b 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/BookmarkManager.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/BookmarkManager.cs @@ -17,18 +17,20 @@ public BookmarkManager(IWorkflowBookmarkStore bookmarkStore, IEventPublisher eve _eventPublisher = eventPublisher; } - public async Task DeleteBookmarksAsync(IEnumerable bookmarks, CancellationToken cancellationToken = default) + public async Task DeleteBookmarksAsync(IEnumerable workflowBookmarks, CancellationToken cancellationToken = default) { - var list = bookmarks.ToList(); + var list = workflowBookmarks.ToList(); var ids = list.Select(x => x.Id).ToList(); + var bookmarks = list.Select(x => x.ToBookmark()).ToList(); await _bookmarkStore.DeleteManyAsync(ids, cancellationToken); - await _eventPublisher.PublishAsync(new WorkflowBookmarksDeleted(list), cancellationToken); + await _eventPublisher.PublishAsync(new WorkflowBookmarksDeleted(bookmarks), cancellationToken); } - public async Task SaveBookmarksAsync(IEnumerable bookmarks, CancellationToken cancellationToken = default) + public async Task SaveBookmarksAsync(IEnumerable workflowBookmarks, CancellationToken cancellationToken = default) { - var list = bookmarks.ToList(); + var list = workflowBookmarks.ToList(); + var bookmarks = list.Select(x => x.ToBookmark()).ToList(); await _bookmarkStore.SaveManyAsync(list, cancellationToken); - await _eventPublisher.PublishAsync(new WorkflowBookmarksSaved(list), cancellationToken); + await _eventPublisher.PublishAsync(new WorkflowBookmarksSaved(bookmarks), cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs index 4aae8199e7..10218fae28 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowInvoker.cs @@ -1,83 +1,90 @@ -using Elsa.Models; -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Core.Services; -using Elsa.Workflows.Core.State; -using Elsa.Workflows.Persistence.Entities; -using Elsa.Workflows.Persistence.Services; -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Implementations; - -/// -/// A basic implementation that directly executes the specified workflow in local memory (as opposed elsewhere in some cluster). -/// -public class DefaultWorkflowInvoker : IWorkflowInvoker -{ - private readonly IWorkflowDefinitionStore _workflowDefinitionStore; - private readonly IWorkflowInstanceStore _workflowInstanceStore; - private readonly IWorkflowDefinitionService _workflowDefinitionService; - private readonly IWorkflowRunner _workflowRunner; - - public DefaultWorkflowInvoker( - IWorkflowDefinitionStore workflowDefinitionStore, - IWorkflowInstanceStore workflowInstanceStore, - IWorkflowDefinitionService workflowDefinitionService, - IWorkflowRunner workflowRunner) - { - _workflowInstanceStore = workflowInstanceStore; - _workflowDefinitionService = workflowDefinitionService; - _workflowRunner = workflowRunner; - _workflowDefinitionStore = workflowDefinitionStore; - } - - public async Task InvokeAsync(InvokeWorkflowDefinitionRequest request, CancellationToken cancellationToken = default) - { - var definition = await GetWorkflowDefinitionAsync(request.DefinitionId, request.VersionOptions, cancellationToken); - var input = request.Input; - var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); - return await _workflowRunner.RunAsync(workflow, input, cancellationToken); - } - - public async Task InvokeAsync(InvokeWorkflowInstanceRequest request, CancellationToken cancellationToken = default) - { - var workflowInstance = await _workflowInstanceStore.FindByIdAsync(request.InstanceId, cancellationToken); - - if (workflowInstance == null) - throw new Exception($"No workflow instance found with ID {request.InstanceId}"); - - return await InvokeAsync(workflowInstance!, request.Bookmark, request.Input, cancellationToken); - } - - public async Task InvokeAsync(WorkflowInstance workflowInstance, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) - { - var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowInstance.DefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); - - if (workflow == null) - throw new Exception($"Workflow instance references a workflow that does not exist"); - - var workflowState = workflowInstance.WorkflowState; - return await InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); - } - - public async Task InvokeAsync(WorkflowDefinition workflowDefinition, WorkflowState workflowState, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) - { - var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); - return await InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); - } - - public async Task InvokeAsync(Workflow workflow, WorkflowState workflowState, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) - { - return await _workflowRunner.RunAsync(workflow, workflowState, bookmark, input, cancellationToken); - } - - private async Task GetWorkflowDefinitionAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) - { - var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, versionOptions, cancellationToken)).FirstOrDefault(); - - if (definition == null) - throw new Exception($"Workflow instance references a workflow that does not exist"); - - return definition; - } -} \ No newline at end of file +// using Elsa.Models; +// using Elsa.Workflows.Core.Models; +// using Elsa.Workflows.Core.Services; +// using Elsa.Workflows.Core.State; +// using Elsa.Workflows.Persistence.Entities; +// using Elsa.Workflows.Persistence.Services; +// using Elsa.Workflows.Runtime.Models; +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Implementations; +// +// /// +// /// A basic implementation that directly executes the specified workflow in local memory (as opposed elsewhere in some cluster). +// /// +// public class DefaultWorkflowInvoker : IWorkflowInvoker +// { +// private readonly IWorkflowDefinitionStore _workflowDefinitionStore; +// private readonly IWorkflowInstanceStore _workflowInstanceStore; +// private readonly IWorkflowDefinitionService _workflowDefinitionService; +// private readonly IWorkflowRunner _workflowRunner; +// +// public DefaultWorkflowInvoker( +// IWorkflowDefinitionStore workflowDefinitionStore, +// IWorkflowInstanceStore workflowInstanceStore, +// IWorkflowDefinitionService workflowDefinitionService, +// IWorkflowRunner workflowRunner) +// { +// _workflowInstanceStore = workflowInstanceStore; +// _workflowDefinitionService = workflowDefinitionService; +// _workflowRunner = workflowRunner; +// _workflowDefinitionStore = workflowDefinitionStore; +// } +// +// public async Task InvokeAsync(InvokeWorkflowDefinitionRequest request, CancellationToken cancellationToken = default) +// { +// var definition = await GetWorkflowDefinitionAsync(request.DefinitionId, request.VersionOptions, cancellationToken); +// var input = request.Input; +// var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); +// var result = await _workflowRunner.RunAsync(workflow, input, cancellationToken); +// return new InvokeWorkflowResult(result.WorkflowState, result.Bookmarks); +// } +// +// public async Task InvokeAsync(InvokeWorkflowInstanceRequest request, CancellationToken cancellationToken = default) +// { +// var workflowInstance = await _workflowInstanceStore.FindByIdAsync(request.InstanceId, cancellationToken); +// +// if (workflowInstance == null) +// throw new Exception($"No workflow instance found with ID {request.InstanceId}"); +// +// return await InvokeAsync(workflowInstance!, request.Bookmark, request.Input, cancellationToken); +// } +// +// public async Task InvokeAsync(WorkflowInstance workflowInstance, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) +// { +// var workflow = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(workflowInstance.DefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); +// +// if (workflow == null) +// throw new Exception($"Workflow instance references a workflow that does not exist"); +// +// var workflowState = workflowInstance.WorkflowState; +// return await InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); +// } +// +// public async Task InvokeAsync(WorkflowDefinition workflowDefinition, WorkflowState workflowState, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) +// { +// var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(workflowDefinition, cancellationToken); +// return await InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); +// } +// +// public async Task InvokeAsync(Workflow workflow, WorkflowState workflowState, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default) +// { +// var result = await _workflowRunner.RunAsync(workflow, workflowState, bookmark, input, cancellationToken); +// return new InvokeWorkflowResult(result.WorkflowState, result.Bookmarks); +// } +// +// public Task StartAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) +// { +// throw new NotImplementedException(); +// } +// +// private async Task GetWorkflowDefinitionAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) +// { +// var definition = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(definitionId, versionOptions, cancellationToken)).FirstOrDefault(); +// +// if (definition == null) +// throw new Exception($"Workflow instance references a workflow that does not exist"); +// +// return definition; +// } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowRuntime.cs new file mode 100644 index 0000000000..bce835d972 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/DefaultWorkflowRuntime.cs @@ -0,0 +1,21 @@ +using Elsa.Workflows.Runtime.Services; + +namespace Elsa.Workflows.Runtime.Implementations; + +public class DefaultWorkflowRuntime : IWorkflowRuntime +{ + public async Task StartWorkflowAsync(string definitionId, StartWorkflowOptions options, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public async Task ResumeWorkflowAsync(string instanceId, string bookmarkId, ResumeWorkflowOptions options, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task TriggerWorkflowsAsync(object bookmarkPayload, TriggerWorkflowsOptions options, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/StimulusInterpreter.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/StimulusInterpreter.cs index 41393637e2..090a0e71cf 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/StimulusInterpreter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/StimulusInterpreter.cs @@ -1,20 +1,20 @@ -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Implementations; - -public class StimulusInterpreter : IStimulusInterpreter -{ - private readonly IEnumerable _workflowExecutionInstructionProviders; - - public StimulusInterpreter(IEnumerable workflowExecutionInstructionProviders) - { - _workflowExecutionInstructionProviders = workflowExecutionInstructionProviders; - } - - public async Task> GetExecutionInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken = default) - { - var providers = _workflowExecutionInstructionProviders.Where(x => x.GetSupportsStimulus(stimulus)).ToList(); - var tasks = providers.Select(x => x.GetInstructionsAsync(stimulus, cancellationToken).AsTask()); - return (await Task.WhenAll(tasks)).SelectMany(x => x); - } -} \ No newline at end of file +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Implementations; +// +// public class StimulusInterpreter : IStimulusInterpreter +// { +// private readonly IEnumerable _workflowExecutionInstructionProviders; +// +// public StimulusInterpreter(IEnumerable workflowExecutionInstructionProviders) +// { +// _workflowExecutionInstructionProviders = workflowExecutionInstructionProviders; +// } +// +// public async Task> GetExecutionInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken = default) +// { +// var providers = _workflowExecutionInstructionProviders.Where(x => x.GetSupportsStimulus(stimulus)).ToList(); +// var tasks = providers.Select(x => x.GetInstructionsAsync(stimulus, cancellationToken).AsTask()); +// return (await Task.WhenAll(tasks)).SelectMany(x => x); +// } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs index ae014c1993..35578a6c84 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowDefinitionService.cs @@ -1,19 +1,23 @@ +using Elsa.Models; using Elsa.Workflows.Management.Services; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Persistence.Entities; +using Elsa.Workflows.Persistence.Services; using Elsa.Workflows.Runtime.Services; namespace Elsa.Workflows.Runtime.Implementations; public class WorkflowDefinitionService : IWorkflowDefinitionService { + private readonly IWorkflowDefinitionStore _workflowDefinitionStore; private readonly IEnumerable _materializers; - public WorkflowDefinitionService(IEnumerable materializers) + public WorkflowDefinitionService(IWorkflowDefinitionStore workflowDefinitionStore, IEnumerable materializers) { + _workflowDefinitionStore = workflowDefinitionStore; _materializers = materializers; } - + public async Task MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { var provider = _materializers.FirstOrDefault(x => x.Name == definition.MaterializerName); @@ -23,4 +27,7 @@ public async Task MaterializeWorkflowAsync(WorkflowDefinition definiti return await provider.MaterializeAsync(definition, cancellationToken); } + + public async Task FindAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) => + await _workflowDefinitionStore.FindByDefinitionIdAsync(definitionId, versionOptions, cancellationToken); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs index 9b004d3cf3..84150023a1 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstanceFactory.cs @@ -33,7 +33,7 @@ public async Task CreateAsync(string workflowDefinitionId, str public async Task CreateAsync(string workflowDefinitionId, VersionOptions versionOptions, string? correlationId, CancellationToken cancellationToken = default) { - var workflow = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken)).FirstOrDefault()!; + var workflow = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken)).FirstOrDefault()!; return await CreateAsync(workflow, correlationId, cancellationToken); } diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstructionExecutor.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstructionExecutor.cs index a3e1e3a0eb..433313d7e8 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstructionExecutor.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowInstructionExecutor.cs @@ -1,42 +1,42 @@ -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Implementations; - -public class WorkflowInstructionExecutor : IWorkflowInstructionExecutor -{ - private readonly IEnumerable _workflowExecutionInstructionHandlers; - - public WorkflowInstructionExecutor(IEnumerable workflowExecutionInstructionHandlers) - { - _workflowExecutionInstructionHandlers = workflowExecutionInstructionHandlers; - } - - public async Task> ExecuteInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default) - { - var handlers = _workflowExecutionInstructionHandlers.Where(x => x.GetSupportsInstruction(instruction)).ToList(); - var tasks = handlers.Select(x => x.ExecuteInstructionAsync(instruction, cancellationToken).AsTask()); - var results = await Task.WhenAll(tasks); - return results.Where(x => x != null).Select(x => x!).ToList(); - } - - public async Task> ExecuteInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default) - { - var tasks = instructions.Select(x => ExecuteInstructionAsync(x, cancellationToken)); - return (await Task.WhenAll(tasks)).SelectMany(x => x); - } - - public async Task> DispatchInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default) - { - var handlers = _workflowExecutionInstructionHandlers.Where(x => x.GetSupportsInstruction(instruction)).ToList(); - var tasks = handlers.Select(x => x.DispatchInstructionAsync(instruction, cancellationToken).AsTask()); - var results = await Task.WhenAll(tasks); - return results.Where(x => x != null).Select(x => x!).ToList(); - } - - public async Task> DispatchInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default) - { - var tasks = instructions.Select(x => DispatchInstructionAsync(x, cancellationToken)); - return (await Task.WhenAll(tasks)).SelectMany(x => x); - } -} \ No newline at end of file +// using Elsa.Workflows.Runtime.Models; +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Implementations; +// +// public class WorkflowInstructionExecutor : IWorkflowInstructionExecutor +// { +// private readonly IEnumerable _workflowExecutionInstructionHandlers; +// +// public WorkflowInstructionExecutor(IEnumerable workflowExecutionInstructionHandlers) +// { +// _workflowExecutionInstructionHandlers = workflowExecutionInstructionHandlers; +// } +// +// public async Task> ExecuteInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default) +// { +// var handlers = _workflowExecutionInstructionHandlers.Where(x => x.GetSupportsInstruction(instruction)).ToList(); +// var tasks = handlers.Select(x => x.ExecuteInstructionAsync(instruction, cancellationToken).AsTask()); +// var results = await Task.WhenAll(tasks); +// return results.Where(x => x != null).Select(x => x!).ToList(); +// } +// +// public async Task> ExecuteInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default) +// { +// var tasks = instructions.Select(x => ExecuteInstructionAsync(x, cancellationToken)); +// return (await Task.WhenAll(tasks)).SelectMany(x => x); +// } +// +// public async Task> DispatchInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default) +// { +// var handlers = _workflowExecutionInstructionHandlers.Where(x => x.GetSupportsInstruction(instruction)).ToList(); +// var tasks = handlers.Select(x => x.DispatchInstructionAsync(instruction, cancellationToken).AsTask()); +// var results = await Task.WhenAll(tasks); +// return results.Where(x => x != null).Select(x => x!).ToList(); +// } +// +// public async Task> DispatchInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default) +// { +// var tasks = instructions.Select(x => DispatchInstructionAsync(x, cancellationToken)); +// return (await Task.WhenAll(tasks)).SelectMany(x => x); +// } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowService.cs b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowService.cs index 770c3c707b..127e668e21 100644 --- a/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowService.cs +++ b/src/modules/Elsa.Workflows.Runtime/Implementations/WorkflowService.cs @@ -1,96 +1,88 @@ -using Elsa.Models; -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Core.Services; -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Implementations; - -public class WorkflowService : IWorkflowService -{ - private readonly IWorkflowInvoker _workflowInvoker; - private readonly IWorkflowDispatcher _workflowDispatcher; - private readonly IWorkflowInstructionExecutor _workflowInstructionExecutor; - private readonly IStimulusInterpreter _stimulusInterpreter; - private readonly IHasher _hasher; - - public WorkflowService( - IWorkflowInvoker workflowInvoker, - IWorkflowDispatcher workflowDispatcher, - IWorkflowInstructionExecutor workflowInstructionExecutor, - IStimulusInterpreter stimulusInterpreter, - IHasher hasher) - { - _workflowInvoker = workflowInvoker; - _workflowDispatcher = workflowDispatcher; - _workflowInstructionExecutor = workflowInstructionExecutor; - _stimulusInterpreter = stimulusInterpreter; - _hasher = hasher; - } - - public async Task ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) - { - var executeRequest = new InvokeWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId); - return await _workflowInvoker.InvokeAsync(executeRequest, cancellationToken); - } - - public async Task ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) - { - var request = new InvokeWorkflowInstanceRequest(instanceId, bookmark, input, correlationId); - return await _workflowInvoker.InvokeAsync(request, cancellationToken); - } - - public async Task DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) - { - var executeRequest = new DispatchWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId); - return await _workflowDispatcher.DispatchAsync(executeRequest, cancellationToken); - } - - public async Task DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) - { - var request = new DispatchWorkflowInstanceRequest(instanceId, bookmark, input, correlationId); - return await _workflowDispatcher.DispatchAsync(request, cancellationToken); - } - - public async Task> ExecuteStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken = default) - { - // Collect instructions for the specified stimulus. - var instructions = await GetWorkflowInstructionsAsync(stimulus, cancellationToken); - - // Execute instructions. - return await _workflowInstructionExecutor.ExecuteInstructionsAsync(instructions, cancellationToken); - } - - public async Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, object? inputs = default, string? correlationId = default, CancellationToken cancellationToken = default) - { - var hash = _hasher.Hash(bookmarkPayload); - var stimulus = Stimulus.Standard(bookmarkName, hash, inputs, correlationId); - return await DispatchStimulusAsync(stimulus, cancellationToken); - } - - public async Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, IDictionary inputs, string? correlationId = default, CancellationToken cancellationToken = default) - { - var hash = _hasher.Hash(bookmarkPayload); - var stimulus = Stimulus.Standard(bookmarkName, hash, inputs, correlationId); - return await DispatchStimulusAsync(stimulus, cancellationToken); - } - - public async Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, string? correlationId = default, CancellationToken cancellationToken = default) - { - var hash = _hasher.Hash(bookmarkPayload); - var stimulus = Stimulus.Standard(bookmarkName, hash, default, correlationId); - return await DispatchStimulusAsync(stimulus, cancellationToken); - } - - public async Task> DispatchStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken) - { - // Collect instructions for the specified stimulus. - var instructions = await GetWorkflowInstructionsAsync(stimulus, cancellationToken); - - // Execute instructions. - return await _workflowInstructionExecutor.DispatchInstructionsAsync(instructions, cancellationToken); - } - - private async Task> GetWorkflowInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken = default) => - await _stimulusInterpreter.GetExecutionInstructionsAsync(stimulus, cancellationToken); -} \ No newline at end of file +// using Elsa.Models; +// using Elsa.Workflows.Core.Models; +// using Elsa.Workflows.Core.Services; +// using Elsa.Workflows.Persistence.Entities; +// using Elsa.Workflows.Runtime.Models; +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Implementations; +// +// public class WorkflowService : IWorkflowService +// { +// private readonly IWorkflowInvoker _workflowInvoker; +// private readonly IWorkflowDispatcher _workflowDispatcher; +// private readonly IHasher _hasher; +// +// public WorkflowService( +// IWorkflowInvoker workflowInvoker, +// IWorkflowDispatcher workflowDispatcher, +// IHasher hasher) +// { +// _workflowInvoker = workflowInvoker; +// _workflowDispatcher = workflowDispatcher; +// _hasher = hasher; +// } +// +// public async Task ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) +// { +// var executeRequest = new InvokeWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId); +// var result = await _workflowInvoker.InvokeAsync(executeRequest, cancellationToken); +// +// return new ExecuteWorkflowResult(result.WorkflowState, result.Bookmarks); +// } +// +// public async Task ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) +// { +// var request = new InvokeWorkflowInstanceRequest(instanceId, bookmark, input, correlationId); +// var result = await _workflowInvoker.InvokeAsync(request, cancellationToken); +// return new ExecuteWorkflowResult(result.WorkflowState, result.Bookmarks); +// } +// +// public async Task DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) +// { +// var executeRequest = new DispatchWorkflowDefinitionRequest(definitionId, versionOptions, input, correlationId); +// return await _workflowDispatcher.DispatchAsync(executeRequest, cancellationToken); +// } +// +// public async Task DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default) +// { +// var request = new DispatchWorkflowInstanceRequest(instanceId, bookmark, input, correlationId); +// return await _workflowDispatcher.DispatchAsync(request, cancellationToken); +// } +// +// protected override async ValueTask ResumeWorkflowAsync(WorkflowBookmark workflowBookmark, CancellationToken cancellationToken = default) +// { +// // var workflowDefinitionId = workflowBookmark.WorkflowDefinitionId; +// // var workflowInstanceId = workflowBookmark.WorkflowInstanceId; +// // var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); +// // +// // if (workflowInstance == null) +// // { +// // _logger +// // .LogWarning( +// // "Workflow bookmark {WorkflowBookmarkId} for workflow definition {WorkflowDefinitionId} references workflow instance ID {WorkflowInstanceId}, but no such workflow instance was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId, workflowBookmark.WorkflowInstanceId); +// // +// // return null; +// // } +// // +// // var definition = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); +// // +// // if (definition == null) +// // { +// // _logger.LogWarning("Workflow bookmark {WorkflowBookmarkId} references workflow definition ID {WorkflowDefinitionId}, but no such workflow definition was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId); +// // return null; +// // } +// // +// // // Resume workflow instance. +// // var bookmark = new Bookmark(workflowBookmark.Id, workflowBookmark.Name, workflowBookmark.Hash, workflowBookmark.Data, workflowBookmark.ActivityId, workflowBookmark.ActivityInstanceId, workflowBookmark.CallbackMethodName); +// // var workflowState = workflowInstance.WorkflowState; +// // var input = instruction.Input; +// // var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); +// // var workflowExecutionResult = await _workflowInvoker.InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); +// // +// // // Update workflow instance with new workflow state. +// // workflowInstance.WorkflowState = workflowExecutionResult.WorkflowState; +// // +// // return new ExecuteWorkflowInstructionResult(workflowExecutionResult); +// } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs b/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs index 3774994139..e85cb48e83 100644 --- a/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Interpreters/ResumeWorkflowInstructionInterpreter.cs @@ -1,112 +1,112 @@ -using Elsa.Models; -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Core.Services; -using Elsa.Workflows.Persistence.Entities; -using Elsa.Workflows.Persistence.Services; -using Elsa.Workflows.Runtime.Abstractions; -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; -using Microsoft.Extensions.Logging; - -namespace Elsa.Workflows.Runtime.Interpreters; - -public record ResumeWorkflowInstruction(WorkflowBookmark WorkflowBookmark, IDictionary? Input, string? CorrelationId) : IWorkflowInstruction; - -public class ResumeWorkflowInstructionInterpreter : WorkflowInstructionInterpreter -{ - private readonly IWorkflowRunner _workflowRunner; - private readonly IWorkflowInvoker _workflowInvoker; - private readonly IWorkflowDispatcher _workflowDispatcher; - private readonly IWorkflowDefinitionStore _workflowDefinitionStore; - private readonly IWorkflowInstanceStore _workflowInstanceStore; - private readonly IWorkflowDefinitionService _workflowDefinitionService; - private readonly ILogger _logger; - - public ResumeWorkflowInstructionInterpreter( - IWorkflowRunner workflowRunner, - IWorkflowInvoker workflowInvoker, - IWorkflowDispatcher workflowDispatcher, - IWorkflowDefinitionStore workflowDefinitionStore, - IWorkflowInstanceStore workflowInstanceStore, - IWorkflowDefinitionService workflowDefinitionService, - ILogger logger) - { - _workflowRunner = workflowRunner; - _workflowInvoker = workflowInvoker; - _workflowDispatcher = workflowDispatcher; - _workflowDefinitionStore = workflowDefinitionStore; - _workflowInstanceStore = workflowInstanceStore; - _workflowDefinitionService = workflowDefinitionService; - _logger = logger; - } - - protected override async ValueTask ExecuteInstructionAsync(ResumeWorkflowInstruction instruction, CancellationToken cancellationToken = default) - { - var workflowBookmark = instruction.WorkflowBookmark; - var workflowDefinitionId = workflowBookmark.WorkflowDefinitionId; - var workflowInstanceId = workflowBookmark.WorkflowInstanceId; - var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); - - if (workflowInstance == null) - { - _logger - .LogWarning( - "Workflow bookmark {WorkflowBookmarkId} for workflow definition {WorkflowDefinitionId} references workflow instance ID {WorkflowInstanceId}, but no such workflow instance was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId, workflowBookmark.WorkflowInstanceId); - - return null; - } - - var definition = (await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); - - if (definition == null) - { - _logger.LogWarning("Workflow bookmark {WorkflowBookmarkId} references workflow definition ID {WorkflowDefinitionId}, but no such workflow definition was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId); - return null; - } - - // Resume workflow instance. - var bookmark = new Bookmark(workflowBookmark.Id, workflowBookmark.Name, workflowBookmark.Hash, workflowBookmark.Data, workflowBookmark.ActivityId, workflowBookmark.ActivityInstanceId, workflowBookmark.CallbackMethodName); - var workflowState = workflowInstance.WorkflowState; - var input = instruction.Input; - var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); - var workflowExecutionResult = await _workflowInvoker.InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); - - // Update workflow instance with new workflow state. - workflowInstance.WorkflowState = workflowExecutionResult.WorkflowState; - - return new ExecuteWorkflowInstructionResult(workflowExecutionResult); - } - - protected override async ValueTask DispatchInstructionAsync(ResumeWorkflowInstruction instruction, CancellationToken cancellationToken = default) - { - var workflowBookmark = instruction.WorkflowBookmark; - var workflowDefinitionId = workflowBookmark.WorkflowDefinitionId; - var workflowInstanceId = workflowBookmark.WorkflowInstanceId; - var correlationId = instruction.CorrelationId; - var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); - - if (workflowInstance == null) - { - _logger - .LogWarning( - "Workflow bookmark {WorkflowBookmarkId} for workflow definition {WorkflowDefinitionId} references workflow instance ID {WorkflowInstanceId}, but no such workflow instance was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId, workflowBookmark.WorkflowInstanceId); - - return null; - } - - var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken); - - if (definition == null) - { - _logger.LogWarning("Workflow bookmark {WorkflowBookmarkId} references workflow definition ID {WorkflowDefinitionId}, but no such workflow definition was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId); - return null; - } - - // Resume workflow instance. - var bookmark = new Bookmark(workflowBookmark.Id, workflowBookmark.Name, workflowBookmark.Hash, workflowBookmark.Data, workflowBookmark.ActivityId, workflowBookmark.ActivityInstanceId, workflowBookmark.CallbackMethodName); - var input = instruction.Input; - await _workflowDispatcher.DispatchAsync(new DispatchWorkflowInstanceRequest(workflowInstanceId, bookmark, input, correlationId), cancellationToken); - - return new DispatchWorkflowInstructionResult(); - } -} \ No newline at end of file +// using Elsa.Models; +// using Elsa.Workflows.Core.Models; +// using Elsa.Workflows.Core.Services; +// using Elsa.Workflows.Persistence.Entities; +// using Elsa.Workflows.Persistence.Services; +// using Elsa.Workflows.Runtime.Abstractions; +// using Elsa.Workflows.Runtime.Models; +// using Elsa.Workflows.Runtime.Services; +// using Microsoft.Extensions.Logging; +// +// namespace Elsa.Workflows.Runtime.Interpreters; +// +// public record ResumeWorkflowInstruction(WorkflowBookmark WorkflowBookmark, IDictionary? Input, string? CorrelationId) : IWorkflowInstruction; +// +// public class ResumeWorkflowInstructionInterpreter : WorkflowInstructionInterpreter +// { +// private readonly IWorkflowRunner _workflowRunner; +// private readonly IWorkflowInvoker _workflowInvoker; +// private readonly IWorkflowDispatcher _workflowDispatcher; +// private readonly IWorkflowDefinitionStore _workflowDefinitionStore; +// private readonly IWorkflowInstanceStore _workflowInstanceStore; +// private readonly IWorkflowDefinitionService _workflowDefinitionService; +// private readonly ILogger _logger; +// +// public ResumeWorkflowInstructionInterpreter( +// IWorkflowRunner workflowRunner, +// IWorkflowInvoker workflowInvoker, +// IWorkflowDispatcher workflowDispatcher, +// IWorkflowDefinitionStore workflowDefinitionStore, +// IWorkflowInstanceStore workflowInstanceStore, +// IWorkflowDefinitionService workflowDefinitionService, +// ILogger logger) +// { +// _workflowRunner = workflowRunner; +// _workflowInvoker = workflowInvoker; +// _workflowDispatcher = workflowDispatcher; +// _workflowDefinitionStore = workflowDefinitionStore; +// _workflowInstanceStore = workflowInstanceStore; +// _workflowDefinitionService = workflowDefinitionService; +// _logger = logger; +// } +// +// protected override async ValueTask ExecuteInstructionAsync(ResumeWorkflowInstruction instruction, CancellationToken cancellationToken = default) +// { +// var workflowBookmark = instruction.WorkflowBookmark; +// var workflowDefinitionId = workflowBookmark.WorkflowDefinitionId; +// var workflowInstanceId = workflowBookmark.WorkflowInstanceId; +// var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); +// +// if (workflowInstance == null) +// { +// _logger +// .LogWarning( +// "Workflow bookmark {WorkflowBookmarkId} for workflow definition {WorkflowDefinitionId} references workflow instance ID {WorkflowInstanceId}, but no such workflow instance was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId, workflowBookmark.WorkflowInstanceId); +// +// return null; +// } +// +// var definition = (await _workflowDefinitionStore.FindManyByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken)).FirstOrDefault(); +// +// if (definition == null) +// { +// _logger.LogWarning("Workflow bookmark {WorkflowBookmarkId} references workflow definition ID {WorkflowDefinitionId}, but no such workflow definition was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId); +// return null; +// } +// +// // Resume workflow instance. +// var bookmark = new Bookmark(workflowBookmark.Id, workflowBookmark.Name, workflowBookmark.Hash, workflowBookmark.Data, workflowBookmark.ActivityId, workflowBookmark.ActivityInstanceId, workflowBookmark.CallbackMethodName); +// var workflowState = workflowInstance.WorkflowState; +// var input = instruction.Input; +// var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); +// var workflowExecutionResult = await _workflowInvoker.InvokeAsync(workflow, workflowState, bookmark, input, cancellationToken); +// +// // Update workflow instance with new workflow state. +// workflowInstance.WorkflowState = workflowExecutionResult.WorkflowState; +// +// return new ExecuteWorkflowInstructionResult(workflowExecutionResult); +// } +// +// protected override async ValueTask DispatchInstructionAsync(ResumeWorkflowInstruction instruction, CancellationToken cancellationToken = default) +// { +// var workflowBookmark = instruction.WorkflowBookmark; +// var workflowDefinitionId = workflowBookmark.WorkflowDefinitionId; +// var workflowInstanceId = workflowBookmark.WorkflowInstanceId; +// var correlationId = instruction.CorrelationId; +// var workflowInstance = await _workflowInstanceStore.FindByIdAsync(workflowInstanceId, cancellationToken); +// +// if (workflowInstance == null) +// { +// _logger +// .LogWarning( +// "Workflow bookmark {WorkflowBookmarkId} for workflow definition {WorkflowDefinitionId} references workflow instance ID {WorkflowInstanceId}, but no such workflow instance was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId, workflowBookmark.WorkflowInstanceId); +// +// return null; +// } +// +// var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, VersionOptions.SpecificVersion(workflowInstance.Version), cancellationToken); +// +// if (definition == null) +// { +// _logger.LogWarning("Workflow bookmark {WorkflowBookmarkId} references workflow definition ID {WorkflowDefinitionId}, but no such workflow definition was found", workflowBookmark.Id, workflowBookmark.WorkflowDefinitionId); +// return null; +// } +// +// // Resume workflow instance. +// var bookmark = new Bookmark(workflowBookmark.Id, workflowBookmark.Name, workflowBookmark.Hash, workflowBookmark.Data, workflowBookmark.ActivityId, workflowBookmark.ActivityInstanceId, workflowBookmark.CallbackMethodName); +// var input = instruction.Input; +// await _workflowDispatcher.DispatchAsync(new DispatchWorkflowInstanceRequest(workflowInstanceId, bookmark, input, correlationId), cancellationToken); +// +// return new DispatchWorkflowInstructionResult(); +// } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Interpreters/TriggerWorkflowInstructionInterpreter.cs b/src/modules/Elsa.Workflows.Runtime/Interpreters/TriggerWorkflowInstructionInterpreter.cs index 4fbd26e7fe..a159e85ee2 100644 --- a/src/modules/Elsa.Workflows.Runtime/Interpreters/TriggerWorkflowInstructionInterpreter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Interpreters/TriggerWorkflowInstructionInterpreter.cs @@ -1,69 +1,69 @@ -using Elsa.Models; -using Elsa.Workflows.Persistence.Entities; -using Elsa.Workflows.Persistence.Services; -using Elsa.Workflows.Runtime.Abstractions; -using Elsa.Workflows.Runtime.Models; -using Elsa.Workflows.Runtime.Services; -using Microsoft.Extensions.Logging; - -namespace Elsa.Workflows.Runtime.Interpreters; - -public record TriggerWorkflowInstruction(WorkflowTrigger WorkflowTrigger, IDictionary? Input, string? CorrelationId) : IWorkflowInstruction; - -public class TriggerWorkflowInstructionInterpreter : WorkflowInstructionInterpreter -{ - private readonly IWorkflowInvoker _workflowInvoker; - private readonly IWorkflowDispatcher _workflowDispatcher; - private readonly IWorkflowDefinitionStore _workflowDefinitionStore; - private readonly ILogger _logger; - - public TriggerWorkflowInstructionInterpreter( - IWorkflowInvoker workflowInvoker, - IWorkflowDispatcher workflowDispatcher, - IWorkflowDefinitionStore workflowDefinitionStore, - ILogger logger) - { - _workflowInvoker = workflowInvoker; - _workflowDispatcher = workflowDispatcher; - _workflowDefinitionStore = workflowDefinitionStore; - _logger = logger; - } - - protected override async ValueTask ExecuteInstructionAsync(TriggerWorkflowInstruction instruction, CancellationToken cancellationToken = default) - { - var workflowTrigger = instruction.WorkflowTrigger; - var definitionId = workflowTrigger.WorkflowDefinitionId; - - // Check if the workflow definition exists. - var exists = await GetDefinitionExistsAsync(definitionId, cancellationToken); - - if (!exists) - return null; - - // Execute workflow. - var executeRequest = new InvokeWorkflowDefinitionRequest(definitionId, VersionOptions.Published, instruction.Input, instruction.CorrelationId); - var workflowExecutionResult = await _workflowInvoker.InvokeAsync(executeRequest, cancellationToken); - - return new ExecuteWorkflowInstructionResult(workflowExecutionResult); - } - - protected override async ValueTask DispatchInstructionAsync(TriggerWorkflowInstruction instruction, CancellationToken cancellationToken = default) - { - var workflowTrigger = instruction.WorkflowTrigger; - var definitionId = workflowTrigger.WorkflowDefinitionId; - - // Check if the workflow definition exists. - var exists = await GetDefinitionExistsAsync(definitionId, cancellationToken); - - if (!exists) - return null; - - // Execute workflow. - var dispatchRequest = new DispatchWorkflowDefinitionRequest(definitionId, VersionOptions.Published, instruction.Input, instruction.CorrelationId); - await _workflowDispatcher.DispatchAsync(dispatchRequest, cancellationToken); - - return new DispatchWorkflowInstructionResult(); - } - - private async Task GetDefinitionExistsAsync(string definitionId, CancellationToken cancellationToken) => await _workflowDefinitionStore.GetExistsAsync(definitionId, VersionOptions.Published, cancellationToken); -} \ No newline at end of file +// using Elsa.Models; +// using Elsa.Workflows.Persistence.Entities; +// using Elsa.Workflows.Persistence.Services; +// using Elsa.Workflows.Runtime.Abstractions; +// using Elsa.Workflows.Runtime.Models; +// using Elsa.Workflows.Runtime.Services; +// using Microsoft.Extensions.Logging; +// +// namespace Elsa.Workflows.Runtime.Interpreters; +// +// public record TriggerWorkflowInstruction(WorkflowTrigger WorkflowTrigger, IDictionary? Input, string? CorrelationId) : IWorkflowInstruction; +// +// public class TriggerWorkflowInstructionInterpreter : WorkflowInstructionInterpreter +// { +// private readonly IWorkflowInvoker _workflowInvoker; +// private readonly IWorkflowDispatcher _workflowDispatcher; +// private readonly IWorkflowDefinitionStore _workflowDefinitionStore; +// private readonly ILogger _logger; +// +// public TriggerWorkflowInstructionInterpreter( +// IWorkflowInvoker workflowInvoker, +// IWorkflowDispatcher workflowDispatcher, +// IWorkflowDefinitionStore workflowDefinitionStore, +// ILogger logger) +// { +// _workflowInvoker = workflowInvoker; +// _workflowDispatcher = workflowDispatcher; +// _workflowDefinitionStore = workflowDefinitionStore; +// _logger = logger; +// } +// +// protected override async ValueTask ExecuteInstructionAsync(TriggerWorkflowInstruction instruction, CancellationToken cancellationToken = default) +// { +// var workflowTrigger = instruction.WorkflowTrigger; +// var definitionId = workflowTrigger.WorkflowDefinitionId; +// +// // Check if the workflow definition exists. +// var exists = await GetDefinitionExistsAsync(definitionId, cancellationToken); +// +// if (!exists) +// return null; +// +// // Execute workflow. +// var executeRequest = new InvokeWorkflowDefinitionRequest(definitionId, VersionOptions.Published, instruction.Input, instruction.CorrelationId); +// var workflowExecutionResult = await _workflowInvoker.InvokeAsync(executeRequest, cancellationToken); +// +// return new ExecuteWorkflowInstructionResult(workflowExecutionResult); +// } +// +// protected override async ValueTask DispatchInstructionAsync(TriggerWorkflowInstruction instruction, CancellationToken cancellationToken = default) +// { +// var workflowTrigger = instruction.WorkflowTrigger; +// var definitionId = workflowTrigger.WorkflowDefinitionId; +// +// // Check if the workflow definition exists. +// var exists = await GetDefinitionExistsAsync(definitionId, cancellationToken); +// +// if (!exists) +// return null; +// +// // Execute workflow. +// var dispatchRequest = new DispatchWorkflowDefinitionRequest(definitionId, VersionOptions.Published, instruction.Input, instruction.CorrelationId); +// await _workflowDispatcher.DispatchAsync(dispatchRequest, cancellationToken); +// +// return new DispatchWorkflowInstructionResult(); +// } +// +// private async Task GetDefinitionExistsAsync(string definitionId, CancellationToken cancellationToken) => await _workflowDefinitionStore.GetExistsAsync(definitionId, VersionOptions.Published, cancellationToken); +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowInstanceMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowInstanceMiddleware.cs index 4428f60d12..e916172f46 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowInstanceMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/PersistWorkflowInstanceMiddleware.cs @@ -163,12 +163,12 @@ public override async ValueTask InvokeAsync(WorkflowExecutionContext context) var diff = Diff.For(bookmarksSnapshot, context.Bookmarks.ToList()); // Delete removed bookmarks. - var removedBookmarks = diff.Removed.Select(x => WorkflowBookmark.FromBookmark(x, workflowInstance)).ToList(); - await _bookmarkManager.DeleteBookmarksAsync(removedBookmarks, cancellationToken); + var removedBookmarks = diff.Removed; + //await _bookmarkManager.DeleteBookmarksAsync(removedBookmarks, cancellationToken); // Persist created bookmarks. - var createdBookmarks = diff.Added.Select(x => WorkflowBookmark.FromBookmark(x, workflowInstance)).ToList(); - await _bookmarkManager.SaveBookmarksAsync(createdBookmarks, cancellationToken); + var createdBookmarks = diff.Added; + //await _bookmarkManager.SaveBookmarksAsync(createdBookmarks, cancellationToken); // Publish an event so that observers can update their workers. await _eventPublisher.PublishAsync(new WorkflowBookmarksIndexed(new IndexedWorkflowBookmarks(workflowState, createdBookmarks, removedBookmarks)), cancellationToken); diff --git a/src/modules/Elsa.Workflows.Runtime/Models/DispatchWorkflowInstanceRequest.cs b/src/modules/Elsa.Workflows.Runtime/Models/DispatchWorkflowInstanceRequest.cs index fac53d4f53..379d10e413 100644 --- a/src/modules/Elsa.Workflows.Runtime/Models/DispatchWorkflowInstanceRequest.cs +++ b/src/modules/Elsa.Workflows.Runtime/Models/DispatchWorkflowInstanceRequest.cs @@ -1,5 +1,3 @@ -using Elsa.Workflows.Core.Models; - namespace Elsa.Workflows.Runtime.Models; -public record DispatchWorkflowInstanceRequest(string InstanceId, Bookmark? Bookmark = default, IDictionary? Input = default, string? CorrelationId = default); \ No newline at end of file +public record DispatchWorkflowInstanceRequest(string InstanceId, string BookmarkId, IDictionary? Input = default, string? CorrelationId = default); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Models/ExecuteWorkflowResult.cs b/src/modules/Elsa.Workflows.Runtime/Models/ExecuteWorkflowResult.cs new file mode 100644 index 0000000000..9733f2db02 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Models/ExecuteWorkflowResult.cs @@ -0,0 +1,6 @@ +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Core.State; + +namespace Elsa.Workflows.Runtime.Models; + +public record ExecuteWorkflowResult(WorkflowState WorkflowState, ICollection Bookmarks); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Models/IndexedWorkflowBookmarks.cs b/src/modules/Elsa.Workflows.Runtime/Models/IndexedWorkflowBookmarks.cs index aca4b5d783..0b7f6d9a04 100644 --- a/src/modules/Elsa.Workflows.Runtime/Models/IndexedWorkflowBookmarks.cs +++ b/src/modules/Elsa.Workflows.Runtime/Models/IndexedWorkflowBookmarks.cs @@ -1,6 +1,6 @@ +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.State; -using Elsa.Workflows.Persistence.Entities; namespace Elsa.Workflows.Runtime.Models; -public record IndexedWorkflowBookmarks(WorkflowState WorkflowState, IReadOnlyCollection AddedBookmarks, IReadOnlyCollection RemovedBookmarks); \ No newline at end of file +public record IndexedWorkflowBookmarks(WorkflowState WorkflowState, ICollection AddedBookmarks, ICollection RemovedBookmarks); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Models/Stimulus.cs b/src/modules/Elsa.Workflows.Runtime/Models/Stimulus.cs index 6ab0cbf1b5..eef7c3961b 100644 --- a/src/modules/Elsa.Workflows.Runtime/Models/Stimulus.cs +++ b/src/modules/Elsa.Workflows.Runtime/Models/Stimulus.cs @@ -1,22 +1,22 @@ -using Elsa.Workflows.Core; -using Elsa.Workflows.Core.Helpers; -using Elsa.Workflows.Core.Services; -using Elsa.Workflows.Runtime.Stimuli; - -namespace Elsa.Workflows.Runtime.Models; - -public static class Stimulus -{ - public static StandardStimulus Standard(string? hash = default, IDictionary? input = default, string? correlationId = default) where T : IActivity => - new(ActivityTypeNameHelper.GenerateTypeName(), hash, input, correlationId); - - public static StandardStimulus Standard(string? hash, object input, string? correlationId = default) where T : IActivity => - new(ActivityTypeNameHelper.GenerateTypeName(), hash, input.ToDictionary(), correlationId); - - public static StandardStimulus Standard(object input, string? correlationId = default) where T : IActivity => - new(ActivityTypeNameHelper.GenerateTypeName(), default, input.ToDictionary(), correlationId); - - public static StandardStimulus Standard(string activityTypeName, string? hash = default, IDictionary? input = default, string? correlationId = default) => new(activityTypeName, hash, input, correlationId); - public static StandardStimulus Standard(string activityTypeName, string? hash, object? input, string? correlationId = default) => new(activityTypeName, hash, input?.ToDictionary(), correlationId); - public static StandardStimulus Standard(string activityTypeName, object? input, string? correlationId = default) => new(activityTypeName, default, input?.ToDictionary(), correlationId); -} \ No newline at end of file +// using Elsa.Workflows.Core; +// using Elsa.Workflows.Core.Helpers; +// using Elsa.Workflows.Core.Services; +// using Elsa.Workflows.Runtime.Stimuli; +// +// namespace Elsa.Workflows.Runtime.Models; +// +// public static class Stimulus +// { +// public static StandardStimulus Standard(string? hash = default, IDictionary? input = default, string? correlationId = default) where T : IActivity => +// new(ActivityTypeNameHelper.GenerateTypeName(), hash, input, correlationId); +// +// public static StandardStimulus Standard(string? hash, object input, string? correlationId = default) where T : IActivity => +// new(ActivityTypeNameHelper.GenerateTypeName(), hash, input.ToDictionary(), correlationId); +// +// public static StandardStimulus Standard(object input, string? correlationId = default) where T : IActivity => +// new(ActivityTypeNameHelper.GenerateTypeName(), default, input.ToDictionary(), correlationId); +// +// public static StandardStimulus Standard(string activityTypeName, string? hash = default, IDictionary? input = default, string? correlationId = default) => new(activityTypeName, hash, input, correlationId); +// public static StandardStimulus Standard(string activityTypeName, string? hash, object? input, string? correlationId = default) => new(activityTypeName, hash, input?.ToDictionary(), correlationId); +// public static StandardStimulus Standard(string activityTypeName, object? input, string? correlationId = default) => new(activityTypeName, default, input?.ToDictionary(), correlationId); +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksDeleted.cs b/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksDeleted.cs index c1ad5b89a5..e66af76403 100644 --- a/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksDeleted.cs +++ b/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksDeleted.cs @@ -1,6 +1,7 @@ using Elsa.Mediator.Services; +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Persistence.Entities; namespace Elsa.Workflows.Runtime.Notifications; -public record WorkflowBookmarksDeleted(List Bookmarks) : INotification; \ No newline at end of file +public record WorkflowBookmarksDeleted(List Bookmarks) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksSaved.cs b/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksSaved.cs index b3c21efe5d..74a6660b00 100644 --- a/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksSaved.cs +++ b/src/modules/Elsa.Workflows.Runtime/Notifications/WorkflowBookmarksSaved.cs @@ -1,6 +1,7 @@ using Elsa.Mediator.Services; +using Elsa.Workflows.Core.Models; using Elsa.Workflows.Persistence.Entities; namespace Elsa.Workflows.Runtime.Notifications; -public record WorkflowBookmarksSaved(List Bookmarks) : INotification; \ No newline at end of file +public record WorkflowBookmarksSaved(List Bookmarks) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IBookmarkManager.cs b/src/modules/Elsa.Workflows.Runtime/Services/IBookmarkManager.cs index 6267aa61dd..1f84e4c48e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IBookmarkManager.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IBookmarkManager.cs @@ -7,6 +7,6 @@ namespace Elsa.Workflows.Runtime.Services; /// public interface IBookmarkManager { - Task DeleteBookmarksAsync(IEnumerable bookmarks, CancellationToken cancellationToken = default); - Task SaveBookmarksAsync(IEnumerable bookmarks, CancellationToken cancellationToken = default); + Task DeleteBookmarksAsync(IEnumerable workflowBookmarks, CancellationToken cancellationToken = default); + Task SaveBookmarksAsync(IEnumerable workflowBookmarks, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IStimulus.cs b/src/modules/Elsa.Workflows.Runtime/Services/IStimulus.cs index aee44f3f13..c0204c0441 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IStimulus.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IStimulus.cs @@ -1,5 +1,5 @@ -namespace Elsa.Workflows.Runtime.Services; - -public interface IStimulus -{ -} \ No newline at end of file +// namespace Elsa.Workflows.Runtime.Services; +// +// public interface IStimulus +// { +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IStimulusHandler.cs b/src/modules/Elsa.Workflows.Runtime/Services/IStimulusHandler.cs index 1d3c61a206..0d5d1909e0 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IStimulusHandler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IStimulusHandler.cs @@ -1,7 +1,7 @@ -namespace Elsa.Workflows.Runtime.Services; - -public interface IStimulusHandler -{ - bool GetSupportsStimulus(IStimulus stimulus); - ValueTask> GetInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken = default); -} \ No newline at end of file +// namespace Elsa.Workflows.Runtime.Services; +// +// public interface IStimulusHandler +// { +// bool GetSupportsStimulus(IStimulus stimulus); +// ValueTask> GetInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken = default); +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IStimulusInterpreter.cs b/src/modules/Elsa.Workflows.Runtime/Services/IStimulusInterpreter.cs index 6ac9ac3fc2..c7cf640388 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IStimulusInterpreter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IStimulusInterpreter.cs @@ -1,6 +1,6 @@ -namespace Elsa.Workflows.Runtime.Services; - -public interface IStimulusInterpreter -{ - Task> GetExecutionInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken = default); -} \ No newline at end of file +// namespace Elsa.Workflows.Runtime.Services; +// +// public interface IStimulusInterpreter +// { +// Task> GetExecutionInstructionsAsync(IStimulus stimulus, CancellationToken cancellationToken = default); +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs index 186b0eb97c..f90cf8c0dc 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowDefinitionService.cs @@ -1,3 +1,4 @@ +using Elsa.Models; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Persistence.Entities; @@ -9,4 +10,5 @@ namespace Elsa.Workflows.Runtime.Services; public interface IWorkflowDefinitionService { Task MaterializeWorkflowAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default); + Task FindAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInstructionExecutor.cs b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInstructionExecutor.cs index f90192d4f3..f3c8b54b59 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInstructionExecutor.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInstructionExecutor.cs @@ -1,11 +1,11 @@ -using Elsa.Workflows.Runtime.Models; - -namespace Elsa.Workflows.Runtime.Services; - -public interface IWorkflowInstructionExecutor -{ - Task> ExecuteInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default); - Task> ExecuteInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default); - Task> DispatchInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default); - Task> DispatchInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default); -} \ No newline at end of file +// using Elsa.Workflows.Runtime.Models; +// +// namespace Elsa.Workflows.Runtime.Services; +// +// public interface IWorkflowInstructionExecutor +// { +// Task> ExecuteInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default); +// Task> ExecuteInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default); +// Task> DispatchInstructionAsync(IWorkflowInstruction instruction, CancellationToken cancellationToken = default); +// Task> DispatchInstructionsAsync(IEnumerable instructions, CancellationToken cancellationToken = default); +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInvoker.cs b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInvoker.cs deleted file mode 100644 index 04e9db9b30..0000000000 --- a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowInvoker.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Core.State; -using Elsa.Workflows.Persistence.Entities; -using Elsa.Workflows.Runtime.Models; - -namespace Elsa.Workflows.Runtime.Services; - -/// -/// Invokes a workflow using a configured runtime (e.g. ProtoActor). -/// -public interface IWorkflowInvoker -{ - /// - /// Creates a workflow instance of the specified workflow definition and then invokes the workflow instance. - /// - Task InvokeAsync(InvokeWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); - - /// - /// Invokes the specified workflow instance. - /// - Task InvokeAsync(InvokeWorkflowInstanceRequest request, CancellationToken cancellationToken = default); - - /// - /// Invokes the specified workflow instance. - /// - Task InvokeAsync(WorkflowInstance workflowInstance, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default); - - /// - /// Invokes the specified workflow definition. - /// - Task InvokeAsync(WorkflowDefinition workflowDefinition, WorkflowState workflowState, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default); - - /// - /// Invokes the specified workflow. - /// - Task InvokeAsync(Workflow workflow, WorkflowState workflowState, Bookmark? bookmark = default, IDictionary? input = default, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowRuntime.cs new file mode 100644 index 0000000000..c0e52ae5b7 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowRuntime.cs @@ -0,0 +1,19 @@ +using Elsa.Models; +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Runtime.Models; + +namespace Elsa.Workflows.Runtime.Services; + +public interface IWorkflowRuntime +{ + Task StartWorkflowAsync(string definitionId, StartWorkflowOptions options, CancellationToken cancellationToken = default); + Task ResumeWorkflowAsync(string instanceId, string bookmarkId, ResumeWorkflowOptions options, CancellationToken cancellationToken = default); + Task TriggerWorkflowsAsync(object bookmarkPayload, TriggerWorkflowsOptions options, CancellationToken cancellationToken = default); +} + +public record StartWorkflowOptions(string? CorrelationId = default, IDictionary? Input = default, VersionOptions VersionOptions = default); +public record ResumeWorkflowOptions(IDictionary? Input = default); +public record StartWorkflowResult(string InstanceId); +public record ResumeWorkflowResult; +public record TriggerWorkflowsOptions(IDictionary? Input = default); +public record TriggerWorkflowsResult; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowService.cs b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowService.cs index ec8ccbf1c6..1898f486d0 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowService.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/IWorkflowService.cs @@ -1,22 +1,22 @@ -using Elsa.Models; -using Elsa.Workflows.Core.Models; -using Elsa.Workflows.Runtime.Models; - -namespace Elsa.Workflows.Runtime.Services; - -/// -/// Represents a high-level service to invoke workflows. -/// -public interface IWorkflowService -{ - Task ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); - Task ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); - Task DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); - Task DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); - Task> ExecuteStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken = default); - Task> DispatchStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken = default); - Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, object inputs, string? correlationId = default, CancellationToken cancellationToken = default); - Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, IDictionary inputs, string? correlationId = default, CancellationToken cancellationToken = default); - Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, string? correlationId = default, CancellationToken cancellationToken = default); - -} \ No newline at end of file +// using Elsa.Models; +// using Elsa.Workflows.Core.Models; +// using Elsa.Workflows.Runtime.Models; +// +// namespace Elsa.Workflows.Runtime.Services; +// +// /// +// /// Represents a high-level service to invoke workflows. +// /// +// public interface IWorkflowService +// { +// Task ExecuteWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); +// Task ExecuteWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); +// Task DispatchWorkflowAsync(string definitionId, VersionOptions versionOptions, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); +// Task DispatchWorkflowAsync(string instanceId, Bookmark bookmark, IDictionary? input = default, string? correlationId = default, CancellationToken cancellationToken = default); +// // Task> ExecuteStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken = default); +// // Task> DispatchStimulusAsync(IStimulus stimulus, CancellationToken cancellationToken = default); +// // Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, object inputs, string? correlationId = default, CancellationToken cancellationToken = default); +// // Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, IDictionary inputs, string? correlationId = default, CancellationToken cancellationToken = default); +// // Task> DispatchStimulusAsync(string bookmarkName, object bookmarkPayload, string? correlationId = default, CancellationToken cancellationToken = default); +// +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/ResumeWorkflowsStimulusHandler.cs b/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/ResumeWorkflowsStimulusHandler.cs index f9cc1cbdd9..6886337ee7 100644 --- a/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/ResumeWorkflowsStimulusHandler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/ResumeWorkflowsStimulusHandler.cs @@ -1,19 +1,19 @@ -using Elsa.Workflows.Persistence.Services; -using Elsa.Workflows.Runtime.Abstractions; -using Elsa.Workflows.Runtime.Interpreters; -using Elsa.Workflows.Runtime.Services; -using Open.Linq.AsyncExtensions; - -namespace Elsa.Workflows.Runtime.Stimuli.Handlers; - -public class ResumeWorkflowsStimulusHandler : StimulusHandler -{ - private readonly IWorkflowBookmarkStore _bookmarkStore; - public ResumeWorkflowsStimulusHandler(IWorkflowBookmarkStore bookmarkStore) => _bookmarkStore = bookmarkStore; - - protected override async ValueTask> GetInstructionsAsync(StandardStimulus stimulus, CancellationToken cancellationToken = default) - { - var workflowBookmarks = await _bookmarkStore.FindManyAsync(stimulus.ActivityTypeName, stimulus.Hash, cancellationToken).ToList(); - return workflowBookmarks.Select(x => new ResumeWorkflowInstruction(x, stimulus.Input, stimulus.CorrelationId)); - } -} \ No newline at end of file +// using Elsa.Workflows.Persistence.Services; +// using Elsa.Workflows.Runtime.Abstractions; +// using Elsa.Workflows.Runtime.Interpreters; +// using Elsa.Workflows.Runtime.Services; +// using Open.Linq.AsyncExtensions; +// +// namespace Elsa.Workflows.Runtime.Stimuli.Handlers; +// +// public class ResumeWorkflowsStimulusHandler : StimulusHandler +// { +// private readonly IWorkflowBookmarkStore _bookmarkStore; +// public ResumeWorkflowsStimulusHandler(IWorkflowBookmarkStore bookmarkStore) => _bookmarkStore = bookmarkStore; +// +// protected override async ValueTask> GetInstructionsAsync(StandardStimulus stimulus, CancellationToken cancellationToken = default) +// { +// var workflowBookmarks = await _bookmarkStore.FindManyAsync(stimulus.ActivityTypeName, stimulus.Hash, cancellationToken).ToList(); +// return workflowBookmarks.Select(x => new ResumeWorkflowInstruction(x, stimulus.Input, stimulus.CorrelationId)); +// } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/TriggerWorkflowsStimulusHandler.cs b/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/TriggerWorkflowsStimulusHandler.cs index dea865e1fe..1b4fe7073a 100644 --- a/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/TriggerWorkflowsStimulusHandler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Stimuli/Handlers/TriggerWorkflowsStimulusHandler.cs @@ -1,22 +1,22 @@ -using Elsa.Workflows.Persistence.Services; -using Elsa.Workflows.Runtime.Abstractions; -using Elsa.Workflows.Runtime.Interpreters; -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Stimuli.Handlers; - -public class TriggerWorkflowsStimulusHandler : StimulusHandler -{ - private readonly IWorkflowTriggerStore _workflowTriggerStore; - - public TriggerWorkflowsStimulusHandler(IWorkflowTriggerStore workflowTriggerStore) - { - _workflowTriggerStore = workflowTriggerStore; - } - - protected override async ValueTask> GetInstructionsAsync(StandardStimulus stimulus, CancellationToken cancellationToken = default) - { - var workflowTriggers = (await _workflowTriggerStore.FindManyByNameAsync(stimulus.ActivityTypeName, stimulus.Hash, cancellationToken)).ToList(); - return workflowTriggers.Select(x => new TriggerWorkflowInstruction(x, stimulus.Input, stimulus.CorrelationId)); - } -} \ No newline at end of file +// using Elsa.Workflows.Persistence.Services; +// using Elsa.Workflows.Runtime.Abstractions; +// using Elsa.Workflows.Runtime.Interpreters; +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Stimuli.Handlers; +// +// public class TriggerWorkflowsStimulusHandler : StimulusHandler +// { +// private readonly IWorkflowTriggerStore _workflowTriggerStore; +// +// public TriggerWorkflowsStimulusHandler(IWorkflowTriggerStore workflowTriggerStore) +// { +// _workflowTriggerStore = workflowTriggerStore; +// } +// +// protected override async ValueTask> GetInstructionsAsync(StandardStimulus stimulus, CancellationToken cancellationToken = default) +// { +// var workflowTriggers = (await _workflowTriggerStore.FindManyByNameAsync(stimulus.ActivityTypeName, stimulus.Hash, cancellationToken)).ToList(); +// return workflowTriggers.Select(x => new TriggerWorkflowInstruction(x, stimulus.Input, stimulus.CorrelationId)); +// } +// } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Stimuli/StandardStimulus.cs b/src/modules/Elsa.Workflows.Runtime/Stimuli/StandardStimulus.cs index 1936122fab..a08e9b0d78 100644 --- a/src/modules/Elsa.Workflows.Runtime/Stimuli/StandardStimulus.cs +++ b/src/modules/Elsa.Workflows.Runtime/Stimuli/StandardStimulus.cs @@ -1,8 +1,8 @@ -using Elsa.Workflows.Runtime.Services; - -namespace Elsa.Workflows.Runtime.Stimuli; - -/// -/// Represents a simple stimulus that targets a specific activity type and optionally a corresponding hash to use as a bookmark lookup. -/// -public record StandardStimulus(string ActivityTypeName, string? Hash = default, IDictionary? Input = default, string? CorrelationId = default) : IStimulus; \ No newline at end of file +// using Elsa.Workflows.Runtime.Services; +// +// namespace Elsa.Workflows.Runtime.Stimuli; +// +// /// +// /// Represents a simple stimulus that targets a specific activity type and optionally a corresponding hash to use as a bookmark lookup. +// /// +// public record StandardStimulus(string ActivityTypeName, string? Hash = default, IDictionary? Input = default, string? CorrelationId = default) : IStimulus; \ No newline at end of file diff --git a/src/samples/aspnet/Elsa.Samples.Web2/Program.cs b/src/samples/aspnet/Elsa.Samples.Web2/Program.cs index 139d1495bc..6fd8cafd9a 100644 --- a/src/samples/aspnet/Elsa.Samples.Web2/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.Web2/Program.cs @@ -23,7 +23,10 @@ services .AddElsa(elsa => elsa .UseWorkflows() - .UseRuntime(runtime => runtime.UseProtoActor(f=> f.WithLocalhostProvider(opt => opt.Name = "my-cluster"))) + .UseRuntime(runtime => runtime.UseProtoActor(f=> + { + f.ClusterName = "my-cluster"; + })) .UseManagement(management => management .AddActivity() .AddActivity() diff --git a/test/Elsa.IntegrationTests/WorkflowInvoker/Tests.cs b/test/Elsa.IntegrationTests/WorkflowInvoker/Tests.cs deleted file mode 100644 index d55add933c..0000000000 --- a/test/Elsa.IntegrationTests/WorkflowInvoker/Tests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Threading.Tasks; -using Elsa.Extensions; -using Elsa.Persistence.Common.Implementations; -using Elsa.Testing.Shared; -using Elsa.Workflows.Core.Pipelines.WorkflowExecution.Components; -using Elsa.Workflows.Persistence.Entities; -using Elsa.Workflows.Runtime.Extensions; -using Elsa.Workflows.Runtime.Services; -using Microsoft.Extensions.DependencyInjection; -using Xunit; -using Xunit.Abstractions; - -namespace Elsa.IntegrationTests.WorkflowInvoker; - -public class Tests -{ - private readonly IWorkflowInvoker _workflowInvoker; - private readonly CapturingTextWriter _capturingTextWriter = new(); - private readonly MemoryStore _workflowInstanceStore; - private readonly MemoryStore _workflowBookmarkStore; - - public Tests(ITestOutputHelper testOutputHelper) - { - var services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build(); - _workflowInvoker = services.GetRequiredService(); - _workflowInstanceStore = services.GetRequiredService>(); - _workflowBookmarkStore = services.GetRequiredService>(); - - services.ConfigureDefaultWorkflowExecutionPipeline(pipeline => pipeline - .UsePersistence() - .UseStackBasedActivityScheduler()); - } - - [Fact(DisplayName = "Invoker creates workflow instance")] - public Task Test1() - { - //var workflow = new WorkflowDefinitionBuilder().BuildWorkflowAsync(); - return Task.CompletedTask; - //var requ - //var result = await _workflowInvoker.InvokeAsync(new InvokeWorkflowDefinitionRequest()); - //var workflowInstance = _workflowInstanceStore.Find(x => x.Id == result.WorkflowState.Id); - - //Assert.NotNull(workflowInstance); - } -} \ No newline at end of file