In the previous sections, you learned about Durable Functions and the types of functions you can use when you create a solution using this technology.
In this section, you will see how Durable Functions manage the state and can orchestrate the activity functions (the execution state).
When you talk about the execution state, you keep in mind three pillars:
- Event sourcing
- Checkpoint
- Replay
The orchestrator functions manage their state using the event sourcing design pattern.
This pattern is based on registering all the events a single Durable Function receives. In this way, every moment a single instance of a Durable Function can rebuild its actual state simply replays the whole set of events received.
Every time a Durable Function calls an activity or an activity completes its work, the Durable Task Framework (transparently) saves the event inside a storage table (called the execution history). This operation is an append operation inside the execution history, and it's a much faster and more efficient than saving the entire state. When you save the state of something, in fact, you must do an update on a record in a table instead of a simple append using the Durable Task Framework.
The execution history also contains the payloads used by the orchestrator and the single activity. These payloads are stored in JSON format, and they are the reason why you must use only serializable objects between the orchestrator and activity.
When a Durable Function needs to proceed with its job, the Durable Task Framework replays all the events received and checks whether that event was completed and executed or not (for example, if an activity was called and it returned the result). In this way, the framework reconstructs the state of the orchestrator every time before continuing its job.
The Durable Task Framework saves the execution history in a storage table in a logical container called a Durable Functions task hub.
You can share the same task hub between different Durable Functions or use a single task hub for each function.
You can define which task hub your function must use by simply modifying the host.json file:
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "MyTaskHub"
}
}
}
If you like, the hub name can be retrieved from the app settings using the following format:
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "%MyTaskHub%"
}
}
}
Another way is to retrieve the hub name directly in the OrchestrationClient attribute:
[FunctionName("HttpStart")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, methods: "post")] HttpRequestMessage req,
[OrchestrationClient(TaskHub = "%MyTaskHub%")] DurableOrchestrationClientBase starter,
string functionName,
ILogger log)
{
// Function code
}
By default, the task name is DurableFunctionsHub, and the history table is DurableFunctionsHubHistory:
If you look at the storage account that store the history table, you will find another table called DurableFunctionsHubInstances that contains the list of the orchestrator instances executed or in execution in your solution. Here you can find the state of the single instance without the need to reconstruct the state from the event sourcing storage:
The status of a Durable Function is reconstructed, therefore, directly from the list of events that it has received, and these events are closely related to the code of the orchestration function. What could happen if the code changes? In the next section, we will see the problems that may arise if the code changes and how to manage the versioning.