Итак, я создаю прототип некоторых Azure Durable Functions , чтобы попытаться понять, подойдут ли они к предлагаемому решению для нашей внутренней системы API.
Основываясь на примерах, ясоздал клиент Orchestrator (HelloOrchestratorClient.cs
), который отвечает на HttpTrigger
.Этот клиент извлекает некоторую информацию из исходного запроса, затем переходит к запуску функции Orchestrator (HelloOrchestrator.cs
), передавая часть извлеченной информации:
Complex HelloOrchestratorClient.cs:
[FunctionName("HttpSyncStart")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, methods: "get", Route = "orchestrators/{functionName}/wait")]
HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient starter,
string functionName,
ILogger log)
{
HttpReq originalRequest = new HttpReq() {
DeveloperId = GetDevKey(req,apiHeaderKey),
QueryString = req.RequestUri.Query,
APIName = GetQueryStringValue(req,APIName),
APIVersion = GetQueryStringValue(req,APIVersion)
};
string instanceId = await starter.StartNewAsync(functionName, originalRequest);
TimeSpan timeout = GetTimeSpan(req, Timeout) ?? TimeSpan.FromSeconds(30);
TimeSpan retryInterval = GetTimeSpan(req, RetryInterval) ?? TimeSpan.FromSeconds(1);
return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(
req,
instanceId,
timeout,
retryInterval);
}
На данный момент HelloOrchestrator.cs
просто вызывает один из наших внутренних API и возвращает полезную нагрузку JsonProduct
(Простое POCO, описывающее, как вы уже догадались, заголовок), используя ActivityTigger
с именемHelloOrchestrator.APICall
для вызова самого API.
Сложный HelloOrchestrator.cs:
[FunctionName("E1_JsonProduct")]
public static async Task<List<JsonProduct>> Run(
[OrchestrationTrigger] DurableOrchestrationContextBase context,
ILogger log)
{
List<JsonProduct> output = new List<JsonProduct>();
HttpReq r = context.GetInput<HttpReq>();
if(r != null)
{
if(r.DeveloperId == null)
{
return output;
}
output.Add(await context.CallActivityAsync<JsonProduct>("E1_CallAPI",r));
return output;
}
return output;
}
[FunctionName("E1_CallAPI")]
public async static Task<JsonProduct> APICall([ActivityTrigger] HttpReq req,
ILogger log)
{
JsonProduct products = null;
string u = $"{baseAddress}{req.APIVersion}/{req.APIName}{req.QueryString}";
var request = new HttpRequestMessage(HttpMethod.Get, u);
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
request.Headers.Add("x-apikey",req.DeveloperId);
log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'.");
HttpResponseMessage response = await client.SendAsync(request);
// return await response.Content.ReadAsStringAsync();
if(response.IsSuccessStatusCode)
{
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings = HelloProj.CosmosDB.Models.Products.Converter.Settings
};
products = await response.Content.ReadAsAsync<JsonProduct>(new [] {formatter});
}
return products;
}
Стороннее примечание: План, если я могу заставить это работать, состоит в том, чтобы разветвлять кучу процессов к различнымAPI и веер снова включаются, объединяют полезную нагрузку JSON и возвращают ее отправителю.
Проблема, с которой я сталкиваюсь
Итак, когда мой List<JsonProduct>
возвращается из HelloOrchestrator.Run
Я получаю следующее NullReferenceException
, найденное в этом Gist (Трассировка большого стека), и я получаю 500 ответ от Orchestrator Client .
Следующее доказывает, что возвращенный output
действительно имеет объект во время выполнения:
Может ли это быть из-за сложности JsonProduct
(Снова найти модель классов здесь )?Я спрашиваю, потому что, когда я заменяю свою функцию оркестровщика на более простую структуру модели, я не получаю 500, я получаю свою JSON Payload.
В этом примере показана функция Simple Orchestrator HelloOrchestrator.cs
, возвращающая простой TestToDo.cs
( Gist для модели ) плоский объект, который не содержит ошибок :
Простой HelloOrchestrator.cs:
[FunctionName("E1_Todo")]
public static async Task<TestToDo> RunToDo(
[OrchestrationTrigger] DurableOrchestrationContextBase context,
ILogger log)
{
HttpReq r = context.GetInput<HttpReq>();
TestToDo todo = new TestToDo();
if(r != null)
{
todo = await context.CallActivityAsync<TestToDo>("E1_CallAPITodo",r);
}
return todo;
}
[FunctionName("E1_CallAPITodo")]
public async static Task<TestToDo> APITodoCall([ActivityTrigger] HttpReq req,
ILogger log)
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://jsonplaceholder.typicode.com/todos/1");
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json")
);
log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'. for {req.QueryString}");
HttpResponseMessage response = await client.SendAsync(request);
return await response.Content.ReadAsAsync<TestToDo>();
}
Дополнительная информация
Если вам нужны мои полные прототипы проектов, вы можете найти их здесь:
Когда вы его запустите, используйте что-то вроде Postman (после F5ing):
http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=W%26N&N
При запуске используйтепосле чего-то вроде Почтальона (после F5ing):
http://localhost:7071/api/orchestrators/E1_Todo/wait?timeout=20&retryInterval=0.25