A Durable Function example showing how to call external services with a call-back, retry and timeout.
The sample uses a Durable Entity to track the status and output of attempts to call and external API.
- Domain: Domain porject which contains models that are shared across other projects.
- FunctionApp: This is a Durable Function project which contains the main orchestration function, its clients, activities and entities. The functions included are as follows:
FunctionApp.Clients.HttpStartClient
This is the main http client which can be requested to start the main orchestration functionFunctionApp.Clients.HttpAttemptCounterEntityClient
This is a http client function which can be used to get the latest attempt data (AttemptsEntityState
)FunctionApp.Orchestrators.MainOrchestrator
The main orchestration function which carries out the requests and retries to the API and waits for the external system call-back.FunctionApp.Activities.CallApiActivity
the activity function which makes the actual calls to the Api.FunctionApp.Entities.AttemptsEntity
the entity function which stores data abojut attempts to call the Api.
- WebApi: This is a web api project which contains a very simple API which the function will attempt to call and re-attempt based on the settings. This API represents an external system which the function needs to call.
You will need Visual Studio and Postman (or alternatives) to run the solution.
The sample is designed to be run in Visual Studio 2022 (Visual Studio 2019 has almost identical steps) and PostMan. If you are using an alternative editor or API tool, you may need to adapt some of these steps for your toolset.
To run the sample you will need to run both the FunctionApp
and WebApi
concurrently, to configure this:
- Right-click on the
RetryTimeoutCallback
solution and chooseSet Startup porjects
- Choose
Multiple startup projects
- Set both
FunctionApp
andWebApi
to "Start" - Start the solution
When the solution runs, you will see a func.exe
console which will list out all the function in the solution. We will make a request to the HttpStartClient
to start the main orchestration.
- From the
func.exe
console, copy the URL forHttpStartClient
. It is typically something like "http://localhost:7071/api/HttpStartClient" - Make a new
POST
request in Postman to the URL obtained in step 1 (no body required) - To confirm that the function is running, make a new request to the URL in the
statusQueryGetUri
property in the response body in step 2
You can observe the attempts to the API via the HttpAttemptCounterEntityClient function.
If you leave the function to run without intervention it will make 5 attempts, all of which will end with the state of "TimedOut" after 60 seconds. Each attempt status will go through the following sequential states if you do not make the call back manually:
- New
- Executing
- ExecutedSuccess
- WaitingForCallback
- TimedOut
You can repeat the request in step 3 multiple times and see the attempts data expand over time.
- Follow the steps in "Invoke the Orchestration" and make a copy of the
id
property from the response body in step 2. - From the
func.exe
console, copy the URL forHttpAttemptCounterEntityClient
. It is typically something like "http://localhost:7071/api/HttpAttemptCounterEntityClient" - Make a new
GET
request in Postman to the URL obtained in step 2 (no body required) but append?instanceid={Id}
whereid
is the value you obtained in step 1. The full request url will be something like "http://localhost:7071/api/HttpAttemptCounterEntityClient?instanceid=13a990ff1f914a13995a11ae1ff1fc3c" - Repeat step 3 until the
overallState
is "Completed API request after 5 attempts. Final state TimedOut, status text: Event Callback not received in 00:01:00"
The system is designed to expect a call-back from an external system. In this case, the external system is Postman, but this could be an external system like Databricks, Logic Apps etc.
To make the call back and allow the function to complete with success, follow these steps:
- Follow the steps in "Invoke the Orchestration" and make a copy of the
id
property from the response body in step 2. - Copy the URL for
sendEventPostUri
property from the response body. It is typically something like "http://localhost:7071/runtime/webhooks/durabletask/instances/3f733347b1cd44d1af699e8993a68b7f/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=MaHpwEx2o6sEZKMoDAG8dfWihFNm7Pa1DxdcNuQRlXr7PivLT/9rlA==" - Replace
{eventname}
withCallback
- Make a
POST
request to the url you created in step 3. The body should be a raw json body with just the word "true" (no json structure). You should get a202/Accepted
response - If you make a request to
HttpAttemptCounterEntityClient
(see step 3 of Observing Attempts) you should see that a single attempt was made which resulted in "CallbackSuccess" - If you make a request to
statusQueryGetUri
(see step 3 of Invoke the Orchestration) you should see that the orchestration function completed with aruntimeStatus
of "Completed" and an output hat reads something like "Completed API request after 1 attempts. Final state CallbackSuccess, status text: External system called back with CallbackSuccess"