Add refresh/persist workflow context options to activity

This commit is contained in:
Sipke Schoorstra 2020-11-03 22:03:08 +01:00
parent 50b0921e98
commit 1bb2953efa
13 changed files with 88 additions and 40 deletions

View file

@ -16,12 +16,16 @@ namespace Elsa.Builders
public string? Description { get; set; }
IDictionary<string, IActivityPropertyValueProvider>? PropertyValueProviders { get; }
bool PersistWorkflow { get; set; }
bool LoadWorkflowContext { get; set; }
bool SaveWorkflowContext { get; set; }
IActivityBuilder Add<T>(Action<ISetupActivity<T>>? 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<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> BuildActivityAsync();
}
}

View file

@ -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();
}
}

View file

@ -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<bool> CanExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnCanExecuteAsync(context, cancellationToken);
public ValueTask<IActivityExecutionResult> ExecuteAsync(ActivityExecutionContext context, CancellationToken cancellationToken) => OnExecuteAsync(context, cancellationToken);

View file

@ -38,6 +38,16 @@ namespace Elsa.Services
/// </summary>
bool PersistWorkflow { get; set; }
/// <summary>
/// A value indicating whether the workflow context (if any) will be refreshed automatically before executing this activity.
/// </summary>
bool LoadWorkflowContext { get; set; }
/// <summary>
/// A value indicating whether the workflow context (if any) will be persisted automatically after executing this activity.
/// </summary>
bool SaveWorkflowContext { get; set; }
/// <summary>
/// A data store for the activity to store information that needs to be persisted as part of the workflow instance.
/// </summary>

View file

@ -15,12 +15,16 @@ namespace Elsa.Services.Models
string? name,
string type,
bool persistWorkflow,
bool loadWorkflowContext,
bool saveWorkflowContext,
Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> 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<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> CreateActivityAsync { get; set; } = default!;
}

View file

@ -19,7 +19,9 @@ namespace Elsa.Services.Models
string? name,
string type,
bool persistWorkflow,
Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> createActivity) : base(id, name, type, persistWorkflow, createActivity)
bool loadWorkflowContext,
bool saveWorkflowContext,
Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> createActivity) : base(id, name, type, persistWorkflow, loadWorkflowContext, saveWorkflowContext, createActivity)
{
}

View file

@ -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<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> CreateActivityAsync { get; }
}
}

View file

@ -27,7 +27,7 @@ namespace Elsa.Services.Models
bool deleteCompletedInstances,
IEnumerable<IActivityBlueprint> activities,
IEnumerable<IConnection> connections,
IActivityPropertyProviders activityPropertyValueProviders) : base(id, name, id, true, null!)
IActivityPropertyProviders activityPropertyValueProviders) : base(id, name, id, true, false, false, null!)
{
Id = id;
Version = version;

View file

@ -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<string, IActivityPropertyValueProvider>? PropertyValueProviders { get; protected set; }
public IActivityBuilder Add<T>(
@ -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<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> BuildActivityAsync() =>
async (context, cancellationToken) =>

View file

@ -14,7 +14,7 @@ namespace Elsa.Builders
private readonly Func<ICompositeActivityBuilder> _workflowBuilderFactory;
public CompositeActivityBuilder(
IIdGenerator idGenerator,
IIdGenerator idGenerator,
IActivityActivator activityActivator,
IServiceProvider serviceProvider)
{
@ -23,7 +23,7 @@ namespace Elsa.Builders
ActivityId = idGenerator.Generate();
ActivityBuilders = new List<IActivityBuilder>();
ConnectionBuilders = new List<IConnectionBuilder>();
_workflowBuilderFactory = () =>
{
var builder = serviceProvider.GetRequiredService<ICompositeActivityBuilder>();
@ -101,7 +101,7 @@ namespace Elsa.Builders
ActivityBuilders.Add(activityBuilder);
return activityBuilder;
}
public override IActivityBuilder Then<T>(Action<ISetupActivity<T>>? setup = null, Action<IActivityBuilder>? 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<IActivityBlueprint>();
var connections = new List<IConnection>();
var activityPropertyProviders = new Dictionary<string, IDictionary<string, IActivityPropertyValueProvider>>();
// 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<IActivityBuilder> compositeActivityBuilders,
ICollection<IActivityBlueprint> 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());
}
}
}

View file

@ -48,6 +48,13 @@ namespace Elsa.Services
setup?.Invoke(activity);
return activity;
}
public T ActivateActivity<T>(Action<T>? setup = null) where T : class, IActivity
{
var activity = ActivatorUtilities.GetServiceOrCreateInstance<T>(_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<T>(Action<T>? setup = null) where T : class, IActivity
{
var activity = ActivatorUtilities.GetServiceOrCreateInstance<T>(_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;
}

View file

@ -98,13 +98,14 @@ namespace Elsa.Services
private static async ValueTask<IActivity> 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;
}

View file

@ -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<object?> LoadWorkflowContextAsync(IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance, WorkflowContextFidelity fidelity, CancellationToken cancellationToken)
private async ValueTask<object?> 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<string?> SaveWorkflowContextAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowContextFidelity fidelity, CancellationToken cancellationToken)
private async ValueTask<string?> 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();