Optimize workflow selector

This commit is contained in:
Sipke Schoorstra 2020-10-17 15:38:27 +02:00
parent 4cd3d08d44
commit 3d0d372de3
14 changed files with 152 additions and 66 deletions

View file

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

View file

@ -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<ReceiveHttpRequestTrigger>
public class ReceiveHttpRequestTriggerProvider : TriggerProvider<ReceiveHttpRequestTrigger, ReceiveHttpRequest>
{
private readonly IServiceProvider _serviceProvider;
private readonly IWorkflowFactory _workflowFactory;
public ReceiveHttpRequestTriggerProvider(IServiceProvider serviceProvider, IWorkflowFactory workflowFactory)
{
_serviceProvider = serviceProvider;
_workflowFactory = workflowFactory;
}
public override async IAsyncEnumerable<ITrigger> 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<ITrigger> GetTriggerAsync(TriggerProviderContext<ReceiveHttpRequest> context, CancellationToken cancellationToken) =>
new ReceiveHttpRequestTrigger
{
var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, scope.ServiceProvider, activity);
yield return new ReceiveHttpRequestTrigger
{
ActivityId = activity.Id,
Path = await workflowBlueprint.GetActivityPropertyValue<ReceiveHttpRequest, PathString>(activity.Id, x => x.Path, activityExecutionContext, cancellationToken),
Method = await workflowBlueprint.GetActivityPropertyValue<ReceiveHttpRequest, string?>(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)
};
}
}

View file

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

View file

@ -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<TActivity> where TActivity : IActivity
{
private readonly ActivityExecutionContext _activityExecutionContext;
public ActivityBlueprintWrapper(ActivityExecutionContext activityExecutionContext)
{
_activityExecutionContext = activityExecutionContext;
}
public async ValueTask<T> GetPropertyValueAsync<T>(Expression<Func<TActivity, T>> propertyExpression, CancellationToken cancellationToken = default)
{
var workflowBlueprint = _activityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint;
var activityId = _activityExecutionContext.ActivityBlueprint.Id;
return await workflowBlueprint.GetActivityPropertyValue(activityId, propertyExpression, _activityExecutionContext, cancellationToken);
}
}
}

View file

@ -0,0 +1,6 @@
namespace Elsa.Triggers
{
public interface IDescribeTriggersContext
{
}
}

View file

@ -2,6 +2,5 @@
{
public interface ITrigger
{
string ActivityId { get; set; }
}
}

View file

@ -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<ITrigger> GetTriggersAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default);
Type ForActivityType();
ValueTask<ITrigger> GetTriggerAsync(TriggerProviderContext context, CancellationToken cancellationToken = default);
}
}

View file

@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Elsa.Triggers
{
public interface IWorkflowSelector
{
IAsyncEnumerable<WorkflowSelectorResult> SelectWorkflowsAsync(
Task<IEnumerable<WorkflowSelectorResult>> SelectWorkflowsAsync(
Type triggerType,
Func<ITrigger, bool> evaluate,
CancellationToken cancellationToken = default);

View file

@ -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!;
}
}

View file

@ -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<T> : ITriggerProvider where T : ITrigger
public abstract class TriggerProvider<T, TActivity> : ITriggerProvider where T : ITrigger where TActivity : IActivity
{
public Type ForType() => typeof(T);
public abstract IAsyncEnumerable<ITrigger> GetTriggersAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default);
public Type ForActivityType() => typeof(TActivity);
public abstract ValueTask<ITrigger> GetTriggerAsync(TriggerProviderContext<TActivity> context, CancellationToken cancellationToken);
ValueTask<ITrigger> ITriggerProvider.GetTriggerAsync(TriggerProviderContext context, CancellationToken cancellationToken) =>
GetTriggerAsync(new TriggerProviderContext<TActivity>(context.ActivityExecutionContext), cancellationToken);
}
}

View file

@ -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<TActivity> GetActivity<TActivity>() where TActivity : IActivity => new ActivityBlueprintWrapper<TActivity>(ActivityExecutionContext);
}
public class TriggerProviderContext<T> : TriggerProviderContext where T:IActivity
{
public TriggerProviderContext(ActivityExecutionContext activityExecutionContext) : base(activityExecutionContext)
{
}
public ActivityBlueprintWrapper<T> Activity => GetActivity<T>();
}
}

View file

@ -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<WorkflowSelectorResult> SelectWorkflowsAsync<T>(
public static Task<IEnumerable<WorkflowSelectorResult>> SelectWorkflowsAsync<T>(
this IWorkflowSelector workflowSelector,
Func<T, bool> evaluate,
CancellationToken cancellationToken = default) where T : ITrigger =>

View file

@ -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<ITriggerProvider> _triggerProviders;
private readonly IServiceProvider _serviceProvider;
private ICollection<TriggerDescriptor>? _descriptors;
public WorkflowSelector(IWorkflowRegistry workflowRegistry, IEnumerable<ITriggerProvider> triggerProviders)
public WorkflowSelector(IWorkflowRegistry workflowRegistry, IWorkflowFactory workflowFactory, IEnumerable<ITriggerProvider> triggerProviders, IServiceProvider serviceProvider)
{
_workflowRegistry = workflowRegistry;
_workflowFactory = workflowFactory;
_triggerProviders = triggerProviders;
_serviceProvider = serviceProvider;
}
public async IAsyncEnumerable<WorkflowSelectorResult> SelectWorkflowsAsync(
public async Task<IEnumerable<WorkflowSelectorResult>> SelectWorkflowsAsync(
Type triggerType,
Func<ITrigger, bool> 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<IEnumerable<TriggerDescriptor>> BuildDescriptorsAsync(CancellationToken cancellationToken)
{
var providers = _triggerProviders.ToList();
var workflowBlueprints = await _workflowRegistry.GetWorkflowsAsync(cancellationToken).ToList();
var descriptors = new List<TriggerDescriptor>();
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;
}
}
}