Implemented versioning (#56)

This commit is contained in:
Sipke Schoorstra 2019-07-25 21:06:46 +02:00 committed by GitHub
parent 12448cde5b
commit cb5b2fa53c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 162 additions and 83 deletions

View file

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Elsa.Activities.Console.Extensions;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Persistence.YesSql.Extensions;
using Elsa.Persistence.YesSql.Options;
@ -31,7 +32,7 @@ namespace Sample10
await definitionStore.AddAsync(workflowDefinition);
// Load the workflow definition.
workflowDefinition = await definitionStore.GetByIdAsync(workflowDefinition.Id);
workflowDefinition = await definitionStore.GetByIdAsync(workflowDefinition.Id, VersionOptions.Latest);
// Execute the workflow.
var invoker = services.GetRequiredService<IWorkflowInvoker>();

View file

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using Elsa.Activities.Console.Extensions;
using Elsa.Activities.Primitives;
using Elsa.Activities.UserTask.Activities;
using Elsa.Activities.UserTask.Extensions;
using Elsa.Extensions;
using Elsa.Models;
@ -10,6 +11,7 @@ using Elsa.Persistence.Memory;
using Elsa.Runtime;
using Elsa.Services;
using Elsa.Services.Extensions;
using Elsa.Services.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Sample12
@ -27,8 +29,10 @@ namespace Sample12
var invoker = services.GetRequiredService<IWorkflowInvoker>();
// Invoke the workflow.
var executionContext = await invoker.InvokeAsync(workflowDefinition);
var correlationId = Guid.NewGuid().ToString("N");
await invoker.InvokeAsync(workflowDefinition, correlationId: correlationId);
WorkflowExecutionContext executionContext;
do
{
// Workflow is now halted on the user task activity. Ask user for input:
@ -36,9 +40,9 @@ namespace Sample12
var userAction = Console.ReadLine();
// Resume the workflow with the received stimulus.
var blockingActivities = executionContext.Workflow.BlockingActivities.Select(x => x.Id);
executionContext = await invoker.ResumeAsync(executionContext.Workflow.ToInstance(), new Variables { ["UserAction"] = userAction }, blockingActivities);
var triggeredExecutionContexts = await invoker.TriggerAsync(nameof(UserTask), new Variables { ["UserAction"] = userAction}, correlationId);
executionContext = triggeredExecutionContexts.First();
} while (executionContext.Workflow.IsHalted());
}

View file

@ -98,7 +98,7 @@ namespace Elsa.Activities.Http.RequestHandlers.Handlers
["Signal"] = signal.Name
};
var workflowDefinition = workflowRegistry.GetById(workflowInstance.DefinitionId);
var workflowDefinition = workflowRegistry.GetById(workflowInstance.DefinitionId, workflowInstance.Version);
var workflow = workflowFactory.CreateWorkflow(workflowDefinition, input, workflowInstance);
var blockingSignalActivities = workflow.BlockingActivities.ToList();
await workflowInvoker.ResumeAsync(workflow, blockingSignalActivities, cancellationToken);

View file

@ -0,0 +1,30 @@
namespace Elsa.Models
{
public struct VersionOptions
{
/// <summary>
/// Gets the latest version.
/// </summary>
public static readonly VersionOptions Latest = new VersionOptions { IsLatest = true };
/// <summary>
/// Gets the latest published version.
/// </summary>
public static readonly VersionOptions Published = new VersionOptions { IsPublished = true };
/// <summary>
/// Gets the latest draft version.
/// </summary>
public static readonly VersionOptions Draft = new VersionOptions { IsDraft = true };
/// <summary>
/// Gets a specific version.
/// </summary>
public static VersionOptions SpecificVersion(int version) => new VersionOptions { Version = version };
public bool IsLatest { get; private set; }
public bool IsPublished { get; private set; }
public bool IsDraft { get; private set; }
public int Version { get; private set; }
}
}

View file

@ -1,37 +1,41 @@
using System.Collections.Generic;
using System.Linq;
namespace Elsa.Models
{
public class WorkflowDefinition
{
public WorkflowDefinition()
{
}
public WorkflowDefinition(
string id,
IEnumerable<ActivityDefinition> activities,
IEnumerable<ConnectionDefinition> connections,
bool isSingleton,
Variables variables) : this(id)
{
Activities = activities.ToList();
Connections = connections.ToList();
IsSingleton = isSingleton;
Variables = variables;
}
public WorkflowDefinition(string id)
{
Id = id;
Variables = new Variables();
}
public string Id { get; }
public ICollection<ActivityDefinition> Activities { get; set; }
public IList<ConnectionDefinition> Connections { get; set; }
public Variables Variables { get; }
public bool IsSingleton { get; set; }
}
using System.Collections.Generic;
using System.Linq;
namespace Elsa.Models
{
public class WorkflowDefinition
{
public WorkflowDefinition()
{
}
public WorkflowDefinition(
string id,
int version,
IEnumerable<ActivityDefinition> activities,
IEnumerable<ConnectionDefinition> connections,
bool isSingleton,
Variables variables) : this(id)
{
Version = version;
Activities = activities.ToList();
Connections = connections.ToList();
IsSingleton = isSingleton;
Variables = variables;
}
public WorkflowDefinition(string id)
{
Id = id;
Variables = new Variables();
}
public string Id { get; }
public int Version { get; }
public ICollection<ActivityDefinition> Activities { get; set; }
public IList<ConnectionDefinition> Connections { get; set; }
public Variables Variables { get; }
public bool IsSingleton { get; set; }
public bool IsPublished { get; set; }
}
}

View file

@ -7,6 +7,7 @@ namespace Elsa.Models
{
public string Id { get; set; }
public string DefinitionId { get; set; }
public int Version { get; set; }
public WorkflowStatus Status { get; set; }
public string CorrelationId { get; set; }
public Instant CreatedAt { get; set; }

View file

@ -1,12 +1,12 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
namespace Elsa.Persistence
{
public interface IWorkflowDefinitionStore
{
Task AddAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default);
Task<WorkflowDefinition> GetByIdAsync(string id, CancellationToken cancellationToken = default);
}
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
namespace Elsa.Persistence
{
public interface IWorkflowDefinitionStore
{
Task AddAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default);
Task<WorkflowDefinition> GetByIdAsync(string id, VersionOptions version, CancellationToken cancellationToken = default);
}
}

View file

@ -9,6 +9,6 @@ namespace Elsa.Services
void RegisterWorkflow(WorkflowDefinition definition);
WorkflowDefinition RegisterWorkflow<T>() where T:IWorkflow, new();
IEnumerable<(WorkflowDefinition, ActivityDefinition)> ListByStartActivity(string activityType);
WorkflowDefinition GetById(string id);
WorkflowDefinition GetById(string id, int version);
}
}

View file

@ -13,6 +13,7 @@ namespace Elsa.Services.Models
public Workflow(
string id,
string definitionId,
int version,
IEnumerable<IActivity> activities,
IEnumerable<Connection> connections,
Variables input = default,
@ -20,6 +21,8 @@ namespace Elsa.Services.Models
{
Id = id;
DefinitionId = definitionId;
Version = version;
CorrelationId = correlationId;
Activities = activities.ToList();
Connections = connections.ToList();
Input = new Variables(input ?? Variables.Empty);
@ -34,6 +37,7 @@ namespace Elsa.Services.Models
public string Id { get; set; }
public string DefinitionId { get; }
public int Version { get; }
public string CorrelationId { get; set; }
public WorkflowStatus Status { get; set; }
public Instant CreatedAt { get; set; }
@ -56,6 +60,7 @@ namespace Elsa.Services.Models
{
Id = Id,
DefinitionId = DefinitionId,
Version = Version,
CorrelationId = CorrelationId,
Status = Status,
CreatedAt = CreatedAt,

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
@ -20,9 +21,20 @@ namespace Elsa.Persistence.Memory
return Task.CompletedTask;
}
public Task<WorkflowDefinition> GetByIdAsync(string id, CancellationToken cancellationToken = default)
public Task<WorkflowDefinition> GetByIdAsync(string id, VersionOptions version, CancellationToken cancellationToken = default)
{
var definition = definitions.ContainsKey(id) ? definitions[id] : default;
var query = definitions.Values.Where(x => x.Id == id).AsQueryable();
if (version.IsDraft)
query = query.Where(x => !x.IsPublished).OrderByDescending(x => x.Version);
else if(version.IsLatest)
query = query.OrderByDescending(x => x.Version);
else if(version.IsPublished)
query = query.Where(x => x.IsPublished).OrderByDescending(x => x.Version);
else if(version.Version > 0)
query = query.Where(x => x.Version == version.Version);
var definition = query.FirstOrDefault();
return Task.FromResult(definition);
}
}

View file

@ -18,6 +18,7 @@ namespace Elsa.Services.WorkflowBuilders
}
public string Id { get; set; }
public int Version { get; set; } = 1;
public bool IsSingleton { get; set; }
public IReadOnlyList<IActivityBuilder> Activities => activityBuilders.ToList().AsReadOnly();
@ -26,6 +27,12 @@ namespace Elsa.Services.WorkflowBuilders
Id = id;
return this;
}
public IWorkflowBuilder WithVersion(int version)
{
Version = version;
return this;
}
public IWorkflowBuilder AsSingleton(bool value)
{
@ -78,7 +85,7 @@ namespace Elsa.Services.WorkflowBuilders
var activities = activityBuilders.Select(x => x.BuildActivity()).ToList();
var connections = connectionBuilders.Select(x => x.BuildConnection()).ToList();
return new WorkflowDefinition(Id, activities, connections, IsSingleton, Variables.Empty);
return new WorkflowDefinition(Id, Version, activities, connections, IsSingleton, Variables.Empty);
}
}
}

View file

@ -32,7 +32,7 @@ namespace Elsa.Services
var activities = CreateActivities(definition.Activities).ToList();
var connections = CreateConnections(definition.Connections, activities);
var id = idGenerator.Generate();
var workflow = new Workflow(id, definition.Id, activities, connections, input);
var workflow = new Workflow(id, definition.Id, definition.Version, activities, connections, input);
if(workflowInstance != null)
workflow.Initialize(workflowInstance);

View file

@ -90,7 +90,7 @@ namespace Elsa.Services
IEnumerable<string> startActivityIds = default,
CancellationToken cancellationToken = default)
{
var definition = workflowRegistry.GetById(workflowInstance.DefinitionId);
var definition = workflowRegistry.GetById(workflowInstance.DefinitionId, workflowInstance.Version);
return InvokeAsync(definition, input, workflowInstance, startActivityIds, workflowInstance.CorrelationId, cancellationToken);
}
@ -149,7 +149,7 @@ namespace Elsa.Services
foreach (var (workflowInstance, startActivityInstance) in workflowInstances)
{
var workflowDefinition = workflowRegistry.GetById(workflowInstance.DefinitionId);
var workflowDefinition = workflowRegistry.GetById(workflowInstance.DefinitionId, workflowInstance.Version);
workflowInstance.Status = WorkflowStatus.Resuming;

View file

@ -11,17 +11,17 @@ namespace Elsa.Services
public class WorkflowRegistry : IWorkflowRegistry
{
private readonly Func<IWorkflowBuilder> workflowBuilderFactory;
private readonly IDictionary<string, WorkflowDefinition> workflowDefinitions;
private readonly IDictionary<(string, int), WorkflowDefinition> workflowDefinitions;
public WorkflowRegistry(Func<IWorkflowBuilder> workflowBuilderFactory)
{
this.workflowBuilderFactory = workflowBuilderFactory;
workflowDefinitions = new Dictionary<string, WorkflowDefinition>();
workflowDefinitions = new Dictionary<(string, int), WorkflowDefinition>();
}
public void RegisterWorkflow(WorkflowDefinition definition)
{
workflowDefinitions[definition.Id] = definition;
workflowDefinitions[(definition.Id, definition.Version)] = definition;
}
public WorkflowDefinition RegisterWorkflow<T>() where T : IWorkflow, new()
@ -42,9 +42,10 @@ namespace Elsa.Services
return query.Distinct();
}
public WorkflowDefinition GetById(string id)
public WorkflowDefinition GetById(string id, int version)
{
return workflowDefinitions.ContainsKey(id) ? workflowDefinitions[id] : default;
var identifier = (id, version);
return workflowDefinitions.ContainsKey(identifier) ? workflowDefinitions[identifier] : default;
}
}
}

View file

@ -6,9 +6,11 @@ namespace Elsa.Persistence.YesSql.Documents
public class WorkflowDefinitionDocument : YesSqlDocument
{
public string WorkflowDefinitionId { get; set; }
public int Version { get; set; }
public ICollection<ActivityDefinition> Activities { get; set; }
public IList<ConnectionDefinition> Connections { get; set; }
public Variables Variables { get; set; }
public bool IsSingleton { get; set; }
public bool IsPublished { get; set; }
}
}

View file

@ -9,6 +9,7 @@ namespace Elsa.Persistence.YesSql.Documents
public int Id { get; set; }
public string WorkflowInstanceId { get; set; }
public string DefinitionId { get; set; }
public int Version { get; set; }
public WorkflowStatus Status { get; set; }
public string CorrelationId { get; set; }
public Instant CreatedAt { get; set; }

View file

@ -9,9 +9,11 @@ namespace Elsa.Persistence.YesSql.Indexes
public class WorkflowDefinitionIndex : MapIndex
{
public string WorkflowDefinitionId { get; set; }
public int Version { get; set; }
public bool IsPublished { get; set; }
}
public class WorkflowDefinitionStartActivitiesIndex : WorkflowDefinitionIndex
public class WorkflowDefinitionStartActivitiesIndex : MapIndex
{
public string StartActivityId { get; set; }
public string StartActivityType { get; set; }
@ -23,19 +25,20 @@ namespace Elsa.Persistence.YesSql.Indexes
{
context.For<WorkflowDefinitionIndex>()
.Map(
workflowDefinition => new WorkflowDefinitionIndex
document => new WorkflowDefinitionIndex
{
WorkflowDefinitionId = workflowDefinition.WorkflowDefinitionId
WorkflowDefinitionId = document.WorkflowDefinitionId,
Version = document.Version,
IsPublished = document.IsPublished
}
);
context.For<WorkflowDefinitionStartActivitiesIndex>()
.Map(
workflowDefinition => GetStartActivities(workflowDefinition)
document => GetStartActivities(document)
.Select(
activity => new WorkflowDefinitionStartActivitiesIndex
{
WorkflowDefinitionId = workflowDefinition.WorkflowDefinitionId,
StartActivityId = activity.Id,
StartActivityType = activity.Type
}

View file

@ -12,10 +12,11 @@ namespace Elsa.Persistence.YesSql.Indexes
public WorkflowStatus WorkflowStatus { get; set; }
}
public class WorkflowInstanceBlockingActivitiesIndex : WorkflowInstanceIndex
public class WorkflowInstanceBlockingActivitiesIndex : MapIndex
{
public string ActivityId { get; set; }
public string ActivityType { get; set; }
public string CorrelationId { get; set; }
}
public class WorkflowInstanceIndexProvider : IndexProvider<WorkflowInstance>
@ -38,11 +39,9 @@ namespace Elsa.Persistence.YesSql.Indexes
.Select(
activity => new WorkflowInstanceBlockingActivitiesIndex
{
WorkflowInstanceId = workflowInstance.Id,
WorkflowDefinitionId = workflowInstance.Id,
CorrelationId = workflowInstance.CorrelationId,
ActivityId = activity.ActivityId,
ActivityType = activity.ActivityType
ActivityType = activity.ActivityType,
CorrelationId = workflowInstance.CorrelationId
}
)
);

View file

@ -31,11 +31,22 @@ namespace Elsa.Persistence.YesSql.Services
return Task.CompletedTask;
}
public async Task<WorkflowDefinition> GetByIdAsync(string id, CancellationToken cancellationToken = default)
public async Task<WorkflowDefinition> GetByIdAsync(string id, VersionOptions version, CancellationToken cancellationToken = default)
{
using (var session = sessionProvider.GetSession())
{
var document = await session.Query<WorkflowDefinitionDocument, WorkflowDefinitionIndex>(x => x.WorkflowDefinitionId == id).FirstOrDefaultAsync();
var query = session.Query<WorkflowDefinitionDocument, WorkflowDefinitionIndex>();
if (version.IsDraft)
query = query.Where(x => !x.IsPublished).OrderByDescending(x => x.Version);
else if(version.IsLatest)
query = query.OrderByDescending(x => x.Version);
else if(version.IsPublished)
query = query.Where(x => x.IsPublished).OrderByDescending(x => x.Version);
else if(version.Version > 0)
query = query.Where(x => x.Version == version.Version);
var document = await query.FirstOrDefaultAsync();
return mapper.Map<WorkflowDefinition>(document);
}

View file

@ -33,9 +33,10 @@ namespace Elsa.Persistence.YesSql.StartupTasks
new SchemaBuilder(store.Configuration, transaction, false)
.CreateMapIndexTable(nameof(WorkflowDefinitionIndex), table => table
.Column<string>("WorkflowDefinitionId")
.Column<int>("Version")
.Column<bool>("IsPublished")
)
.CreateMapIndexTable(nameof(WorkflowDefinitionStartActivitiesIndex), table => table
.Column<string>("WorkflowDefinitionId")
.Column<string>("StartActivityId")
.Column<string>("StartActivityType")
)
@ -46,12 +47,9 @@ namespace Elsa.Persistence.YesSql.StartupTasks
.Column<string>("WorkflowStatus")
)
.CreateMapIndexTable(nameof(WorkflowInstanceBlockingActivitiesIndex), table => table
.Column<string>("WorkflowInstanceId")
.Column<string>("WorkflowDefinitionId")
.Column<string>("CorrelationId")
.Column<string>("WorkflowStatus")
.Column<string>("ActivityId")
.Column<string>("ActivityType")
.Column<string>("CorrelationId")
);
transaction.Commit();