The goal of this lab is to implement the activities which call out to existing XASA services and call these from the orchestrator.
The first activity function needs to make a call to an HTTP endpoint to retrieve the estimated kinetic energy expressed in megaton TNT.
An HTTP POST need to be done to https://demo-neo.azure-api.net/neo/estimate/energy
with a payload of the DetectedNEOEvent
object in the body. A KineticEnergyResult
object will be returned.
Add a new class, EstimateKineticEnergyActivity
, and add the following method definition which makes it an activity function:
[FunctionName(nameof(EstimateKineticEnergyActivity))]
public async Task<KineticEnergyResult> Run(
[ActivityTrigger] DetectedNeoEvent neoEvent,
ILogger logger)
{
}
In order to make HTTP calls we require a HttpClient
object. The 'old way' of doing this was to use a static private HttpClient
field. However, since Azure Functions now supports dependency injection we can inject an IHttpClientFactory
into this class and the factory manages the lifetime of the HttpClient
.
- Install the
Microsoft.Extensions.Http
NuGet package. This contains theIHttpClientFactory
interface. - Add a constructor to the
EstimateKineticEnergyActivity
class and injectIHttpClientFactory
. - Add a private readonly field of type
HttpClient
to the class. - Set this field in the constructor by using the
CreateClient()
method of theIHttpClientFactory
.
private readonly HttpClient client;
public EstimateKineticEnergyActivity(IHttpClientFactory httpClientFactory)
{
client = httpClientFactory.CreateClient();
}
Now the client can be used in the function method to do a PostAsJsonAsync()
call to the EstimateKineticEnergy endpoint. Note that we also need to pass in the ApiManagementKey in the Ocp-Apim-Subscription-Key
header.
When we do the post to the endpoint, make sure to verify on a success status code on the response. If the response is not successful an exception should be thrown from the activity.
The implementation of the function should be something like this:
var kineticEnergyEndpoint = new Uri(Environment.GetEnvironmentVariable("KineticEnergyEndpoint"));
var apiManagementKey = Environment.GetEnvironmentVariable("ApiManagementKey");
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiManagementKey);
var response = await client.PostAsJsonAsync(kineticEnergyEndpoint, neoEvent);
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new FunctionFailedException(content);
}
var result = await response.Content.ReadAsAsync<KineticEnergyResult>();
return result;
Make sure that the KineticEnergyEndpoint
(https://demo-neo.azure-api.net/neo/estimate/energy
) and the ApiManagementKey
(in the json object from Lab 3) values are placed in the local.settings.json file as application settings.
Let's return to the NeoEventProcessingOrchestrator
class and call the EstimateKineticEnergyActivity
function.
The basic syntax for calling an activity with a return type is:
var kineticEnergy = await context.CallActivityAsync<KineticEnergyResult>(
nameof(EstimateKineticEnergyActivity),
detectedNeoEvent);
If we'd run the Function App now, we'll get exceptions (like the one below) since the IHttpClientFactory
dependency has not been registered yet.
Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '<NAME_OF_FUNCTION>'.
In order to use dependency injection in Azure Functions we need to go through the following steps:
- Add a reference to the
Microsoft.Azure.Functions.Extensions
NuGet package. - Add a new class (e.g Startup.cs) to the Function App and inherit from
FunctionsStartup
. - Add the
FunctionsStartup
attribute on the assembly level and provide the type of the Startup class as the argument. - Implement the
Configure
method and use theAddHttpClient()
extension method on thebuilder.Services
.
The result should look like this:
[assembly: FunctionsStartup(typeof(Startup))]
namespace Demo.NEO.EventProcessing.Application
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
}
}
}
If we run the Function App for a while we'll notice (in the Azure Functions runtime output) that the activity function fails in some occasions. Why is that?
A good solution to cope with failing activity functions in this case is to retry the activity. The Durable Functions API already has built-in support for this using the CallActivityWithRetryAsync
method:
var kineticEnergy = await context.CallActivityWithRetryAsync<KineticEnergyResult>(
nameof(EstimateKineticEnergyActivity),
new RetryOptions(TimeSpan.FromSeconds(10), 5),
detectedNeoEvent);
Notice the different method name and the
RetryOptions
argument. Go to the definition of theRetryOptions
type to see which properties are available.
Now update the orchestrator function with the CallActivityWithRetryAsync
method and run the Function App.
Repeat the same substeps as in Step 1 (excl the Startup class) but now name the function EstimateImpactProbabilityActivity
and do a post to the https://demo-neo.azure-api.net/neo/estimate/probability
endpoint. This also uses a DetectedNeoEvent
as the body and uses the same Ocp-Apim-Subscription-Key
header. An ImpactProbabilityResult
object will be returned.
Repeat the same substeps as in Step 1 (excl the Startup class) but now but now name the function EstimateTorinoImpactActivity
and do a post to the https://demo-neo.azure-api.net/neo/estimate/torino
endpoint. This requires a TorinoImpactRequest
object as the body. The request can be built by combining the KineticEnergyResult
and ImpactProbabilityResult
objects.
The TorinoImpact endpoint returns an TorinoImpactResult
. Again the same Ocp-Apim-Subscription-Key
header is used for authentication.
The users of your application are interested in ProcessedNeoEvent
objects. These are objects built up of DetectedNeoEvents and the results from the three service calls we just implemented.
Once all three activities have been implemented create a new instance of the ProcessedNeoEvent
and for now return this from the orchestrator.
Now run/debug your local Function App by using the HttpTrigger client function.
Continue to the next lab to create the activity to store the processed data.