From 1bb2953efa4720db9dcff14fb33e012f25ea3d82 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 3 Nov 2020 22:03:08 +0100 Subject: [PATCH] Add refresh/persist workflow context options to activity --- .../Builders/IActivityBuilder.cs | 4 ++ .../Models/ActivityDefinition.cs | 2 + .../Elsa.Abstractions/Services/Activity.cs | 2 + .../Elsa.Abstractions/Services/IActivity.cs | 10 +++++ .../Services/Models/ActivityBlueprint.cs | 6 +++ .../Models/CompositeActivityBlueprint.cs | 4 +- .../Services/Models/IActivityBlueprint.cs | 2 + .../Services/Models/WorkflowBlueprint.cs | 2 +- .../Elsa.Core/Builders/ActivityBuilder.cs | 14 +++++++ .../Builders/CompositeActivityBuilder.cs | 38 +++++++++---------- .../Elsa.Core/Services/ActivityActivator.cs | 18 +++++---- .../Services/WorkflowBlueprintMaterializer.cs | 5 ++- src/core/Elsa.Core/Services/WorkflowRunner.cs | 21 +++++----- 13 files changed, 88 insertions(+), 40 deletions(-) diff --git a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs index 3aa059a18..0d32ead08 100644 --- a/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs +++ b/src/core/Elsa.Abstractions/Builders/IActivityBuilder.cs @@ -16,12 +16,16 @@ namespace Elsa.Builders public string? Description { get; set; } IDictionary? PropertyValueProviders { get; } bool PersistWorkflow { get; set; } + bool LoadWorkflowContext { get; set; } + bool SaveWorkflowContext { get; set; } IActivityBuilder Add(Action>? setup = default) where T : class, IActivity; IOutcomeBuilder When(string outcome); IActivityBuilder Then(IActivityBuilder targetActivity); IConnectionBuilder Then(string activityName); IActivityBuilder WithId(string? id); IActivityBuilder WithName(string? name); + IActivityBuilder WithLoadWorkflowContext(bool value = true); + IActivityBuilder WithSaveWorkflowContext(bool value = true); Func> BuildActivityAsync(); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Models/ActivityDefinition.cs b/src/core/Elsa.Abstractions/Models/ActivityDefinition.cs index 3ec42e323..45726df8b 100644 --- a/src/core/Elsa.Abstractions/Models/ActivityDefinition.cs +++ b/src/core/Elsa.Abstractions/Models/ActivityDefinition.cs @@ -10,6 +10,8 @@ namespace Elsa.Models public int? Left { get; set; } public int? Top { get; set; } public bool PersistWorkflow { get; set; } + public bool LoadWorkflowContext { get; set; } + public bool SaveWorkflowContext { get; set; } public ActivityDefinitionProperties Properties { get; set; } = new ActivityDefinitionProperties(); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Activity.cs b/src/core/Elsa.Abstractions/Services/Activity.cs index db3c86363..c126ef8cc 100644 --- a/src/core/Elsa.Abstractions/Services/Activity.cs +++ b/src/core/Elsa.Abstractions/Services/Activity.cs @@ -19,6 +19,8 @@ namespace Elsa.Services public string? DisplayName { get; set; } public string? Description { get; set; } public bool PersistWorkflow { get; set; } + public bool LoadWorkflowContext { get; set; } + public bool SaveWorkflowContext { get; set; } public JObject Data { get; set; } = new JObject(); public ValueTask CanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnCanExecuteAsync(context, cancellationToken); public ValueTask ExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnExecuteAsync(context, cancellationToken); diff --git a/src/core/Elsa.Abstractions/Services/IActivity.cs b/src/core/Elsa.Abstractions/Services/IActivity.cs index 3eb111a84..6a2787c83 100644 --- a/src/core/Elsa.Abstractions/Services/IActivity.cs +++ b/src/core/Elsa.Abstractions/Services/IActivity.cs @@ -38,6 +38,16 @@ namespace Elsa.Services /// bool PersistWorkflow { get; set; } + /// + /// A value indicating whether the workflow context (if any) will be refreshed automatically before executing this activity. + /// + bool LoadWorkflowContext { get; set; } + + /// + /// A value indicating whether the workflow context (if any) will be persisted automatically after executing this activity. + /// + bool SaveWorkflowContext { get; set; } + /// /// A data store for the activity to store information that needs to be persisted as part of the workflow instance. /// diff --git a/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs index b704d1d51..d216f928c 100644 --- a/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/ActivityBlueprint.cs @@ -15,12 +15,16 @@ namespace Elsa.Services.Models string? name, string type, bool persistWorkflow, + bool loadWorkflowContext, + bool saveWorkflowContext, Func> createActivity) { Id = id; Name = name; Type = type; PersistWorkflow = persistWorkflow; + LoadWorkflowContext = loadWorkflowContext; + SaveWorkflowContext = saveWorkflowContext; CreateActivityAsync = createActivity; } @@ -28,6 +32,8 @@ namespace Elsa.Services.Models public string? Name { get; set; } public string Type { get; set; } = default!; public bool PersistWorkflow { get; set; } + public bool LoadWorkflowContext { get; set; } + public bool SaveWorkflowContext { get; set; } public Func> CreateActivityAsync { get; set; } = default!; } diff --git a/src/core/Elsa.Abstractions/Services/Models/CompositeActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/CompositeActivityBlueprint.cs index df04411ca..7a5231ab0 100644 --- a/src/core/Elsa.Abstractions/Services/Models/CompositeActivityBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/CompositeActivityBlueprint.cs @@ -19,7 +19,9 @@ namespace Elsa.Services.Models string? name, string type, bool persistWorkflow, - Func> createActivity) : base(id, name, type, persistWorkflow, createActivity) + bool loadWorkflowContext, + bool saveWorkflowContext, + Func> createActivity) : base(id, name, type, persistWorkflow, loadWorkflowContext, saveWorkflowContext, createActivity) { } diff --git a/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs index 6336ff7d5..951205718 100644 --- a/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/IActivityBlueprint.cs @@ -10,6 +10,8 @@ namespace Elsa.Services.Models string? Name { get; } public string Type { get; } public bool PersistWorkflow { get; } + bool LoadWorkflowContext { get; set; } + bool SaveWorkflowContext { get; set; } Func> CreateActivityAsync { get; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs b/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs index 9123d33f6..9e0360c08 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs +++ b/src/core/Elsa.Abstractions/Services/Models/WorkflowBlueprint.cs @@ -27,7 +27,7 @@ namespace Elsa.Services.Models bool deleteCompletedInstances, IEnumerable activities, IEnumerable connections, - IActivityPropertyProviders activityPropertyValueProviders) : base(id, name, id, true, null!) + IActivityPropertyProviders activityPropertyValueProviders) : base(id, name, id, true, false, false, null!) { Id = id; Version = version; diff --git a/src/core/Elsa.Core/Builders/ActivityBuilder.cs b/src/core/Elsa.Core/Builders/ActivityBuilder.cs index e83c6fe91..e0e96076c 100644 --- a/src/core/Elsa.Core/Builders/ActivityBuilder.cs +++ b/src/core/Elsa.Core/Builders/ActivityBuilder.cs @@ -30,6 +30,8 @@ namespace Elsa.Builders public string? Name { get; set; } public string? Description { get; set; } public bool PersistWorkflow { get; set; } + public bool LoadWorkflowContext { get; set; } + public bool SaveWorkflowContext { get; set; } public IDictionary? PropertyValueProviders { get; protected set; } public IActivityBuilder Add( @@ -71,6 +73,18 @@ namespace Elsa.Builders Name = name; return this; } + + public IActivityBuilder WithLoadWorkflowContext(bool value) + { + LoadWorkflowContext = value; + return this; + } + + public IActivityBuilder WithSaveWorkflowContext(bool value) + { + SaveWorkflowContext = value; + return this; + } public Func> BuildActivityAsync() => async (context, cancellationToken) => diff --git a/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs b/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs index 90e65b5a9..ed8a7026c 100644 --- a/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs +++ b/src/core/Elsa.Core/Builders/CompositeActivityBuilder.cs @@ -14,7 +14,7 @@ namespace Elsa.Builders private readonly Func _workflowBuilderFactory; public CompositeActivityBuilder( - IIdGenerator idGenerator, + IIdGenerator idGenerator, IActivityActivator activityActivator, IServiceProvider serviceProvider) { @@ -23,7 +23,7 @@ namespace Elsa.Builders ActivityId = idGenerator.Generate(); ActivityBuilders = new List(); ConnectionBuilders = new List(); - + _workflowBuilderFactory = () => { var builder = serviceProvider.GetRequiredService(); @@ -101,7 +101,7 @@ namespace Elsa.Builders ActivityBuilders.Add(activityBuilder); return activityBuilder; } - + public override IActivityBuilder Then(Action>? setup = null, Action? branch = null) => StartWith(setup, branch); public override IActivityBuilder Then(IActivityBuilder targetActivity) => Add(targetActivity); @@ -124,30 +124,30 @@ namespace Elsa.Builders ConnectionBuilders.Add(connectionBuilder); return connectionBuilder; } - + public ICompositeActivityBlueprint Build(string activityIdPrefix = "activity") { var activityBuilders = ActivityBuilders.ToList(); var activityBlueprints = new List(); var connections = new List(); var activityPropertyProviders = new Dictionary>(); - + // Assign automatic ids to activity builders var index = 0; - + foreach (var activityBuilder in activityBuilders.Where(x => string.IsNullOrWhiteSpace(x.ActivityId))) activityBuilder.ActivityId = $"{activityIdPrefix}-{++index}"; - + activityBlueprints.AddRange(activityBuilders.Select(BuildActivityBlueprint)); - + // Build composite activities. var compositeActivityBuilders = activityBuilders.Where(x => typeof(CompositeActivity).IsAssignableFrom(x.ActivityType)); BuildCompositeActivities(compositeActivityBuilders, activityBlueprints, connections, activityPropertyProviders); - + var activityBlueprintDictionary = activityBlueprints.ToDictionary(x => x.Id); - + connections.AddRange(ConnectionBuilders.Select(x => new Connection(activityBlueprintDictionary[x.Source().ActivityId], activityBlueprintDictionary[x.Target().ActivityId], x.Outcome))); - + activityPropertyProviders.AddRange( activityBuilders .Select(x => (x.ActivityId, x.PropertyValueProviders)) @@ -161,7 +161,7 @@ namespace Elsa.Builders ActivityPropertyProviders = new ActivityPropertyProviders(activityPropertyProviders) }; } - + private void BuildCompositeActivities( IEnumerable compositeActivityBuilders, ICollection activityBlueprints, @@ -172,29 +172,29 @@ namespace Elsa.Builders { var compositeActivity = (CompositeActivity)_activityActivator.ActivateActivity(activityBuilder.ActivityType.Name); var workflowBuilder = _workflowBuilderFactory(); - + compositeActivity.Build(workflowBuilder); - + var workflow = workflowBuilder.Build($"{activityBuilder.ActivityId}:activity"); var activityDictionary = workflow.Activities.ToDictionary(x => x.Id); - + activityBlueprints.AddRange(workflow.Activities); connections.AddRange(workflow.Connections.Select(x => new Connection(activityDictionary[x.Source.Activity.Id], activityDictionary[x.Target.Activity.Id], x.Source.Outcome))); activityPropertyProviders.AddRange(workflow.ActivityPropertyProviders); - + var compositeActivityBlueprint = (ICompositeActivityBlueprint)activityBlueprints.Single(x => x.Id == activityBuilder.ActivityId); compositeActivityBlueprint.Activities = workflow.Activities; compositeActivityBlueprint.Connections = workflow.Connections; compositeActivityBlueprint.ActivityPropertyProviders = workflow.ActivityPropertyProviders; } } - + private IActivityBlueprint BuildActivityBlueprint(IActivityBuilder builder, int index) { var isComposite = typeof(CompositeActivity).IsAssignableFrom(builder.ActivityType); return isComposite - ? new CompositeActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.BuildActivityAsync()) - : new ActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.BuildActivityAsync()); + ? new CompositeActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.LoadWorkflowContext, builder.SaveWorkflowContext, builder.BuildActivityAsync()) + : new ActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.LoadWorkflowContext, builder.SaveWorkflowContext, builder.BuildActivityAsync()); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/ActivityActivator.cs b/src/core/Elsa.Core/Services/ActivityActivator.cs index f50e7e352..586844d96 100644 --- a/src/core/Elsa.Core/Services/ActivityActivator.cs +++ b/src/core/Elsa.Core/Services/ActivityActivator.cs @@ -48,6 +48,13 @@ namespace Elsa.Services setup?.Invoke(activity); return activity; } + + public T ActivateActivity(Action? setup = null) where T : class, IActivity + { + var activity = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider); + setup?.Invoke(activity); + return activity; + } public IActivity ActivateActivity(IActivityBlueprint activityBlueprint) { @@ -58,16 +65,11 @@ namespace Elsa.Services activity.Id = activityBlueprint.Id; activity.Name = activityBlueprint.Name; activity.PersistWorkflow = activityBlueprint.PersistWorkflow; + activity.SaveWorkflowContext = activityBlueprint.SaveWorkflowContext; + activity.LoadWorkflowContext = activityBlueprint.LoadWorkflowContext; }); } - public T ActivateActivity(Action? setup = null) where T : class, IActivity - { - var activity = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider); - setup?.Invoke(activity); - return activity; - } - public IActivity ActivateActivity(ActivityDefinition activityDefinition) { var activity = ActivateActivity(activityDefinition.Type); @@ -76,6 +78,8 @@ namespace Elsa.Services activity.Name = activityDefinition.Name; activity.DisplayName = activityDefinition.DisplayName; activity.PersistWorkflow = activityDefinition.PersistWorkflow; + activity.LoadWorkflowContext = activityDefinition.LoadWorkflowContext; + activity.SaveWorkflowContext = activityDefinition.SaveWorkflowContext; return activity; } diff --git a/src/core/Elsa.Core/Services/WorkflowBlueprintMaterializer.cs b/src/core/Elsa.Core/Services/WorkflowBlueprintMaterializer.cs index d0e941592..d81584042 100644 --- a/src/core/Elsa.Core/Services/WorkflowBlueprintMaterializer.cs +++ b/src/core/Elsa.Core/Services/WorkflowBlueprintMaterializer.cs @@ -98,13 +98,14 @@ namespace Elsa.Services private static async ValueTask CreateActivityAsync(ActivityDefinition activityDefinition, ActivityExecutionContext context, CancellationToken cancellationToken) { - var activity = context.ActivateActivity(); + var activity = await context.ActivateActivityAsync(cancellationToken); activity.Description = activityDefinition.Description; activity.Id = activityDefinition.ActivityId; activity.Name = activityDefinition.Name; activity.DisplayName = activityDefinition.DisplayName; activity.PersistWorkflow = activityDefinition.PersistWorkflow; - await context.SetActivityPropertiesAsync(activity, cancellationToken); + activity.LoadWorkflowContext = activityDefinition.LoadWorkflowContext; + activity.SaveWorkflowContext = activityDefinition.SaveWorkflowContext; return activity; } diff --git a/src/core/Elsa.Core/Services/WorkflowRunner.cs b/src/core/Elsa.Core/Services/WorkflowRunner.cs index 1f08ef004..4fa33430e 100644 --- a/src/core/Elsa.Core/Services/WorkflowRunner.cs +++ b/src/core/Elsa.Core/Services/WorkflowRunner.cs @@ -128,7 +128,7 @@ namespace Elsa.Services object? input = default, CancellationToken cancellationToken = default) { - var workflowContext = await LoadWorkflowContextAsync(workflowBlueprint, workflowInstance, WorkflowContextFidelity.Burst, cancellationToken); + var workflowContext = await LoadWorkflowContextAsync(workflowBlueprint, workflowInstance, WorkflowContextFidelity.Burst, false, cancellationToken); var workflowExecutionContext = CreateWorkflowExecutionContext(workflowBlueprint, workflowInstance, input, workflowContext, _serviceProvider); var activity = activityId != null ? workflowBlueprint.GetActivity(activityId) : default; @@ -147,7 +147,7 @@ namespace Elsa.Services break; } - workflowInstance.ContextId = await SaveWorkflowContextAsync(workflowExecutionContext, WorkflowContextFidelity.Burst, cancellationToken); + workflowInstance.ContextId = await SaveWorkflowContextAsync(workflowExecutionContext, WorkflowContextFidelity.Burst, false, cancellationToken); await _mediator.Publish(new WorkflowExecuted(workflowExecutionContext), cancellationToken); var statusEvent = workflowExecutionContext.Status switch @@ -165,20 +165,20 @@ namespace Elsa.Services return workflowExecutionContext.WorkflowInstance; } - private async ValueTask LoadWorkflowContextAsync(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, WorkflowContextFidelity fidelity, CancellationToken cancellationToken) + private async ValueTask LoadWorkflowContextAsync(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, WorkflowContextFidelity fidelity, bool always, CancellationToken cancellationToken) { - if (workflowInstance.ContextId == null || workflowBlueprint.ContextOptions == null || workflowBlueprint.ContextOptions.ContextFidelity != fidelity) + if (!always && (workflowInstance.ContextId == null || workflowBlueprint.ContextOptions == null || workflowBlueprint.ContextOptions.ContextFidelity != fidelity)) return null; var context = new LoadWorkflowContext(workflowBlueprint, workflowInstance); return await _workflowContextManager.LoadContext(context, cancellationToken); } - private async ValueTask SaveWorkflowContextAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowContextFidelity fidelity, CancellationToken cancellationToken) + private async ValueTask SaveWorkflowContextAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowContextFidelity fidelity, bool always, CancellationToken cancellationToken) { var workflowContext = workflowExecutionContext.WorkflowContext; - if (workflowContext == null || workflowExecutionContext.WorkflowBlueprint.ContextOptions?.ContextFidelity != fidelity) + if (!always && (workflowContext == null || workflowExecutionContext.WorkflowBlueprint.ContextOptions?.ContextFidelity != fidelity)) return workflowExecutionContext.WorkflowInstance.ContextId; var context = new SaveWorkflowContext(workflowExecutionContext); @@ -233,19 +233,20 @@ namespace Elsa.Services while (workflowExecutionContext.HasScheduledActivities) { - if(workflowBlueprint.ContextOptions?.ContextFidelity == WorkflowContextFidelity.Activity) - workflowExecutionContext.WorkflowContext = await LoadWorkflowContextAsync(workflowBlueprint, workflowInstance, WorkflowContextFidelity.Activity, cancellationToken); - var scheduledActivity = workflowExecutionContext.PopScheduledActivity(); var currentActivityId = scheduledActivity.ActivityId; var activityBlueprint = workflowBlueprint.GetActivity(currentActivityId)!; + + if(workflowBlueprint.ContextOptions?.ContextFidelity == WorkflowContextFidelity.Activity || activityBlueprint.LoadWorkflowContext) + workflowExecutionContext.WorkflowContext = await LoadWorkflowContextAsync(workflowBlueprint, workflowInstance, WorkflowContextFidelity.Activity, activityBlueprint.LoadWorkflowContext, cancellationToken); + var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, serviceProvider, activityBlueprint, scheduledActivity.Input); var activity = await activityBlueprint.CreateActivityAsync(activityExecutionContext, cancellationToken); var result = await activityOperation(activityExecutionContext, activity, cancellationToken); await _mediator.Publish(new ActivityExecuting(activityExecutionContext), cancellationToken); await result.ExecuteAsync(activityExecutionContext, cancellationToken); await _mediator.Publish(new ActivityExecuted(activityExecutionContext), cancellationToken); - workflowExecutionContext.WorkflowInstance.ContextId = await SaveWorkflowContextAsync(workflowExecutionContext, WorkflowContextFidelity.Activity, cancellationToken); + workflowExecutionContext.WorkflowInstance.ContextId = await SaveWorkflowContextAsync(workflowExecutionContext, WorkflowContextFidelity.Activity, activityBlueprint.SaveWorkflowContext, cancellationToken); activityOperation = Execute; workflowExecutionContext.CompletePass();