From 3d0d372de33b2a05fca0909c14242f1cb0c08608 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 17 Oct 2020 15:38:27 +0200 Subject: [PATCH] Optimize workflow selector --- .../Middleware/HttpRequestMiddleware.cs | 7 +- .../Triggers/ReceiveHttpRequestTrigger.cs | 48 ++----------- .../Extensions/WorkflowExtensions.cs | 6 ++ .../Triggers/ActivityBlueprintWrapper.cs | 26 ++++++++ .../Triggers/IDescribeTriggersContext.cs | 6 ++ .../Elsa.Abstractions/Triggers/ITrigger.cs | 1 - .../Triggers/ITriggerProvider.cs | 6 +- .../Triggers/IWorkflowSelector.cs | 3 +- .../Triggers/TriggerDescriptor.cs | 12 ++++ .../Triggers/TriggerProvider.cs | 12 ++-- .../Triggers/TriggerProviderContext.cs | 25 +++++++ .../Triggers/WorkflowSelectorExtensions.cs | 3 +- .../Elsa.Core/Triggers/WorkflowSelector.cs | 63 +++++++++++++++--- .../Elsa.Samples.HelloWorldHttp/elsa.db | Bin 61440 -> 69632 bytes 14 files changed, 152 insertions(+), 66 deletions(-) create mode 100644 src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs create mode 100644 src/core/Elsa.Abstractions/Triggers/IDescribeTriggersContext.cs create mode 100644 src/core/Elsa.Abstractions/Triggers/TriggerDescriptor.cs create mode 100644 src/core/Elsa.Abstractions/Triggers/TriggerProviderContext.cs diff --git a/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs b/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs index e0a337b56..43108f45a 100644 --- a/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs +++ b/src/activities/Elsa.Activities.Http/Middleware/HttpRequestMiddleware.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Threading; using System.Threading.Tasks; using Elsa.Activities.Http.Triggers; using Elsa.Services; @@ -22,10 +21,10 @@ namespace Elsa.Activities.Http.Middleware var path = httpContext.Request.Path; var method = httpContext.Request.Method; var cancellationToken = httpContext.RequestAborted; - + var results = await workflowSelector.SelectWorkflowsAsync( - x => x.Path == path && x.Method == null || x.Method == method, - cancellationToken).ToListAsync(cancellationToken); + x => x.Path == path && x.Method == null || x.Method == method, + cancellationToken); var result = results.FirstOrDefault(); diff --git a/src/activities/Elsa.Activities.Http/Triggers/ReceiveHttpRequestTrigger.cs b/src/activities/Elsa.Activities.Http/Triggers/ReceiveHttpRequestTrigger.cs index 0a04f6fd9..ca3f268b7 100644 --- a/src/activities/Elsa.Activities.Http/Triggers/ReceiveHttpRequestTrigger.cs +++ b/src/activities/Elsa.Activities.Http/Triggers/ReceiveHttpRequestTrigger.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; +using System.Threading; using System.Threading.Tasks; -using Elsa.Services; -using Elsa.Services.Models; using Elsa.Triggers; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; namespace Elsa.Activities.Http.Triggers { @@ -16,42 +9,15 @@ namespace Elsa.Activities.Http.Triggers { public PathString Path { get; set; } public string? Method { get; set; } - public string ActivityId { get; set; } } - public class ReceiveHttpRequestTriggerProvider : TriggerProvider + public class ReceiveHttpRequestTriggerProvider : TriggerProvider { - private readonly IServiceProvider _serviceProvider; - private readonly IWorkflowFactory _workflowFactory; - - public ReceiveHttpRequestTriggerProvider(IServiceProvider serviceProvider, IWorkflowFactory workflowFactory) - { - _serviceProvider = serviceProvider; - _workflowFactory = workflowFactory; - } - - public override async IAsyncEnumerable GetTriggersAsync(IWorkflowBlueprint workflowBlueprint, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var httpRequestActivities = workflowBlueprint - .GetStartActivities() - .Where(x => x.Type == nameof(ReceiveHttpRequest)) - .ToList(); - - using var scope = _serviceProvider.CreateScope(); - var workflowInstance = await _workflowFactory.InstantiateAsync(workflowBlueprint, cancellationToken: cancellationToken); - var workflowExecutionContext = new WorkflowExecutionContext(scope.ServiceProvider, workflowBlueprint, workflowInstance); - - foreach (var activity in httpRequestActivities) + public override async ValueTask GetTriggerAsync(TriggerProviderContext context, CancellationToken cancellationToken) => + new ReceiveHttpRequestTrigger { - var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, scope.ServiceProvider, activity); - - yield return new ReceiveHttpRequestTrigger - { - ActivityId = activity.Id, - Path = await workflowBlueprint.GetActivityPropertyValue(activity.Id, x => x.Path, activityExecutionContext, cancellationToken), - Method = await workflowBlueprint.GetActivityPropertyValue(activity.Id, x => x.Method, activityExecutionContext, cancellationToken) - }; - } - } + Path = await context.Activity.GetPropertyValueAsync(x => x.Path, cancellationToken), + Method = await context.Activity.GetPropertyValueAsync(x => x.Method, cancellationToken) + }; } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs b/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs index fb920b8b7..339a652ca 100644 --- a/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs +++ b/src/core/Elsa.Abstractions/Extensions/WorkflowExtensions.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Linq; using Elsa.Models; +using Elsa.Services; using Elsa.Services.Models; namespace Elsa @@ -47,6 +49,10 @@ namespace Elsa return query; } + public static IEnumerable GetStartActivities(this IWorkflowBlueprint workflowBlueprint, string activityType) => workflowBlueprint.GetStartActivities().Where(x => x.Type == activityType); + public static IEnumerable GetStartActivities(this IWorkflowBlueprint workflowBlueprint, Type activityType) => workflowBlueprint.GetStartActivities(activityType.Name); + public static IEnumerable GetStartActivities(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); diff --git a/src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs b/src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs new file mode 100644 index 000000000..70e1b13ca --- /dev/null +++ b/src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Services; +using Elsa.Services.Models; + +namespace Elsa.Triggers +{ + public class ActivityBlueprintWrapper where TActivity : IActivity + { + private readonly ActivityExecutionContext _activityExecutionContext; + + public ActivityBlueprintWrapper(ActivityExecutionContext activityExecutionContext) + { + _activityExecutionContext = activityExecutionContext; + } + + public async ValueTask GetPropertyValueAsync(Expression> propertyExpression, CancellationToken cancellationToken = default) + { + var workflowBlueprint = _activityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint; + var activityId = _activityExecutionContext.ActivityBlueprint.Id; + return await workflowBlueprint.GetActivityPropertyValue(activityId, propertyExpression, _activityExecutionContext, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/IDescribeTriggersContext.cs b/src/core/Elsa.Abstractions/Triggers/IDescribeTriggersContext.cs new file mode 100644 index 000000000..b7c01b313 --- /dev/null +++ b/src/core/Elsa.Abstractions/Triggers/IDescribeTriggersContext.cs @@ -0,0 +1,6 @@ +namespace Elsa.Triggers +{ + public interface IDescribeTriggersContext + { + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/ITrigger.cs b/src/core/Elsa.Abstractions/Triggers/ITrigger.cs index eb93fbb47..1b52d8564 100644 --- a/src/core/Elsa.Abstractions/Triggers/ITrigger.cs +++ b/src/core/Elsa.Abstractions/Triggers/ITrigger.cs @@ -2,6 +2,5 @@ { public interface ITrigger { - string ActivityId { get; set; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/ITriggerProvider.cs b/src/core/Elsa.Abstractions/Triggers/ITriggerProvider.cs index e3297d3f7..1c0ad0bac 100644 --- a/src/core/Elsa.Abstractions/Triggers/ITriggerProvider.cs +++ b/src/core/Elsa.Abstractions/Triggers/ITriggerProvider.cs @@ -1,13 +1,13 @@ using System; -using System.Collections.Generic; using System.Threading; -using Elsa.Services.Models; +using System.Threading.Tasks; namespace Elsa.Triggers { public interface ITriggerProvider { Type ForType(); - IAsyncEnumerable GetTriggersAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default); + Type ForActivityType(); + ValueTask GetTriggerAsync(TriggerProviderContext context, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/IWorkflowSelector.cs b/src/core/Elsa.Abstractions/Triggers/IWorkflowSelector.cs index ca6cf32af..d872219c3 100644 --- a/src/core/Elsa.Abstractions/Triggers/IWorkflowSelector.cs +++ b/src/core/Elsa.Abstractions/Triggers/IWorkflowSelector.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; namespace Elsa.Triggers { public interface IWorkflowSelector { - IAsyncEnumerable SelectWorkflowsAsync( + Task> SelectWorkflowsAsync( Type triggerType, Func evaluate, CancellationToken cancellationToken = default); diff --git a/src/core/Elsa.Abstractions/Triggers/TriggerDescriptor.cs b/src/core/Elsa.Abstractions/Triggers/TriggerDescriptor.cs new file mode 100644 index 000000000..f2fc01f52 --- /dev/null +++ b/src/core/Elsa.Abstractions/Triggers/TriggerDescriptor.cs @@ -0,0 +1,12 @@ +using Elsa.Services.Models; + +namespace Elsa.Triggers +{ + public class TriggerDescriptor + { + public IWorkflowBlueprint WorkflowBlueprint { get; set; } = default!; + public string ActivityId { get; set; } = default!; + public string ActivityType { get; set; } = default!; + public ITrigger Trigger { get; set; } = default!; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/TriggerProvider.cs b/src/core/Elsa.Abstractions/Triggers/TriggerProvider.cs index 7497d5f92..4f2b5135e 100644 --- a/src/core/Elsa.Abstractions/Triggers/TriggerProvider.cs +++ b/src/core/Elsa.Abstractions/Triggers/TriggerProvider.cs @@ -1,13 +1,17 @@ using System; -using System.Collections.Generic; using System.Threading; -using Elsa.Services.Models; +using System.Threading.Tasks; +using Elsa.Services; namespace Elsa.Triggers { - public abstract class TriggerProvider : ITriggerProvider where T : ITrigger + public abstract class TriggerProvider : ITriggerProvider where T : ITrigger where TActivity : IActivity { public Type ForType() => typeof(T); - public abstract IAsyncEnumerable GetTriggersAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default); + public Type ForActivityType() => typeof(TActivity); + public abstract ValueTask GetTriggerAsync(TriggerProviderContext context, CancellationToken cancellationToken); + + ValueTask ITriggerProvider.GetTriggerAsync(TriggerProviderContext context, CancellationToken cancellationToken) => + GetTriggerAsync(new TriggerProviderContext(context.ActivityExecutionContext), cancellationToken); } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/TriggerProviderContext.cs b/src/core/Elsa.Abstractions/Triggers/TriggerProviderContext.cs new file mode 100644 index 000000000..b5332993f --- /dev/null +++ b/src/core/Elsa.Abstractions/Triggers/TriggerProviderContext.cs @@ -0,0 +1,25 @@ +using Elsa.Services; +using Elsa.Services.Models; + +namespace Elsa.Triggers +{ + public class TriggerProviderContext + { + public TriggerProviderContext(ActivityExecutionContext activityExecutionContext) + { + ActivityExecutionContext = activityExecutionContext; + } + + public ActivityExecutionContext ActivityExecutionContext { get; } + public ActivityBlueprintWrapper GetActivity() where TActivity : IActivity => new ActivityBlueprintWrapper(ActivityExecutionContext); + } + + public class TriggerProviderContext : TriggerProviderContext where T:IActivity + { + public TriggerProviderContext(ActivityExecutionContext activityExecutionContext) : base(activityExecutionContext) + { + } + + public ActivityBlueprintWrapper Activity => GetActivity(); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorExtensions.cs b/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorExtensions.cs index b761428c3..0695ae9f1 100644 --- a/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorExtensions.cs +++ b/src/core/Elsa.Abstractions/Triggers/WorkflowSelectorExtensions.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; namespace Elsa.Triggers { public static class WorkflowSelectorExtensions { - public static IAsyncEnumerable SelectWorkflowsAsync( + public static Task> SelectWorkflowsAsync( this IWorkflowSelector workflowSelector, Func evaluate, CancellationToken cancellationToken = default) where T : ITrigger => diff --git a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs index 1a624d890..776bf8756 100644 --- a/src/core/Elsa.Core/Triggers/WorkflowSelector.cs +++ b/src/core/Elsa.Core/Triggers/WorkflowSelector.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Elsa.Services; +using Elsa.Services.Models; +using Microsoft.Extensions.DependencyInjection; using Open.Linq.AsyncExtensions; namespace Elsa.Triggers @@ -12,31 +13,71 @@ namespace Elsa.Triggers public class WorkflowSelector : IWorkflowSelector { private readonly IWorkflowRegistry _workflowRegistry; + private readonly IWorkflowFactory _workflowFactory; private readonly IEnumerable _triggerProviders; + private readonly IServiceProvider _serviceProvider; + private ICollection? _descriptors; - public WorkflowSelector(IWorkflowRegistry workflowRegistry, IEnumerable triggerProviders) + public WorkflowSelector(IWorkflowRegistry workflowRegistry, IWorkflowFactory workflowFactory, IEnumerable triggerProviders, IServiceProvider serviceProvider) { _workflowRegistry = workflowRegistry; + _workflowFactory = workflowFactory; _triggerProviders = triggerProviders; + _serviceProvider = serviceProvider; } - public async IAsyncEnumerable SelectWorkflowsAsync( + public async Task> SelectWorkflowsAsync( Type triggerType, Func evaluate, - [EnumeratorCancellation] CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default) { - var providers = _triggerProviders.Where(x => x.ForType() == triggerType).ToList(); - var workflowBlueprints = await _workflowRegistry.GetWorkflowsAsync(cancellationToken).ToList(); + _descriptors ??= await BuildDescriptorsAsync(cancellationToken).ToList(); + + var query = + from descriptor in _descriptors + let trigger = descriptor.Trigger + where trigger.GetType() == triggerType && evaluate(trigger) + select new WorkflowSelectorResult(descriptor.WorkflowBlueprint, descriptor.ActivityId); + + return query; + } + + private async Task> BuildDescriptorsAsync(CancellationToken cancellationToken) + { + var providers = _triggerProviders.ToList(); + var workflowBlueprints = await _workflowRegistry.GetWorkflowsAsync(cancellationToken).ToList(); + var descriptors = new List(); + + using var scope = _serviceProvider.CreateScope(); - foreach (var provider in providers) foreach (var workflowBlueprint in workflowBlueprints) { - var triggers = provider.GetTriggersAsync(workflowBlueprint, cancellationToken); + var workflowInstance = await _workflowFactory.InstantiateAsync(workflowBlueprint, cancellationToken: cancellationToken); + var workflowExecutionContext = new WorkflowExecutionContext(scope.ServiceProvider, workflowBlueprint, workflowInstance); + var startActivities = workflowBlueprint.GetStartActivities(); - await foreach (var trigger in triggers.WithCancellation(cancellationToken)) - if (evaluate(trigger)) - yield return new WorkflowSelectorResult(workflowBlueprint, trigger.ActivityId); + foreach (var startActivity in startActivities) + { + var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, scope.ServiceProvider, startActivity); + var providerContext = new TriggerProviderContext(activityExecutionContext); + + foreach (var provider in providers) + { + var trigger = await provider.GetTriggerAsync(providerContext, cancellationToken); + var descriptor = new TriggerDescriptor + { + ActivityId = startActivity.Id, + ActivityType = startActivity.Type, + WorkflowBlueprint = workflowBlueprint, + Trigger = trigger, + }; + + descriptors.Add(descriptor); + } + } } + + return descriptors; } } } \ No newline at end of file diff --git a/src/samples/Elsa.Samples.HelloWorldHttp/elsa.db b/src/samples/Elsa.Samples.HelloWorldHttp/elsa.db index 03cf2c6e9c3408839177dea1574391bb298fdbd4..60984edfc7055fdfc809d28cacc9c1f159a74e5b 100644 GIT binary patch delta 480 zcmc(by-or_6oqF3J0pL4Sz}`;+{7Mtc9xli#s{#mvz3LRvCzWAga#4|OMf;LHo^l~ zNcIH?F-8g#ODbbQWo2P3M8E;UGuV8|ZO*ypYre;to%mJGNF#)-YkL=`yN8|Q4MU$8 z#+t~2vGEw|<6igr9w*Nmgy5r#tpo{idncTo5iG*B`nFy0WlgBETV{U6b1OBUt8$?b zNF}(#Yf^@L^#kA~E=!)5XUr|S!i^s4oAXFx8ELE{BYgUk%IWSbNorCUBKU=G_=FD_ z!5a)C%kD$kh+$0WoP>mq*DpD4UNFu@kuT)_0bS*YrvLx| delta 127 zcmV-_0D%91pag*61CSd93IG5A50M~00Sd8Tpl_0}1P%oQ!T<}&3CIV=1;hiwvk?%m z0}6wG1qK0YhaU+AlL4PElmB-HvjOz~3la(d000FK1n?vk?&M4wIFhGqW(C&`|rlD=q*4