WIP: extract composite activity builder from workflow builder

This commit is contained in:
Sipke Schoorstra 2020-11-01 22:26:19 +01:00
parent 594853d45c
commit 90c8577864
23 changed files with 385 additions and 327 deletions

View file

@ -9,7 +9,7 @@ namespace Elsa.Builders
{
public interface IActivityBuilder : IBuilder
{
IWorkflowBuilder WorkflowBuilder { get; }
ICompositeActivityBuilder WorkflowBuilder { get; }
public Type ActivityType { get; }
string ActivityId { get; set; }
public string? Name { get; set; }
@ -23,6 +23,6 @@ namespace Elsa.Builders
IActivityBuilder WithId(string? id);
IActivityBuilder WithName(string? name);
Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> BuildActivityAsync();
IWorkflowBlueprint Build();
//IWorkflowBlueprint Build();
}
}

View file

@ -0,0 +1,11 @@
namespace Elsa.Builders
{
public interface ICompositeActivity<in TBuilder> where TBuilder: ICompositeActivityBuilder
{
void Build(TBuilder compositeActivity);
}
public interface ICompositeActivity : ICompositeActivity<ICompositeActivityBuilder>
{
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Builders
{
public interface ICompositeActivityBuilder : IActivityBuilder
{
IServiceProvider ServiceProvider { get; }
IReadOnlyCollection<IActivityBuilder> Activities { get; }
IActivityBuilder New<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity;
IActivityBuilder New<T>(
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default)
where T : class, IActivity;
IActivityBuilder Add<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity;
IActivityBuilder Add<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity;
IConnectionBuilder Connect(
IActivityBuilder source,
IActivityBuilder target,
string outcome = OutcomeNames.Done);
IConnectionBuilder Connect(
Func<IActivityBuilder> source,
Func<IActivityBuilder> target,
string outcome = OutcomeNames.Done);
ICompositeActivityBlueprint Build(string activityIdPrefix = "activity");
}
}

View file

@ -4,7 +4,7 @@ namespace Elsa.Builders
{
public interface IConnectionBuilder
{
IWorkflowBuilder WorkflowBuilder { get; }
ICompositeActivityBuilder WorkflowBuilder { get; }
Func<IActivityBuilder> Source { get; }
Func<IActivityBuilder> Target{ get; }
string Outcome { get; }

View file

@ -4,7 +4,7 @@ namespace Elsa.Builders
{
public interface IOutcomeBuilder : IBuilder
{
IWorkflowBuilder WorkflowBuilder { get; }
ICompositeActivityBuilder WorkflowBuilder { get; }
IActivityBuilder Source { get; }
string? Outcome { get; }
IConnectionBuilder Then(string activityName);

View file

@ -1,7 +1,6 @@
namespace Elsa.Builders
{
public interface IWorkflow
public interface IWorkflow : ICompositeActivity<IWorkflowBuilder>
{
void Build(IWorkflowBuilder workflow);
}
}

View file

@ -1,24 +1,15 @@
using System;
using System.Collections.Generic;
using Elsa.Models;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Builders
{
public interface IWorkflowBuilder : IBuilder
public interface IWorkflowBuilder : ICompositeActivityBuilder
{
string Id { get; }
string Name { get; }
string Description { get; }
int Version { get; }
WorkflowPersistenceBehavior PersistenceBehavior { get; }
bool DeleteCompletedInstances { get; }
bool IsEnabled { get; }
IServiceProvider ServiceProvider { get; }
IReadOnlyCollection<IActivityBuilder> Activities { get; }
IWorkflowBuilder WithId(string value);
IWorkflowBuilder WithName(string value);
IWorkflowBuilder WithDescription(string value);
IWorkflowBuilder WithVersion(int value);
IWorkflowBuilder AsSingleton();
@ -29,42 +20,8 @@ namespace Elsa.Builders
IWorkflowBuilder WithPersistenceBehavior(WorkflowPersistenceBehavior value);
IWorkflowBuilder Enable(bool value);
IActivityBuilder New<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity;
IActivityBuilder New<T>(
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(
Action<ISetupActivity<T>>? setup,
Action<IActivityBuilder>? branch = default) where T : class, IActivity;
IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default)
where T : class, IActivity;
IActivityBuilder Add<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity;
IActivityBuilder Add<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity;
IConnectionBuilder Connect(
IActivityBuilder source,
IActivityBuilder target,
string outcome = OutcomeNames.Done);
IConnectionBuilder Connect(
Func<IActivityBuilder> source,
Func<IActivityBuilder> target,
string outcome = OutcomeNames.Done);
IWorkflowBlueprint Build(string activityIdPrefix = "activity");
IWorkflowBlueprint BuildBlueprint(string activityIdPrefix = "activity");
// IWorkflowBlueprint Build(string activityIdPrefix = "activity");
IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix = "activity");
IWorkflowBlueprint Build(Type workflowType, string activityIdPrefix = "activity");
IWorkflowBlueprint Build<T>(string activityIdPrefix = "activity") where T : IWorkflow;

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Elsa.Models;
@ -7,36 +7,9 @@ using Elsa.Services.Models;
namespace Elsa
{
public static class WorkflowExtensions
public static class CompositeActivityBlueprintExtensions
{
public static IEnumerable<IWorkflowBlueprint> WithVersion(
this IEnumerable<IWorkflowBlueprint> query,
VersionOptions version) =>
query.AsQueryable().WithVersion(version);
public static IQueryable<IWorkflowBlueprint> WithVersion(
this IQueryable<IWorkflowBlueprint> query,
VersionOptions version)
{
if (version.IsDraft)
query = query.Where(x => !x.IsPublished);
else if (version.IsLatest)
query = query.Where(x => x.IsLatest);
else if (version.IsPublished)
query = query.Where(x => x.IsPublished);
else if (version.IsLatestOrPublished)
query = query.Where(x => x.IsPublished || x.IsLatest);
else if (version.AllVersions)
{
// Nothing to filter.
}
else if (version.Version > 0)
query = query.Where(x => x.Version == version.Version);
return query.OrderByDescending(x => x.Version);
}
public static IEnumerable<IActivityBlueprint> GetStartActivities(this IWorkflowBlueprint workflowBlueprint)
public static IEnumerable<IActivityBlueprint> GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint)
{
var targetActivityIds = workflowBlueprint.Connections.Select(x => x.Target.Activity.Id).Distinct()
.ToLookup(x => x);
@ -49,22 +22,22 @@ namespace Elsa
return query;
}
public static IEnumerable<IActivityBlueprint> GetStartActivities(this IWorkflowBlueprint workflowBlueprint, string activityType) => workflowBlueprint.GetStartActivities().Where(x => x.Type == activityType);
public static IEnumerable<IActivityBlueprint> GetStartActivities(this IWorkflowBlueprint workflowBlueprint, Type activityType) => workflowBlueprint.GetStartActivities(activityType.Name);
public static IEnumerable<IActivityBlueprint> GetStartActivities<T>(this IWorkflowBlueprint workflowBlueprint) where T : IActivity => workflowBlueprint.GetStartActivities(typeof(T));
public static IActivityBlueprint? GetActivity(this IWorkflowBlueprint workflowBlueprint, string id) => workflowBlueprint.Activities.FirstOrDefault(x => x.Id == id);
public static IEnumerable<IActivityBlueprint> GetActivities(this IWorkflowBlueprint workflowBlueprint, IEnumerable<string> ids) => workflowBlueprint.Activities.Where(x => ids.Contains(x.Id));
public static IEnumerable<IActivityBlueprint> GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint, string activityType) => workflowBlueprint.GetStartActivities().Where(x => x.Type == activityType);
public static IEnumerable<IActivityBlueprint> GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint, Type activityType) => workflowBlueprint.GetStartActivities(activityType.Name);
public static IEnumerable<IActivityBlueprint> GetStartActivities<T>(this ICompositeActivityBlueprint workflowBlueprint) where T : IActivity => workflowBlueprint.GetStartActivities(typeof(T));
public static IActivityBlueprint? GetActivity(this ICompositeActivityBlueprint workflowBlueprint, string id) => workflowBlueprint.Activities.FirstOrDefault(x => x.Id == id);
public static IEnumerable<IActivityBlueprint> GetActivities(this ICompositeActivityBlueprint workflowBlueprint, IEnumerable<string> ids) => workflowBlueprint.Activities.Where(x => ids.Contains(x.Id));
public static IEnumerable<IActivityBlueprint> GetBlockingActivities(this IWorkflowBlueprint workflowBlueprint, WorkflowInstance workflowInstance) =>
public static IEnumerable<IActivityBlueprint> GetBlockingActivities(this ICompositeActivityBlueprint workflowBlueprint, WorkflowInstance workflowInstance) =>
workflowBlueprint.Activities.Where(activity => workflowInstance.BlockingActivities.Select(x => x.ActivityId).Contains(activity.Id));
public static IEnumerable<IConnection> GetInboundConnections(
this IWorkflowBlueprint workflowBlueprint,
this ICompositeActivityBlueprint workflowBlueprint,
string activityId) =>
workflowBlueprint.Connections.Where(x => x.Target.Activity.Id == activityId).ToList();
public static IEnumerable<IConnection> GetOutboundConnections(
this IWorkflowBlueprint workflowBlueprint,
this ICompositeActivityBlueprint workflowBlueprint,
string activityId) =>
workflowBlueprint.Connections.Where(x => x.Source.Activity.Id == activityId).ToList();
@ -72,7 +45,7 @@ namespace Elsa
/// Returns the full path of incoming activities.
/// </summary>
public static IEnumerable<string> GetInboundActivityPath(
this IWorkflowBlueprint workflowBlueprint,
this ICompositeActivityBlueprint workflowBlueprint,
string activityId)
{
var inspectedActivityIds = new HashSet<string>();
@ -82,7 +55,7 @@ namespace Elsa
}
private static IEnumerable<string> GetInboundActivityPathInternal(
this IWorkflowBlueprint workflowBlueprintBlueprintInstance,
this ICompositeActivityBlueprint workflowBlueprintBlueprintInstance,
string activityId,
HashSet<string> inspectedActivityIds)
{

View file

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Services;
using Elsa.Services.Models;
using Newtonsoft.Json;
@ -59,5 +62,32 @@ namespace Elsa
var serializer = activityExecutionContext.GetService<JsonSerializer>();
return activity.Data[propertyName]!.ToObject<T>(serializer)!;
}
public static IEnumerable<IWorkflowBlueprint> WithVersion(
this IEnumerable<IWorkflowBlueprint> query,
VersionOptions version) =>
query.AsQueryable().WithVersion(version);
public static IQueryable<IWorkflowBlueprint> WithVersion(
this IQueryable<IWorkflowBlueprint> query,
VersionOptions version)
{
if (version.IsDraft)
query = query.Where(x => !x.IsPublished);
else if (version.IsLatest)
query = query.Where(x => x.IsLatest);
else if (version.IsPublished)
query = query.Where(x => x.IsPublished);
else if (version.IsLatestOrPublished)
query = query.Where(x => x.IsPublished || x.IsLatest);
else if (version.AllVersions)
{
// Nothing to filter.
}
else if (version.Version > 0)
query = query.Where(x => x.Version == version.Version);
return query.OrderByDescending(x => x.Version);
}
}
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using Elsa.ActivityResults;
using Elsa.Builders;
using Elsa.Services.Models;
@ -8,11 +7,12 @@ namespace Elsa.Services
{
public abstract class CompositeActivity : Activity
{
public abstract void Build(IWorkflowBuilder workflowBuilder);
public abstract void Build(ICompositeActivityBuilder composite);
protected override IActivityExecutionResult OnExecute(ActivityExecutionContext context)
{
var startActivities = context.ActivityBlueprint.ChildWorkflow!.GetStartActivities().Select(x => x.Id).ToList();
var compositeActivityBlueprint = (ICompositeActivityBlueprint)context.ActivityBlueprint;
var startActivities = compositeActivityBlueprint.GetStartActivities().Select(x => x.Id).ToList();
return Combine(Done(), Schedule(startActivities, null!));
}
}

View file

@ -28,8 +28,7 @@ namespace Elsa.Services.Models
public string? Name { get; set; }
public string Type { get; set; } = default!;
public bool PersistWorkflow { get; set; }
public IWorkflowBlueprint? ChildWorkflow { get; set; }
public Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> CreateActivityAsync { get; set; } = default!;
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Elsa.Services.Models
{
public class CompositeActivityBlueprint : ActivityBlueprint, ICompositeActivityBlueprint
{
public CompositeActivityBlueprint()
{
Activities = new List<IActivityBlueprint>();
Connections = new List<IConnection>();
ActivityPropertyProviders = new ActivityPropertyProviders();
}
public CompositeActivityBlueprint(
string id,
string? name,
string type,
bool persistWorkflow,
Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> createActivity) : base(id, name, type, persistWorkflow, createActivity)
{
}
public ICollection<IActivityBlueprint> Activities { get; set; } = default!;
public ICollection<IConnection> Connections { get; set; }= default!;
public IActivityPropertyProviders ActivityPropertyProviders { get; set; } = default!;
}
}

View file

@ -11,6 +11,5 @@ namespace Elsa.Services.Models
public string Type { get; }
public bool PersistWorkflow { get; }
Func<ActivityExecutionContext, CancellationToken, ValueTask<IActivity>> CreateActivityAsync { get; }
IWorkflowBlueprint? ChildWorkflow { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Elsa.Services.Models
{
public interface ICompositeActivityBlueprint : IActivityBlueprint
{
public ICollection<IActivityBlueprint> Activities { get; set; }
public ICollection<IConnection> Connections { get; }
IActivityPropertyProviders ActivityPropertyProviders { get; }
}
}

View file

@ -1,12 +1,9 @@
using System.Collections.Generic;
using Elsa.Models;
using Elsa.Models;
namespace Elsa.Services.Models
{
public interface IWorkflowBlueprint
public interface IWorkflowBlueprint : ICompositeActivityBlueprint
{
public string? Name { get; }
public string Id { get; }
public int Version { get; set; }
public bool IsSingleton { get; }
public bool IsEnabled { get; }
@ -21,8 +18,6 @@ namespace Elsa.Services.Models
public WorkflowContextOptions? ContextOptions { get; set; }
public WorkflowPersistenceBehavior PersistenceBehavior { get; }
public bool DeleteCompletedInstances { get; }
public ICollection<IActivityBlueprint> Activities { get; }
public ICollection<IConnection> Connections { get; }
IActivityPropertyProviders ActivityPropertyProviders { get; }
}
}

View file

@ -4,12 +4,10 @@ using Elsa.Models;
namespace Elsa.Services.Models
{
public class WorkflowBlueprint : IWorkflowBlueprint
public class WorkflowBlueprint : CompositeActivityBlueprint, IWorkflowBlueprint
{
public WorkflowBlueprint()
{
Activities = new List<IActivityBlueprint>();
Connections = new List<IConnection>();
ActivityPropertyProviders = new ActivityPropertyProviders();
Variables = new Variables();
}
@ -29,7 +27,7 @@ namespace Elsa.Services.Models
bool deleteCompletedInstances,
IEnumerable<IActivityBlueprint> activities,
IEnumerable<IConnection> connections,
IActivityPropertyProviders activityPropertyValueProviders)
IActivityPropertyProviders activityPropertyValueProviders) : base(id, name, id, true, null!)
{
Id = id;
Version = version;
@ -47,12 +45,10 @@ namespace Elsa.Services.Models
Connections = connections.ToList();
ActivityPropertyProviders = activityPropertyValueProviders;
}
public string Id { get; set; } = default!;
public int Version { get; set; }
public bool IsSingleton { get; set; }
public bool IsEnabled { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public bool IsPublished { get; set; }
public bool IsLatest { get; set; }
@ -60,10 +56,5 @@ namespace Elsa.Services.Models
public WorkflowContextOptions? ContextOptions { get; set; }
public WorkflowPersistenceBehavior PersistenceBehavior { get; set; }
public bool DeleteCompletedInstances { get; set; }
public ICollection<IActivityBlueprint> Activities { get; set; }
public ICollection<IConnection> Connections { get; set; }
public IActivityPropertyProviders ActivityPropertyProviders { get; set; }
}
}

View file

@ -12,7 +12,7 @@ namespace Elsa.Builders
{
public ActivityBuilder(
Type activityType,
IWorkflowBuilder workflowBuilder,
ICompositeActivityBuilder workflowBuilder,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders)
{
ActivityType = activityType;
@ -20,8 +20,8 @@ namespace Elsa.Builders
PropertyValueProviders = propertyValueProviders;
}
public Type ActivityType { get; }
public IWorkflowBuilder WorkflowBuilder { get; }
public Type ActivityType { get; } = null!;
public ICompositeActivityBuilder WorkflowBuilder { get; } = default!;
public string ActivityId { get; set; } = default!;
public string? Name { get; set; }
public string? Description { get; set; }
@ -79,6 +79,6 @@ namespace Elsa.Builders
return activity;
};
public IWorkflowBlueprint Build() => WorkflowBuilder.Build();
//public IWorkflowBlueprint Build() => WorkflowBuilder.Build();
}
}

View file

@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Elsa.Services;
using Elsa.Services.Models;
using Microsoft.Extensions.DependencyInjection;
using NetBox.Extensions;
namespace Elsa.Builders
{
public class CompositeActivityBuilder : ActivityBuilder, ICompositeActivityBuilder
{
private readonly IActivityActivator _activityActivator;
private readonly Func<ICompositeActivityBuilder> _workflowBuilderFactory;
public CompositeActivityBuilder(
IIdGenerator idGenerator,
IActivityActivator activityActivator,
IServiceProvider serviceProvider) : base()
{
_activityActivator = activityActivator;
ServiceProvider = serviceProvider;
ActivityId = idGenerator.Generate();
ActivityBuilders = new List<IActivityBuilder>();
ConnectionBuilders = new List<IConnectionBuilder>();
_workflowBuilderFactory = serviceProvider.GetRequiredService<ICompositeActivityBuilder>;
}
public IServiceProvider ServiceProvider { get; }
protected IList<IActivityBuilder> ActivityBuilders { get; }
protected IList<IConnectionBuilder> ConnectionBuilders { get; }
public IReadOnlyCollection<IActivityBuilder> Activities => ActivityBuilders.ToList().AsReadOnly();
public IActivityBuilder New(Type activityType, IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
{
var activityBuilder = new ActivityBuilder(activityType, this, propertyValueProviders);
return activityBuilder;
}
public IActivityBuilder New<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity =>
New(typeof(T), propertyValueProviders);
public IActivityBuilder New<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity
{
var propertyValuesBuilder = new SetupActivity<T>();
setup?.Invoke(propertyValuesBuilder);
var valueProviders = propertyValuesBuilder.ValueProviders.ToDictionary(
x => x.Key,
x => (IActivityPropertyValueProvider)new DelegateActivityPropertyValueProvider(x.Value));
return New<T>(branch, valueProviders);
}
public IActivityBuilder StartWith<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity
{
var activityBuilder = New(setup, branch);
return Add(activityBuilder, branch);
}
public IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default)
where T : class, IActivity =>
Add<T>(branch);
public IActivityBuilder Add<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity
{
var activityBuilder = New(setup, branch);
return Add(activityBuilder, branch);
}
public IActivityBuilder Add<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity
{
var activityBuilder = new ActivityBuilder(typeof(T), this, propertyValueProviders);
return Add(activityBuilder);
}
public IActivityBuilder Add(
IActivityBuilder activityBuilder,
Action<IActivityBuilder>? branch = default)
{
branch?.Invoke(activityBuilder);
ActivityBuilders.Add(activityBuilder);
return activityBuilder;
}
public IConnectionBuilder Connect(
IActivityBuilder source,
IActivityBuilder target,
string outcome = OutcomeNames.Done) =>
Connect(() => source, () => target, outcome);
public IConnectionBuilder Connect(
Func<IActivityBuilder> source,
Func<IActivityBuilder> target,
string outcome = OutcomeNames.Done)
{
var connectionBuilder = new ConnectionBuilder(this, source, target, outcome);
ConnectionBuilders.Add(connectionBuilder);
return connectionBuilder;
}
public ICompositeActivityBlueprint Build(string activityIdPrefix = "activity")
{
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 => x is ICompositeActivityBuilder).Cast<ICompositeActivityBuilder>();
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))
.ToDictionary(x => x.ActivityId!, x => x.PropertyValueProviders!));
var workflow = new CompositeActivityBlueprint
{
Connections = connections,
Activities = activityBlueprints,
ActivityPropertyProviders = new ActivityPropertyProviders(activityPropertyProviders)
};
return workflow;
}
private void BuildCompositeActivities(
IEnumerable<ICompositeActivityBuilder> activityBuilders,
ICollection<IActivityBlueprint> activityBlueprints,
ICollection<IConnection> connections,
IDictionary<string, IDictionary<string, IActivityPropertyValueProvider>> activityPropertyProviders)
{
foreach (var activityBuilder in activityBuilders)
{
var compositeActivity = (CompositeActivity)_activityActivator.ActivateActivity(activityBuilder.ActivityType.Name);
var workflowBuilder = _workflowBuilderFactory();
compositeActivity.Build(workflowBuilder);
var workflow = workflowBuilder.Build($"activity-{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;
}
}
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());
}
}
}

View file

@ -4,12 +4,12 @@ namespace Elsa.Builders
{
public class ConnectionBuilder : IConnectionBuilder
{
public IWorkflowBuilder WorkflowBuilder { get; }
public ICompositeActivityBuilder WorkflowBuilder { get; }
public Func<IActivityBuilder> Source { get; }
public Func<IActivityBuilder> Target { get; }
public string Outcome { get; }
public ConnectionBuilder(IWorkflowBuilder workflowBuilder, Func<IActivityBuilder> source, Func<IActivityBuilder> target, string outcome = OutcomeNames.Done)
public ConnectionBuilder(ICompositeActivityBuilder workflowBuilder, Func<IActivityBuilder> source, Func<IActivityBuilder> target, string outcome = OutcomeNames.Done)
{
Source = source;
Target = target;

View file

@ -7,14 +7,14 @@ namespace Elsa.Builders
{
public class OutcomeBuilder : IOutcomeBuilder
{
public OutcomeBuilder(IWorkflowBuilder workflowBuilder, IActivityBuilder source, string outcome = "Done")
public OutcomeBuilder(ICompositeActivityBuilder workflowBuilder, IActivityBuilder source, string outcome = "Done")
{
WorkflowBuilder = workflowBuilder;
Source = source;
Outcome = outcome;
}
public IWorkflowBuilder WorkflowBuilder { get; }
public ICompositeActivityBuilder WorkflowBuilder { get; }
public IActivityBuilder Source { get; }
public string Outcome { get; }
@ -41,6 +41,6 @@ namespace Elsa.Builders
return activityBuilder;
}
public IWorkflowBlueprint Build() => WorkflowBuilder.Build();
public IWorkflowBlueprint Build() => ((IWorkflowBuilder)WorkflowBuilder).BuildBlueprint();
}
}

View file

@ -1,40 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Elsa.Models;
using Elsa.Services;
using Elsa.Services.Models;
using Microsoft.Extensions.DependencyInjection;
using NetBox.Extensions;
namespace Elsa.Builders
{
public class WorkflowBuilder : IWorkflowBuilder
public class WorkflowBuilder : CompositeActivityBuilder, IWorkflowBuilder
{
private readonly IIdGenerator _idGenerator;
private readonly IActivityActivator _activityActivator;
private readonly Func<IWorkflowBuilder> _workflowBuilderFactory;
private readonly IList<IActivityBuilder> _activityBuilders;
private readonly IList<IConnectionBuilder> _connectionBuilders;
public WorkflowBuilder(IIdGenerator idGenerator, IActivityActivator activityActivator, Func<IWorkflowBuilder> workflowBuilderFactory, IServiceProvider serviceProvider)
public WorkflowBuilder(IIdGenerator idGenerator, IActivityActivator activityActivator, IServiceProvider serviceProvider) : base(
idGenerator,
activityActivator,
serviceProvider)
{
_idGenerator = idGenerator;
_activityActivator = activityActivator;
_workflowBuilderFactory = workflowBuilderFactory;
ServiceProvider = serviceProvider;
Id = idGenerator.Generate();
Version = 1;
IsEnabled = true;
Variables = new Variables();
_activityBuilders = new List<IActivityBuilder>();
_connectionBuilders = new List<IConnectionBuilder>();
}
public IServiceProvider ServiceProvider { get; }
public string Id { get; private set; }
public string? Name { get; private set; }
public string? Description { get; private set; }
public int Version { get; private set; }
public bool IsSingleton { get; private set; }
@ -43,19 +26,6 @@ namespace Elsa.Builders
public WorkflowPersistenceBehavior PersistenceBehavior { get; private set; }
public bool DeleteCompletedInstances { get; private set; }
public bool IsEnabled { get; private set; }
public IReadOnlyCollection<IActivityBuilder> Activities => _activityBuilders.ToList().AsReadOnly();
public IWorkflowBuilder WithId(string value)
{
Id = value;
return this;
}
public IWorkflowBuilder WithName(string? value)
{
Name = value;
return this;
}
public IWorkflowBuilder WithDescription(string? value)
{
@ -121,137 +91,27 @@ namespace Elsa.Builders
return this;
}
public IActivityBuilder New(
Type activityType,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
{
var activityBuilder = new ActivityBuilder(activityType, this, propertyValueProviders);
return activityBuilder;
}
public IActivityBuilder New<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity =>
New(typeof(T), propertyValueProviders);
public IActivityBuilder New<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity
{
var propertyValuesBuilder = new SetupActivity<T>();
setup?.Invoke(propertyValuesBuilder);
var valueProviders = propertyValuesBuilder.ValueProviders.ToDictionary(
x => x.Key,
x => (IActivityPropertyValueProvider)new DelegateActivityPropertyValueProvider(x.Value));
return New<T>(branch, valueProviders);
}
public IActivityBuilder StartWith<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity
{
var activityBuilder = New(setup, branch);
return Add(activityBuilder, branch);
}
public IActivityBuilder StartWith<T>(Action<IActivityBuilder>? branch = default)
where T : class, IActivity =>
Add<T>(branch);
public IActivityBuilder Add<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default) where T : class, IActivity
{
var activityBuilder = New(setup, branch);
return Add(activityBuilder, branch);
}
public IActivityBuilder Add<T>(
Action<IActivityBuilder>? branch = default,
IDictionary<string, IActivityPropertyValueProvider>? propertyValueProviders = default)
where T : class, IActivity
{
var activityBuilder = new ActivityBuilder(typeof(T), this, propertyValueProviders);
return Add(activityBuilder);
}
public IActivityBuilder Add(
IActivityBuilder activityBuilder,
Action<IActivityBuilder>? branch = default)
{
branch?.Invoke(activityBuilder);
_activityBuilders.Add(activityBuilder);
return activityBuilder;
}
public IConnectionBuilder Connect(
IActivityBuilder source,
IActivityBuilder target,
string outcome = OutcomeNames.Done) =>
Connect(() => source, () => target, outcome);
public IConnectionBuilder Connect(
Func<IActivityBuilder> source,
Func<IActivityBuilder> target,
string outcome = OutcomeNames.Done)
{
var connectionBuilder = new ConnectionBuilder(this, source, target, outcome);
_connectionBuilders.Add(connectionBuilder);
return connectionBuilder;
}
public IActivityBuilder Then<T>(
Action<ISetupActivity<T>>? setup = default,
Action<IActivityBuilder>? branch = default)
where T : class, IActivity
{
var activityBuilder = New(setup, branch);
return Add(activityBuilder, branch);
}
public IActivityBuilder Then<T>(Action<IActivityBuilder>? branch = default)
where T : class, IActivity =>
StartWith<T>(branch);
public IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix = "activity")
public IWorkflowBlueprint Build(IWorkflow workflow, string activityIdPrefix)
{
WithId(workflow.GetType().Name);
workflow.Build(this);
return Build(activityIdPrefix);
return BuildBlueprint(activityIdPrefix);
}
public IWorkflowBlueprint Build(Type workflowType, string activityIdPrefix)
{
var workflow = (IWorkflow)ActivatorUtilities.GetServiceOrCreateInstance(ServiceProvider, workflowType);
return Build(workflow, activityIdPrefix);
}
public IWorkflowBlueprint Build(string activityIdPrefix = "activity")
public IWorkflowBlueprint Build<T>(string activityIdPrefix) where T : IWorkflow => Build(typeof(T), activityIdPrefix);
public IWorkflowBlueprint BuildBlueprint(string activityIdPrefix)
{
var definitionId = !string.IsNullOrWhiteSpace(Id) ? Id : _idGenerator.Generate();
var activityBlueprints = new List<IActivityBlueprint>();
var connections = new List<Connection>();
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}";
var compositeRoot = base.Build(activityIdPrefix);
activityBlueprints.AddRange(_activityBuilders.Select(BuildActivityBlueprint));
// Build composite activities.
BuildCompositeActivities(_activityBuilders, 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))
.ToDictionary(x => x.ActivityId!, x => x.PropertyValueProviders!));
var workflow = new WorkflowBlueprint(
definitionId,
return new WorkflowBlueprint(
compositeRoot.Id,
Version,
IsSingleton,
IsEnabled,
@ -263,45 +123,9 @@ namespace Elsa.Builders
ContextOptions,
PersistenceBehavior,
DeleteCompletedInstances,
activityBlueprints,
connections,
new ActivityPropertyProviders(activityPropertyProviders));
return workflow;
compositeRoot.Activities,
compositeRoot.Connections,
compositeRoot.ActivityPropertyProviders);
}
private void BuildCompositeActivities(IEnumerable<IActivityBuilder> activityBuilders, ICollection<IActivityBlueprint> activityBlueprints, ICollection<Connection> connections, IDictionary<string, IDictionary<string, IActivityPropertyValueProvider>> activityPropertyProviders)
{
var compositeActivityType = typeof(CompositeActivity);
var compositeActivityBuilders = activityBuilders.Where(x => compositeActivityType.IsAssignableFrom(x.ActivityType)).ToList();
foreach (var activityBuilder in compositeActivityBuilders)
{
var compositeActivity = (CompositeActivity)_activityActivator.ActivateActivity(activityBuilder.ActivityType.Name);
var workflowBuilder = _workflowBuilderFactory();
compositeActivity.Build(workflowBuilder);
var workflow = workflowBuilder.Build($"composite-{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 = activityBlueprints.Single(x => x.Id == activityBuilder.ActivityId);
compositeActivityBlueprint.ChildWorkflow = workflow;
}
}
public IWorkflowBlueprint Build(Type workflowType, string activityIdPrefix = "activity")
{
var workflow = (IWorkflow)ActivatorUtilities.GetServiceOrCreateInstance(ServiceProvider, workflowType);
return Build(workflow, activityIdPrefix);
}
public IWorkflowBlueprint Build<T>(string activityIdPrefix = "activity") where T : IWorkflow => Build(typeof(T), activityIdPrefix);
private IActivityBlueprint BuildActivityBlueprint(IActivityBuilder builder, int index) => new ActivityBlueprint(builder.ActivityId, builder.Name, builder.ActivityType.Name, builder.PersistWorkflow, builder.BuildActivityAsync());
}
}

View file

@ -116,6 +116,7 @@ namespace Microsoft.Extensions.DependencyInjection
.AddWorkflowProvider<ProgrammaticWorkflowProvider>()
.AddWorkflowProvider<StorageWorkflowProvider>()
.AddTransient<IWorkflowBuilder, WorkflowBuilder>()
.AddTransient<ICompositeActivityBuilder, CompositeActivityBuilder>()
.AddTransient<Func<IWorkflowBuilder>>(sp => sp.GetRequiredService<IWorkflowBuilder>)
.AddAutoMapperProfile<NodaTimeProfile>()
.AddAutoMapperProfile<CloningProfile>()

View file

@ -6,9 +6,9 @@ namespace Elsa.Samples.CompositeActivitiesConsole.Activities
{
public class CountDownActivity : CompositeActivity
{
public override void Build(IWorkflowBuilder workflowBuilder)
public override void Build(ICompositeActivityBuilder composite)
{
workflowBuilder
composite
.WriteLine("3!")
.WriteLine("2!")
.WriteLine("1!");