Nexus Async Caller SchemaVersion 0
The configuration for Nexus AsyncCaller has implicit SchemaVersion 0
, where there is no support for multiple queues and the queue name is explicitly given.
Components
To get Async Caller going you need
- An Azure Storage account
- An Azure Function App
- Nexus configuration
Flow
In version 1 of Async Caller, the chain of requests looks like this:
- An adapter or other service provider uses the business api that exposes the async functionality
- The sending system (Nexus Business Events or Business API) asks Nexus Async Caller to handle an asynchronous request
- Async Caller puts the request on a storage queue in the customer's Azure environment
- A function in the customer's Azure environment is triggered and asks Async Caller to actually make the request
- Async Caller makes the request to the receiving system
Nexus configuration
Use this configuration variant:
{
"ConnectionString": "...",
"QueueName": "..."
}
or with explicit SchemaVersion
:
{
"SchemaVersion": 0,
"ConnectionString": "...",
"QueueName": "..."
}
See Configure Async Caller on how to setup Async Caller in the customer's environment.
Azure function app
Create an Azure Function App which has an "AsyncCaller" function in it, listening to the QueueName
in the AC settings. It could look something like
public static class AsyncCaller
{
[FunctionName("AsyncCaller")]
public static async Task Run([QueueTrigger("async-request-queue", Connection = "AzureWebJobsStorage")] string queueItem, ILogger log, ExecutionContext context)
{
await AsyncCallsClient.HandleDistribute(queueItem, log, context);
}
}
The AsyncCallsClient
will contain the code for accessing Async Caller; which could look something like this (with logging stripped away and support for multi-tenant):
using Nexus.Link.Authentication.Sdk;
using Nexus.Link.Libraries.Core.Application;
using Nexus.Link.Libraries.Core.MultiTenant.Model;
using Nexus.Link.Libraries.Core.Platform.Authentication;
//...
public class AsyncCallsClient
{
private static readonly HttpClient HttpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(120) };
public static async Task HandleDistribute(string queueItem, ILogger log, ExecutionContext context)
{
try
{
var asyncCallerRequest = JsonConvert.DeserializeObject<AsyncCallsRequest>(queueItem);
var environment = asyncCallerRequest.Environment;
var organization = asyncCallerRequest.Organization;
var relativeUrl = $"api/v1/{organization}/{environment}/AsyncCalls/Distribute";
var postUrl = $"{ConfigurationHelper.GetSetting("AsyncCaller.PostUrl", context)}/{relativeUrl}";
var request = new HttpRequestMessage(HttpMethod.Post, postUrl)
{
Content = new StringContent(myQueueItem, System.Text.Encoding.UTF8, "application/json")
};
await AuthorizationHelper.AddAuthorization(request, log, context, organization, environment, "asyncCalls");
var response = await HttpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var error = $"Bad response from Dispatcher. PostUrl: {postUrl}" +
" | ResponseCode: {response.StatusCode} | Content: {content}" +
" | Message {queueItem}";
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
{
error += $" | Token: {request.Headers.Authorization}";
}
log.LogError(error);
throw new Exception(error);
}
}
catch (Exception e)
{
// Throwing error will put back item on queue. There will be 5 tries, then put on poison queue.
log.LogError($"Error: {e.Message} {e.InnerException?.Message}");
throw;
}
}
}
where AuthorizationHelper
may help out with
using Nexus.Link.Authentication.Sdk;
using Nexus.Link.Libraries.Core.Application;
using Nexus.Link.Libraries.Core.MultiTenant.Model;
using Nexus.Link.Libraries.Core.Platform.Authentication;
using FulcrumApplicationHelper = Nexus.Link.Libraries.Web.AspNet.Application.FulcrumApplicationHelper;
// ...
public class AuthorizationHelper
{
private static readonly MemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions());
private const int CacheHours = 24;
public static async Task AddAuthorization(HttpRequestMessage request, ILogger log, ExecutionContext context,
string organization, string environment, string function)
{
var cacheKey = $"{function}|token|{organization}|{environment}";
var token = MemoryCache.Get<AuthenticationToken>(cacheKey);
if (token == null)
{
var tenant = new Tenant(organization, environment);
FulcrumApplicationHelper.WebBasicSetup($"function-apps-{environment}", tenant, RunTimeLevelEnum.Production);
var clientId = ConfigurationHelper.GetSetting($"Authentication.ClientId.{environment}", context) ??
ConfigurationHelper.GetSetting($"Authentication.ClientId", context);
var clientSecret = ConfigurationHelper.GetSetting($"Authentication.ClientSecret.{environment}", context) ??
ConfigurationHelper.GetSetting($"Authentication.ClientSecret", context);
var tokenCredentials = new AuthenticationCredentials
{
ClientId = clientId,
ClientSecret = clientSecret
};
var authServiceUrl = ConfigurationHelper.GetSetting($"Authentication.Url", context);
var authenticationManager = new NexusAuthenticationManager(tenant, authServiceUrl);
token = await authenticationManager
.GetJwtTokenAsync(tokenCredentials, TimeSpan.FromHours(1), TimeSpan.FromHours(CacheHours));
MemoryCache.Set(cacheKey, token, DateTimeOffset.Now.AddMinutes(CacheHours * 60 - 30));
log.LogInformation($"Fetched a new token with cache key '{cacheKey}'");
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
}
}