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.HttpStartClientThis is the main http client which can be requested to start the main orchestration functionFunctionApp.Clients.HttpAttemptCounterEntityClientThis is a http client function which can be used to get the latest attempt data (AttemptsEntityState)FunctionApp.Orchestrators.MainOrchestratorThe main orchestration function which carries out the requests and retries to the API and waits for the external system call-back.FunctionApp.Activities.CallApiActivitythe activity function which makes the actual calls to the Api.FunctionApp.Entities.AttemptsEntitythe 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
RetryTimeoutCallbacksolution and chooseSet Startup porjects - Choose
Multiple startup projects - Set both
FunctionAppandWebApito "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.execonsole, copy the URL forHttpStartClient. It is typically something like "http://localhost:7071/api/HttpStartClient" - Make a new
POSTrequest 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
statusQueryGetUriproperty 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
idproperty from the response body in step 2. - From the
func.execonsole, copy the URL forHttpAttemptCounterEntityClient. It is typically something like "http://localhost:7071/api/HttpAttemptCounterEntityClient" - Make a new
GETrequest in Postman to the URL obtained in step 2 (no body required) but append?instanceid={Id}whereidis 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
overallStateis "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
idproperty from the response body in step 2. - Copy the URL for
sendEventPostUriproperty 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
POSTrequest 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/Acceptedresponse - 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 aruntimeStatusof "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"