Sycnrhonize access to publish / save workflow definition endpoint (#3743)

* Refactor SaveAsync and Store APIs

* Synchronize Publish endpoint
This commit is contained in:
Sipke Schoorstra 2023-02-28 15:14:45 +01:00 committed by GitHub
parent c3a8898247
commit bb669c2cfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 129 additions and 70 deletions

View file

@ -0,0 +1,17 @@
using Elsa.Common.Entities;
using Microsoft.EntityFrameworkCore;
namespace Elsa.EntityFrameworkCore.Common;
public class EntityStore<TDbContext, TEntity> : Store<TDbContext, TEntity> where TDbContext : DbContext where TEntity : Entity
{
public EntityStore(IDbContextFactory<TDbContext> dbContextFactory) : base(dbContextFactory)
{
}
public async Task SaveAsync(TEntity entity, CancellationToken cancellationToken = default) => await SaveAsync(entity, null, cancellationToken);
public async Task SaveAsync(TEntity entity, Func<TDbContext, TEntity, TEntity>? onSaving, CancellationToken cancellationToken = default) => await SaveAsync(entity, x => x.Id, onSaving, cancellationToken);
public async Task SaveManyAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, default, cancellationToken);
public async Task SaveManyAsync(IEnumerable<TEntity> entities, Func<TDbContext, TEntity, TEntity>? onSaving = default, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, x => x.Id, onSaving, cancellationToken);
}

View file

@ -1,3 +1,4 @@
using Elsa.Common.Entities;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Microsoft.EntityFrameworkCore;
@ -37,4 +38,12 @@ public abstract class PersistenceFeatureBase<TDbContext> : FeatureBase where TDb
.AddSingleton<TStore>()
;
}
protected void AddEntityStore<TEntity, TStore>() where TEntity : Entity where TStore : class
{
Services
.AddSingleton<EntityStore<TDbContext, TEntity>>()
.AddSingleton<TStore>()
;
}
}

View file

@ -17,26 +17,24 @@ public class Store<TDbContext, TEntity> where TDbContext : DbContext where TEnti
public async Task<TDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default) => await _dbContextFactory.CreateDbContextAsync(cancellationToken);
public async Task SaveAsync(TEntity entity, CancellationToken cancellationToken = default) => await SaveAsync(entity, default, default, cancellationToken);
public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, string>> keySelector, CancellationToken cancellationToken = default) => await SaveAsync(entity, keySelector, null, cancellationToken);
public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, object>>? uniqueField = default, CancellationToken cancellationToken = default) => await SaveAsync(entity, uniqueField, default, cancellationToken);
public async Task SaveAsync(TEntity entity, Func<TDbContext, TEntity, TEntity>? onSaving = default, CancellationToken cancellationToken = default) => await SaveAsync(entity, default, onSaving, cancellationToken);
public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, object>>? uniqueField = default, Func<TDbContext, TEntity, TEntity>? onSaving = default, CancellationToken cancellationToken = default)
public async Task SaveAsync(TEntity entity, Expression<Func<TEntity, string>> keySelector, Func<TDbContext, TEntity, TEntity>? onSaving, CancellationToken cancellationToken = default)
{
await using var dbContext = await CreateDbContextAsync(cancellationToken);
entity = onSaving?.Invoke(dbContext, entity) ?? entity;
await dbContext.BulkUpsertAsync(new[] { entity }, uniqueField, cancellationToken);
var set = dbContext.Set<TEntity>();
var lambda = keySelector.BuildEqualsExpresion(entity);
var exists = await set.AnyAsync(lambda, cancellationToken);
set.Entry(entity).State = exists ? EntityState.Modified : EntityState.Added;
await dbContext.SaveChangesAsync(cancellationToken);
}
public async Task SaveManyAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, default, default, cancellationToken);
public async Task SaveManyAsync(IEnumerable<TEntity> entities, Expression<Func<TEntity, object>>? uniqueField = default, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, uniqueField, default, cancellationToken);
public async Task SaveManyAsync(IEnumerable<TEntity> entities, Expression<Func<TEntity, string>> keySelector, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, keySelector, default, cancellationToken);
public async Task SaveManyAsync(IEnumerable<TEntity> entities, Func<TDbContext, TEntity, TEntity>? onSaving = default, CancellationToken cancellationToken = default) => await SaveManyAsync(entities, default, onSaving, cancellationToken);
public async Task SaveManyAsync(IEnumerable<TEntity> entities, Expression<Func<TEntity, object>>? uniqueField = default, Func<TDbContext, TEntity, TEntity>? onSaving = default, CancellationToken cancellationToken = default)
public async Task SaveManyAsync(IEnumerable<TEntity> entities, Expression<Func<TEntity, string>> keySelector, Func<TDbContext, TEntity, TEntity>? onSaving = default, CancellationToken cancellationToken = default)
{
await using var dbContext = await CreateDbContextAsync(cancellationToken);
var entityList = entities.ToList();
@ -44,7 +42,7 @@ public class Store<TDbContext, TEntity> where TDbContext : DbContext where TEnti
if (onSaving != null)
entityList = entityList.Select(x => onSaving(dbContext, x)).ToList();
await dbContext.BulkUpsertAsync(entityList, uniqueField, cancellationToken);
await dbContext.BulkUpsertAsync(entityList, keySelector, cancellationToken);
}
public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => await FindAsync(predicate, default, cancellationToken);

View file

@ -1,20 +1,43 @@
using System.Linq.Expressions;
using System.Reflection;
using Elsa.Extensions;
using Elsa.Workflows.Runtime.Entities;
namespace Elsa.EntityFrameworkCore.Extensions;
public static class ExpressionExtensions
{
public static Expression<Func<TEntity, bool>> BuildContainsExpression<TEntity>(this Func<TEntity, object> uniqueFieldDelegate, IEnumerable<TEntity> entities, PropertyInfo property) where TEntity : class
public static Expression<Func<TEntity, bool>> BuildContainsExpression<TEntity>(this Expression<Func<TEntity, string>> keySelector, IEnumerable<TEntity> entities) where TEntity : class
{
var list = entities.Select(uniqueFieldDelegate.Invoke);
var compiledKeySelector = keySelector.Compile();
var list = entities.Select(compiledKeySelector);
var property = keySelector.GetProperty()!;
var param = Expression.Parameter(typeof(TEntity));
var body = Expression.Call(
typeof(Enumerable),
"Contains",
new[] {uniqueFieldDelegate.Method.ReturnType},
new[] {compiledKeySelector.Method.ReturnType},
Expression.Constant(list), Expression.Property(param, property));
return Expression.Lambda<Func<TEntity, bool>>(body, param);
}
public static Expression<Func<TEntity, bool>> BuildEqualsExpresion<TEntity>(this Expression<Func<TEntity, string>> keySelector, TEntity entity)
{
var keyName = keySelector.GetProperty()!.Name;
// Define parameters for the lambda expression
var parameter = Expression.Parameter(typeof(TEntity), "x");
var keySelectorLambda = Expression.Lambda<Func<TEntity, string>>(Expression.Property(parameter, keyName), parameter);
// Build the expression that compares the keys
var entityKey = keySelectorLambda.Compile()(entity);
var comparison = Expression.Equal(keySelectorLambda.Body, Expression.Constant(entityKey));
// Create the final lambda expression that can be used in AnyAsync
var lambda = Expression.Lambda<Func<TEntity, bool>>(comparison, parameter);
return lambda;
}
}

View file

@ -1,7 +1,5 @@
using System.Linq.Expressions;
using Elsa.Common.Entities;
using Elsa.Common.Models;
using Elsa.Extensions;
using Microsoft.EntityFrameworkCore;
namespace Elsa.EntityFrameworkCore.Extensions;
@ -14,17 +12,13 @@ public static class QueryableExtensions
/// <summary>
/// Inserts or updates a list of entities in bulk.
/// </summary>
public static async Task BulkUpsertAsync<TDbContext, TEntity>(this TDbContext dbContext, IList<TEntity> entities, Expression<Func<TEntity, object>>? uniqueFieldExpression = default, CancellationToken cancellationToken = default) where TDbContext : DbContext where TEntity : class
public static async Task BulkUpsertAsync<TDbContext, TEntity>(this TDbContext dbContext, IList<TEntity> entities, Expression<Func<TEntity, string>> keySelector, CancellationToken cancellationToken = default) where TDbContext : DbContext where TEntity : class
{
uniqueFieldExpression = ResolveUniqueFieldExpression(uniqueFieldExpression);
var uniqueFieldDelegate = uniqueFieldExpression.Compile();
var propertyInfo = uniqueFieldExpression.GetProperty()!;
var set = dbContext.Set<TEntity>();
var lambda = uniqueFieldDelegate.BuildContainsExpression(entities, propertyInfo);
var existingEntities = await set.AsNoTracking().Where(lambda).ToListAsync(cancellationToken);
var entitiesToUpdate = entities.Where(e => existingEntities.Any(ex => uniqueFieldDelegate.Invoke(ex).ToString() == uniqueFieldDelegate.Invoke(e).ToString())).ToList();
var compiledKeySelector = keySelector.Compile();
var containsLambda = keySelector.BuildContainsExpression(entities);
var existingEntities = await set.AsNoTracking().Where(containsLambda).ToListAsync(cancellationToken);
var entitiesToUpdate = entities.IntersectBy(existingEntities.Select(compiledKeySelector), compiledKeySelector).ToList();
var entitiesToInsert = entities.Except(entitiesToUpdate).ToList();
if (entitiesToUpdate.Any())
@ -58,20 +52,4 @@ public static class QueryableExtensions
var results = await queryable.ToListAsync();
return Page.Of(results, count);
}
private static Expression<Func<TEntity, object>> ResolveUniqueFieldExpression<TEntity>(Expression<Func<TEntity, object>>? uniqueFieldExpression) where TEntity : class
{
if (uniqueFieldExpression != null) return uniqueFieldExpression;
try
{
uniqueFieldExpression = e => ((Entity)(object)e).Id;
}
catch (Exception)
{
throw new Exception(
"Unique field expression must be passed via BulkUpsertAsync if default object to upsert is not of type Entity.");
}
return uniqueFieldExpression;
}
}

View file

@ -28,7 +28,7 @@ public class EFCoreLabelPersistenceFeature : PersistenceFeatureBase<LabelsElsaDb
{
base.Apply();
AddStore<Label, EFCoreLabelStore>();
AddStore<WorkflowDefinitionLabel, EFCoreWorkflowDefinitionLabelStore>();
AddEntityStore<Label, EFCoreLabelStore>();
AddEntityStore<WorkflowDefinitionLabel, EFCoreWorkflowDefinitionLabelStore>();
}
}

View file

@ -8,10 +8,10 @@ namespace Elsa.EntityFrameworkCore.Modules.Labels;
public class EFCoreLabelStore : ILabelStore
{
private readonly Store<LabelsElsaDbContext, Label> _labelStore;
private readonly Store<LabelsElsaDbContext, WorkflowDefinitionLabel> _workflowDefinitionLabelStore;
private readonly EntityStore<LabelsElsaDbContext, Label> _labelStore;
private readonly EntityStore<LabelsElsaDbContext, WorkflowDefinitionLabel> _workflowDefinitionLabelStore;
public EFCoreLabelStore(Store<LabelsElsaDbContext, Label> labelStore, Store<LabelsElsaDbContext, WorkflowDefinitionLabel> workflowDefinitionLabelStore)
public EFCoreLabelStore(EntityStore<LabelsElsaDbContext, Label> labelStore, EntityStore<LabelsElsaDbContext, WorkflowDefinitionLabel> workflowDefinitionLabelStore)
{
_labelStore = labelStore;
_workflowDefinitionLabelStore = workflowDefinitionLabelStore;

View file

@ -4,18 +4,30 @@ using Elsa.Labels.Services;
namespace Elsa.EntityFrameworkCore.Modules.Labels;
/// <inheritdoc />
public class EFCoreWorkflowDefinitionLabelStore : IWorkflowDefinitionLabelStore
{
private readonly Store<LabelsElsaDbContext, WorkflowDefinitionLabel> _store;
public EFCoreWorkflowDefinitionLabelStore(Store<LabelsElsaDbContext, WorkflowDefinitionLabel> store) => _store = store;
private readonly EntityStore<LabelsElsaDbContext, WorkflowDefinitionLabel> _store;
/// <summary>
/// Constructor
/// </summary>
public EFCoreWorkflowDefinitionLabelStore(EntityStore<LabelsElsaDbContext, WorkflowDefinitionLabel> store) => _store = store;
/// <inheritdoc />
public async Task SaveAsync(WorkflowDefinitionLabel record, CancellationToken cancellationToken = default) => await _store.SaveAsync(record, cancellationToken);
/// <inheritdoc />
public async Task SaveManyAsync(IEnumerable<WorkflowDefinitionLabel> records, CancellationToken cancellationToken = default) => await _store.SaveManyAsync(records, cancellationToken);
/// <inheritdoc />
public async Task<bool> DeleteAsync(string id, CancellationToken cancellationToken = default) => await _store.DeleteWhereAsync(x => x.Id == id, cancellationToken) > 0;
/// <inheritdoc />
public async Task<IEnumerable<WorkflowDefinitionLabel>> FindByWorkflowDefinitionVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) =>
await _store.FindManyAsync(x => x.WorkflowDefinitionVersionId == workflowDefinitionVersionId, cancellationToken);
/// <inheritdoc />
public async Task ReplaceAsync(IEnumerable<WorkflowDefinitionLabel> removed, IEnumerable<WorkflowDefinitionLabel> added, CancellationToken cancellationToken = default)
{
var idList = removed.Select(r => r.Id);
@ -23,18 +35,22 @@ public class EFCoreWorkflowDefinitionLabelStore : IWorkflowDefinitionLabelStore
await _store.SaveManyAsync(added, cancellationToken);
}
/// <inheritdoc />
public async Task<int> DeleteByWorkflowDefinitionIdAsync(string workflowDefinitionId, CancellationToken cancellationToken = default) =>
await _store.DeleteWhereAsync(x => x.WorkflowDefinitionId == workflowDefinitionId, cancellationToken);
/// <inheritdoc />
public async Task<int> DeleteByWorkflowDefinitionVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default) =>
await _store.DeleteWhereAsync(x => x.WorkflowDefinitionVersionId == workflowDefinitionVersionId, cancellationToken);
/// <inheritdoc />
public async Task<int> DeleteByWorkflowDefinitionIdsAsync(IEnumerable<string> workflowDefinitionIds, CancellationToken cancellationToken = default)
{
var ids = workflowDefinitionIds.ToList();
return await _store.DeleteWhereAsync(x => ids.Contains(x.WorkflowDefinitionId), cancellationToken);
}
/// <inheritdoc />
public async Task<int> DeleteByWorkflowDefinitionVersionIdsAsync(IEnumerable<string> workflowDefinitionVersionIds, CancellationToken cancellationToken = default)
{
var ids = workflowDefinitionVersionIds.ToList();

View file

@ -32,7 +32,7 @@ public class EFCoreWorkflowManagementPersistenceFeature : PersistenceFeatureBase
{
base.Apply();
AddStore<WorkflowInstance, EFCoreWorkflowInstanceStore>();
AddStore<WorkflowDefinition, EFCoreWorkflowDefinitionStore>();
AddEntityStore<WorkflowInstance, EFCoreWorkflowInstanceStore>();
AddEntityStore<WorkflowDefinition, EFCoreWorkflowDefinitionStore>();
}
}

View file

@ -32,6 +32,6 @@ public class EFCoreWorkflowDefinitionPersistenceFeature : PersistenceFeatureBase
{
base.Apply();
AddStore<WorkflowDefinition, EFCoreWorkflowDefinitionStore>();
AddEntityStore<WorkflowDefinition, EFCoreWorkflowDefinitionStore>();
}
}

View file

@ -15,16 +15,16 @@ namespace Elsa.EntityFrameworkCore.Modules.Management;
/// <inheritdoc />
public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
{
private readonly Store<ManagementElsaDbContext, WorkflowDefinition> _store;
private readonly Store<ManagementElsaDbContext, WorkflowInstance> _workflowInstanceStore;
private readonly EntityStore<ManagementElsaDbContext, WorkflowDefinition> _store;
private readonly EntityStore<ManagementElsaDbContext, WorkflowInstance> _workflowInstanceStore;
private readonly SerializerOptionsProvider _serializerOptionsProvider;
/// <summary>
/// Constructor.
/// </summary>
public EFCoreWorkflowDefinitionStore(
Store<ManagementElsaDbContext, WorkflowDefinition> store,
Store<ManagementElsaDbContext, WorkflowInstance> workflowInstanceStore,
EntityStore<ManagementElsaDbContext, WorkflowDefinition> store,
EntityStore<ManagementElsaDbContext, WorkflowInstance> workflowInstanceStore,
SerializerOptionsProvider serializerOptionsProvider)
{
_store = store;

View file

@ -32,6 +32,6 @@ public class EFCoreWorkflowInstancePersistenceFeature : PersistenceFeatureBase<M
{
base.Apply();
AddStore<WorkflowInstance, EFCoreWorkflowInstanceStore>();
AddEntityStore<WorkflowInstance, EFCoreWorkflowInstanceStore>();
}
}

View file

@ -17,13 +17,13 @@ namespace Elsa.EntityFrameworkCore.Modules.Management;
/// </summary>
public class EFCoreWorkflowInstanceStore : IWorkflowInstanceStore
{
private readonly Store<ManagementElsaDbContext, WorkflowInstance> _store;
private readonly EntityStore<ManagementElsaDbContext, WorkflowInstance> _store;
private readonly SerializerOptionsProvider _serializerOptionsProvider;
/// <summary>
/// Constructor.
/// </summary>
public EFCoreWorkflowInstanceStore(Store<ManagementElsaDbContext, WorkflowInstance> store, SerializerOptionsProvider serializerOptionsProvider)
public EFCoreWorkflowInstanceStore(EntityStore<ManagementElsaDbContext, WorkflowInstance> store, SerializerOptionsProvider serializerOptionsProvider)
{
_store = store;
_serializerOptionsProvider = serializerOptionsProvider;

View file

@ -30,8 +30,8 @@ public class EFCoreDefaultRuntimePersistenceFeature : PersistenceFeatureBase<Run
{
base.Apply();
AddStore<WorkflowState, EFCoreWorkflowStateStore>();
AddStore<StoredTrigger, EFCoreTriggerStore>();
AddEntityStore<WorkflowState, EFCoreWorkflowStateStore>();
AddEntityStore<StoredTrigger, EFCoreTriggerStore>();
AddStore<StoredBookmark, EFCoreBookmarkStore>();
}
}

View file

@ -26,6 +26,6 @@ public class EFCoreExecutionLogRecordPersistenceFeature : PersistenceFeatureBase
{
base.Apply();
AddStore<WorkflowExecutionLogRecord, EFCoreWorkflowExecutionLogStore>();
AddEntityStore<WorkflowExecutionLogRecord, EFCoreWorkflowExecutionLogStore>();
}
}

View file

@ -7,12 +7,12 @@ namespace Elsa.EntityFrameworkCore.Modules.Runtime;
/// <inheritdoc />
public class EFCoreTriggerStore : ITriggerStore
{
private readonly Store<RuntimeElsaDbContext, StoredTrigger> _store;
private readonly EntityStore<RuntimeElsaDbContext, StoredTrigger> _store;
/// <summary>
/// Constructor.
/// </summary>
public EFCoreTriggerStore(Store<RuntimeElsaDbContext, StoredTrigger> store)
public EFCoreTriggerStore(EntityStore<RuntimeElsaDbContext, StoredTrigger> store)
{
_store = store;
}

View file

@ -6,13 +6,24 @@ using Elsa.Workflows.Runtime.Services;
namespace Elsa.EntityFrameworkCore.Modules.Runtime;
/// <inheritdoc />
public class EFCoreWorkflowExecutionLogStore : IWorkflowExecutionLogStore
{
private readonly Store<RuntimeElsaDbContext, WorkflowExecutionLogRecord> _store;
public EFCoreWorkflowExecutionLogStore(Store<RuntimeElsaDbContext, WorkflowExecutionLogRecord> store) => _store = store;
private readonly EntityStore<RuntimeElsaDbContext, WorkflowExecutionLogRecord> _store;
/// <summary>
/// Constructor
/// </summary>
public EFCoreWorkflowExecutionLogStore(EntityStore<RuntimeElsaDbContext, WorkflowExecutionLogRecord> store) => _store = store;
/// <inheritdoc />
public async Task SaveAsync(WorkflowExecutionLogRecord record, CancellationToken cancellationToken = default) => await _store.SaveAsync(record, cancellationToken);
/// <inheritdoc />
public async Task SaveManyAsync(IEnumerable<WorkflowExecutionLogRecord> records, CancellationToken cancellationToken = default) => await _store.SaveManyAsync(records, cancellationToken);
/// <inheritdoc />
public async Task<Page<WorkflowExecutionLogRecord>> FindManyByWorkflowInstanceIdAsync(string workflowInstanceId, PageArgs? pageArgs = default, CancellationToken cancellationToken = default)
{
var records = await _store.FindManyAsync(

View file

@ -17,13 +17,13 @@ public class EFCoreWorkflowStateStore : IWorkflowStateStore
private readonly SerializerOptionsProvider _serializerOptionsProvider;
private readonly ISystemClock _systemClock;
private readonly IDbContextFactory<RuntimeElsaDbContext> _dbContextFactory;
private readonly Store<RuntimeElsaDbContext, WorkflowState> _store;
private readonly EntityStore<RuntimeElsaDbContext, WorkflowState> _store;
/// <summary>
/// Constructor.
/// </summary>
public EFCoreWorkflowStateStore(
Store<RuntimeElsaDbContext, WorkflowState> store,
EntityStore<RuntimeElsaDbContext, WorkflowState> store,
IDbContextFactory<RuntimeElsaDbContext> dbContextFactory,
SerializerOptionsProvider serializerOptionsProvider,
ISystemClock systemClock)

View file

@ -9,6 +9,7 @@ using Elsa.Workflows.Management.Materializers;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Management.Services;
using JetBrains.Annotations;
using Medallion.Threading;
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Post;
@ -18,15 +19,18 @@ internal class Post : ElsaEndpoint<WorkflowDefinitionRequest, WorkflowDefinition
private readonly SerializerOptionsProvider _serializerOptionsProvider;
private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher;
private readonly VariableDefinitionMapper _variableDefinitionMapper;
private readonly IDistributedLockProvider _distributedLockProvider;
public Post(
SerializerOptionsProvider serializerOptionsProvider,
IWorkflowDefinitionPublisher workflowDefinitionPublisher,
VariableDefinitionMapper variableDefinitionMapper)
VariableDefinitionMapper variableDefinitionMapper,
IDistributedLockProvider distributedLockProvider)
{
_serializerOptionsProvider = serializerOptionsProvider;
_workflowDefinitionPublisher = workflowDefinitionPublisher;
_variableDefinitionMapper = variableDefinitionMapper;
_distributedLockProvider = distributedLockProvider;
}
public override void Configure()
@ -38,7 +42,10 @@ internal class Post : ElsaEndpoint<WorkflowDefinitionRequest, WorkflowDefinition
public override async Task HandleAsync(WorkflowDefinitionRequest request, CancellationToken cancellationToken)
{
var definitionId = request.DefinitionId;
var resourceName = $"{GetType().FullName}:{(!string.IsNullOrWhiteSpace(definitionId) ? definitionId : Guid.NewGuid().ToString())}";
await using var handle = await _distributedLockProvider.AcquireLockAsync(resourceName, TimeSpan.FromMinutes(1), cancellationToken);
// Get a workflow draft version.
var draftVersion = request.Version != null ? VersionOptions.SpecificVersion(request.Version.Value) : VersionOptions.Latest;