Last active
January 21, 2022 12:31
-
-
Save olitomlinson/a2f2210490c5ed39019cf56993b80a18 to your computer and use it in GitHub Desktop.
Saga abstraction for Durable Functions, which favours composition rather than inheritance. My initial thoughts on this approach is that the series of compensations may be straightforward, or may be complex. We can provide out the box compensation such as "apply compensations in reverse order", "in actual order", or even "compensate in parallel" …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Threading.Tasks; | |
using Microsoft.Azure.WebJobs; | |
using Microsoft.Azure.WebJobs.Extensions.DurableTask; | |
namespace SagaExample | |
{ | |
public static class DoSaga | |
{ | |
[FunctionName("RunSagaOrchestration")] | |
public static async Task<List<string>> RunSagaOrchestration( | |
[OrchestrationTrigger] IDurableOrchestrationContext context) | |
{ | |
var log = new List<string>(); | |
var saga = new Saga(context, log); | |
saga.OnCompensationComplete(async (log) => | |
{ | |
/* Send "we're sorry, but.." email to customer... */ | |
log.Add("Compensation successfull..."); | |
}); | |
saga.OnCompensationError(async (log) => | |
{ | |
/* Send emails to internal supporting teams */ | |
log.Add("Compensation unsuccessful... Manual intervention required"); | |
}); | |
try | |
{ | |
saga.AddCompensation((context) => context.CallActivityAsync("Activity_A_compensation", null)); | |
await context.CallActivityAsync("Activity_A", null); | |
saga.AddCompensation((context) => context.CallActivityAsync("Activity_B_compensation", null)); | |
await context.CallActivityAsync("Activity_B", null); | |
saga.AddCompensation((context) => context.CallActivityAsync("Activity_C_compensation", null)); | |
await context.CallActivityAsync("Activity_C", null); | |
} | |
catch | |
{ | |
await saga.CompensateAsync(); | |
} | |
return log; | |
} | |
} | |
public class Saga | |
{ | |
private IDurableOrchestrationContext _context; | |
private List<string> _log; | |
private Stack<Func<IDurableOrchestrationContext, Task>> _compensations; | |
private Func<List<string>,Task> _onCompensationError; | |
private Func<List<string>,Task> _onCompensationComplete; | |
public Saga(IDurableOrchestrationContext context, List<string> _log) | |
{ | |
_context = context; | |
_log ??= new List<string>(); | |
_compensations = new Stack<Func<IDurableOrchestrationContext, Task>>(); | |
} | |
public void OnCompensationError(Func<List<string>, Task> onCompensationError) | |
{ | |
_onCompensationError = onCompensationError; | |
} | |
public void OnCompensationComplete(Func<List<string>, Task> onCompensationComplete) | |
{ | |
_onCompensationComplete = onCompensationComplete; | |
} | |
public void AddCompensation(Func<IDurableOrchestrationContext, Task> compensation) | |
{ | |
_compensations.Push(compensation); | |
} | |
public async Task CompensateAsync() | |
{ | |
while (_compensations.Count > 0) | |
{ | |
var c = _compensations.Pop(); | |
try | |
{ | |
_log.Add("Attempting compensation..."); | |
await c.Invoke(_context); | |
_log.Add("Compensation successfull..."); | |
} | |
catch | |
{ | |
/* log details of all other compensations that have not yet been made if this is a show-stopper */ | |
await _onCompensationError(_log); | |
return; | |
} | |
} | |
await _onCompensationComplete(_log); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment