Skip to content

Commit

Permalink
feat: Support refreshable properties (#56)
Browse files Browse the repository at this point in the history
* Refreshable properties

* Remove CRON for electrical heating

* Allow resetting RecurringCronExpression
  • Loading branch information
dstenroejl authored Dec 20, 2024
1 parent fea8bd6 commit 1930077
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,27 @@ internal interface IOrchestrationRegister
Task<IReadOnlyCollection<OrchestrationDescription>> GetAllByHostNameAsync(string hostName);

/// <summary>
/// Durable Functions orchestration host's can use this method to register the orchestrations
/// Determine if <paramref name="hostDescription"/> is unknown to the register and needs to be registered;
/// or if it was previously disabled and needs to be enabled;
/// or if any refreshable property has changed.
/// </summary>
/// <param name="registerDescription">Orchestration description as described in the register.</param>
/// <param name="hostDescription">Orchestration description as described by the application host.</param>
/// <returns><see langword="true"/> if the orchestration description should be registered or updated; otherwise <see langword="false"/>.</returns>
bool ShouldRegisterOrUpdate(OrchestrationDescription? registerDescription, OrchestrationDescription hostDescription);

/// <summary>
/// Durable Functions orchestration host's can use this method to register or update the orchestrations
/// they host.
/// </summary>
Task RegisterAsync(OrchestrationDescription orchestrationDescription, string hostName);
/// <param name="hostDescription">Orchestration description as described by the application host.</param>
/// <param name="hostName">Name of the application host.</param>
Task RegisterOrUpdateAsync(OrchestrationDescription hostDescription, string hostName);

/// <summary>
/// Durable Functions orchestration host's can use this method to disable orchestrations they don't host anymore
/// or want to disable for other reasons.
/// </summary>
Task DeregisterAsync(OrchestrationDescription orchestrationDescription);
/// <param name="registerDescription">Orchestration description as described in the register.</param>
Task DeregisterAsync(OrchestrationDescription registerDescription);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public static async Task SynchronizeAsync(
.SingleOrDefault(x =>
x.UniqueName == hostDescription.UniqueName);

if (registerDescription == null || registerDescription.IsEnabled == false)
await register.RegisterAsync(hostDescription, hostName).ConfigureAwait(false);
if (register.ShouldRegisterOrUpdate(registerDescription, hostDescription))
await register.RegisterOrUpdateAsync(hostDescription, hostName).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public string RecurringCronExpression
{
if (value != _recurringCronExpression)
{
if (CrontabSchedule.TryParse(value) == null)
if (value != string.Empty && CrontabSchedule.TryParse(value) == null)
throw new ArgumentOutOfRangeException($"Invalid cron value '{value}'. See https://github.com/atifaziz/NCrontab/wiki/Crontab-Expression for expected format.");

_recurringCronExpression = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,38 +61,66 @@ public async Task<IReadOnlyCollection<OrchestrationDescription>> GetAllByHostNam
}

/// <inheritdoc />
public async Task RegisterAsync(OrchestrationDescription orchestrationDescription, string hostName)
public bool ShouldRegisterOrUpdate(OrchestrationDescription? registerDescription, OrchestrationDescription hostDescription)
{
ArgumentNullException.ThrowIfNull(orchestrationDescription);
return
registerDescription == null
|| registerDescription.IsEnabled == false
|| AnyRefreshablePropertyHasChanged(registerDescription, hostDescription);
}

/// <inheritdoc />
public async Task RegisterOrUpdateAsync(OrchestrationDescription hostDescription, string hostName)
{
ArgumentNullException.ThrowIfNull(hostDescription);
ArgumentException.ThrowIfNullOrWhiteSpace(hostName);

var existing = await GetOrDefaultAsync(orchestrationDescription.UniqueName, isEnabled: null).ConfigureAwait(false);
if (existing == null)
var existingDescription = await GetOrDefaultAsync(hostDescription.UniqueName, isEnabled: null).ConfigureAwait(false);
if (existingDescription == null)
{
// Enfore certain values
orchestrationDescription.HostName = hostName;
orchestrationDescription.IsEnabled = true;
_context.Add(orchestrationDescription);
// Enforce certain values
hostDescription.HostName = hostName;
hostDescription.IsEnabled = true;
_context.Add(hostDescription);
}
else
{
existing.IsEnabled = true;
existingDescription.IsEnabled = true;
UpdateRefreshableProperties(existingDescription, hostDescription);
}

await _context.SaveChangesAsync().ConfigureAwait(false);
}

/// <inheritdoc />
public async Task DeregisterAsync(OrchestrationDescription orchestrationDescription)
public async Task DeregisterAsync(OrchestrationDescription registerDescription)
{
ArgumentNullException.ThrowIfNull(orchestrationDescription);
ArgumentNullException.ThrowIfNull(registerDescription);

var existing = await GetOrDefaultAsync(orchestrationDescription.UniqueName, isEnabled: true).ConfigureAwait(false);
if (existing == null)
var existingDescription = await GetOrDefaultAsync(registerDescription.UniqueName, isEnabled: true).ConfigureAwait(false);
if (existingDescription == null)
throw new InvalidOperationException("Orchestration description has not been registered or is not currently enabled.");

existing.IsEnabled = false;
existingDescription.IsEnabled = false;

await _context.SaveChangesAsync().ConfigureAwait(false);
}

private static bool AnyRefreshablePropertyHasChanged(
OrchestrationDescription registerDescription,
OrchestrationDescription hostDescription)
{
return registerDescription.RecurringCronExpression != hostDescription.RecurringCronExpression;
}

/// <summary>
/// Properties that can change the behaviour of the orchestation history should not be allowed to
/// change without bumping the version of the orchestration description.
/// </summary>
private static void UpdateRefreshableProperties(
OrchestrationDescription registerDescription,
OrchestrationDescription hostDescription)
{
registerDescription.RecurringCronExpression = hostDescription.RecurringCronExpression;
}
}
6 changes: 4 additions & 2 deletions source/ProcessManager.Orchestrations/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ OrchestrationDescription CreateDescription_Brs_021_ElectricalHeatingCalculation_
canBeScheduled: true,
functionName: nameof(Orchestration_Brs_021_ElectricalHeatingCalculation_V1));

// Runs at 12:00 and 17:00 every day
description.RecurringCronExpression = "0 12,17 * * *";
// DISABLED for now because ElectricalHeatingJob is currently failing
description.RecurringCronExpression = string.Empty;
////// Runs at 12:00 and 17:00 every day
////description.RecurringCronExpression = "0 12,17 * * *";

foreach (var step in Orchestration_Brs_021_ElectricalHeatingCalculation_V1.Steps)
{
Expand Down

0 comments on commit 1930077

Please sign in to comment.