Orchestrating workflows in c# with Durable Task Framework
About Me

Blog: http://blog.codenova.pl/
Twitter: @Kamil_Mrzyglod
GitHub: https://github.com/kamil-mrzyglod
StackOverflow: https://stackoverflow.com/users/1874991/kamo
LinkedIn: www.linkedin.com/in/kamil-mrzygłód-31470376
Scenario


Account 1
Account 2


Database 1
Database 2
Distributed Transaction
Scenario


Debit amount from Account A
Successful?
Credit amount to Account B
Successful?
Yes
No
Yes
No
Undo debit from Account A
Durable Task Framework
Core Concepts

Durable Task Framework
Service Bus

Durable Task Framework
How does it work?
Task Hub Worker
Task Orchestrations
Task Activity
Task Activity

Task Hub Client
Task Hub
Task Activity
Durable Task Framework
Orchestration example
public class EncodeVideoOrchestration : TaskOrchestration<string, string>
{
public override async Task<string> RunTask(OrchestrationContext context,
string input)
{
string encodedUrl =
await context.ScheduleTask<string>(typeof (EncodeActivity), input);
await context.ScheduleTask<object>(typeof (EmailActivity), input);
return encodedUrl;
}
}Orchestrations are single-threaded!
Durable Task Framework
Activities example
public class EncodeActivity : TaskActivity<string, string>
{
protected override string Execute(TaskContext context, string input)
{
Console.WriteLine("Encoding video " + input);
// TODO : actually encode the video to a destination
return "http://<azurebloblocation>/encoded_video.avi";
}
}
public class EmailActivity : TaskActivity<string, object>
{
protected override object Execute(TaskContext context, string input)
{
// TODO : actually send email to user
return null;
}
}Guaranteed to called at-least-once!
Durable Task Framework
Task hub worker & Client example
string serviceBusConnString
= "Endpoint=sb://<namespace>.servicebus.windows.net/;SharedSecretIssuer=[issuer];SharedSecretValue=[value]";
TaskHubWorker hubWorker = new TaskHubWorker("myvideohub", serviceBusConnString)
.AddTaskOrchestrations(typeof (EncodeVideoOrchestration))
.AddTaskActivities(typeof (EncodeActivity), typeof (EmailActivity))
.Start();string serviceBusConnString
= "Endpoint=sb://<namespace>.servicebus.windows.net/;SharedSecretIssuer=[issuer];SharedSecretValue=[value]";
TaskHubClient client = new TaskHubClient("myvideohub", serviceBusConnString);
client.CreateOrchestrationInstance(typeof (EncodeVideoOrchestration), "http://<azurebloblocation>/MyVideo.mpg");Orchestrations
Constraints
Task Orchestration
Activity 1
Activity 2
Activity 3
Replay
Replay
Replay
Execution History
Orchestrations
Error handling
public class DebitCreditOrchestration :
TaskOrchestration<object, DebitCreditOperation>
{
public override async Task<object> RunTask(OrchestrationContext context,
DebitCreditOperation operation)
{
bool failed = false;
bool debited = false;
try
{
await context.ScheduleTask<object>(typeof (DebitAccount),
new Tuple<string, float>(operation.SourceAccount, operation.Amount));
debited = true;
await context.ScheduleTask<object>(typeof(CreditAccount),
new Tuple<string, float>(operation.TargetAccount, operation.Amount));
}
catch (TaskFailedException exception)
{
failed = true;
}
if (failed)
{
if (debited)
{
// can build a try-catch around this as well, in which case the
// orchestration may either retry a few times or log the inconsistency for review
await context.ScheduleTask<object>(typeof(CreditAccount),
new Tuple<string, float>(operation.SourceAccount, operation.Amount));
}
}
return null;
}
}Orchestrations
Automatic retries
public class GetQuoteOrchestration : TaskOrchestration<string, string>
{
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
// retry every 10 seconds upto 5 times before giving up and bubbling up the exception
RetryOptions retryOptions = new RetryOptions(TimeSpan.FromSeconds(10), 5);
await context.ScheduleWithRetry<object>(typeof (GetQuote), retryOptions, null);
return null;
}
}Orchestrations
durable timers
public class EncodeVideoOrchestration : TaskOrchestration<string, string>
{
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
string encodedUrl = await context.ScheduleTask<string>(typeof (EncodeActivity), input);
await context.CreateTimer(context.CurrentUtcDateTime.Add(TimeSpan.FromDays(1)), "timer1");
await context.ScheduleTask<object>(typeof (EmailActivity), input);
return encodedUrl;
}
}public class BillingOrchestration : TaskOrchestration<string, string>
{
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
for (int i = 0; i < 10; i++)
{
await context.CreateTimer(context.CurrentUtcDateTime.Add(TimeSpan.FromDays(1)), "timer1");
await context.ScheduleTask<object>(typeof (BillingActivity));
}
return null;
}
}Orchestrations
durable timers
public class GetQuoteOrchestration : TaskOrchestration<string, string>
{
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
Task timer = context.CreateTimer(
context.CurrentUtcDateTime.Add(TimeSpan.FromSeconds(5)), "timer1");
Task getQuote = context.ScheduleTask<object>(typeof(GetQuote));
Task winner = Task.WhenAny(timer, getQuote);
if (timer.IsCompleted)
{
// request timed out, do some compensating action
}
else
{
// use getQuote task result
}
return null;
}
}Orchestrations
eternal orchestrations
public class CronOrchestration : TaskOrchestration<string, int>
{
public override async Task<string> RunTask(OrchestrationContext context, int intervalHours)
{
// bounded loop
for (int i = 0; i < 10; i++)
{
await context.CreateTimer<object>(
context.CurrentUtcDateTime.Add(TimeSpan.FromHours(intervalHours)), null);
// TODO : do something interesting
}
// create a new instance of self with the same input (or different if needed)
context.ContinueAsNew(intervalHours);
return null;
}
}Orchestrations
external events
public class GetQuoteOrchestration : TaskOrchestration<string, string>
{
TaskCompletionSource<object> getPermission = new TaskCompletionSource<object>();
public override async Task<string> RunTask(OrchestrationContext context, string input)
{
await getPermission.Task;
await context.ScheduleTask<object>(typeof (GetQuote), null);
return null;
}
public override void OnEvent(OrchestrationContext context, string name, string input)
{
getPermission.SetResult(null);
}
}TaskHubClient client = new TaskHubClient("test", serviceBusConnString);
OrchestrationInstance instance = client.CreateOrchestrationInstance(typeof (EncodeVideoOrchestration),
"http://<azurebloblocation>/MyVideo.mpg");
client.RaiseEvent(instance, "dummyEvent", "dummyData");Durable Functions
example
public static async Task<List<string>> Run(DurableOrchestrationContext context)
{
var outputs = new List<string>();
outputs.Add(await context.CallFunctionAsync<string>("E1_SayHello", "Tokyo"));
outputs.Add(await context.CallFunctionAsync<string>("E1_SayHello", "Seattle"));
outputs.Add(await context.CallFunctionAsync<string>("E1_SayHello", "London"));
// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}Questions?
deck
By kamil_mrzyglod
deck
- 759