正确使用 Azure Durable Function - Serializing Complex Objects
Correct use of Azure Durable Function - Serializing Complex Objects
所以我正在制作一些 Azure 持久函数 的原型,试图了解它们是否适合我们内部 API 系统的建议解决方案。
基于示例,我创建了一个 Orchestrator 客户端 (HelloOrchestratorClient.cs
),它响应 HttpTrigger
。此客户端从原始请求中提取一些信息,然后继续触发 Orchestrator 函数 (HelloOrchestrator.cs
) 传递一些提取的信息:
复杂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 和 returning 一个 JsonProduct
有效负载(简单的 POCO 描述,你猜对了,一个标题),使用名为 HelloOrchestrator.APICall
的 ActivityTigger
使 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 有效负载和 return 合并回原发者。
我遇到的问题
因此,当我的 List<JsonProduct>
从 HelloOrchestrator.Run
返回 return 时,我收到以下 NullReferenceException
在此 Gist 上找到的(大堆栈跟踪) 并且我从 Orchestrator Client.
收到 500 响应
以下证明 output
returned 在 运行 时确实有一个 object:
难道是因为JsonProduct
的复杂性(再次求模型类here)?我问,因为当我将 Orchestrator Function 换成更简单的模型结构时,我 不会 收到 500,我收到我的 JSON 有效负载。
此示例显示了 Simple Orchestrator 函数 HelloOrchestrator.cs
、returning 一个简单的 TestToDo.cs
(Gist for model) flat object 不会出错:
简单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 的东西中使用以下内容(F5 后):
当你 运行 它时,在类似 Postman 的东西中使用以下内容(在 F5 之后):
http://localhost:7071/api/orchestrators/E1_Todo/wait?timeout=20&retryInterval=0.25
查看您发布的调用堆栈,NullReferenceException
似乎是 DurableOrchestrationClient
class 中的错误。查看代码(您可以找到 here)似乎有可能,如果您使用的查询字符串无法正确解析,则可能出现空引用。
您提到您正在使用以下 URL 进行测试:
我想知道最后两个字符 (&N
) 是否是问题的根源。是否可以对 &
进行编码或将其完全删除以隔离问题?
无论如何,如果您能在这里记录一个问题就太好了:https://github.com/Azure/azure-functions-durable-extension/issues
所以我正在制作一些 Azure 持久函数 的原型,试图了解它们是否适合我们内部 API 系统的建议解决方案。
基于示例,我创建了一个 Orchestrator 客户端 (HelloOrchestratorClient.cs
),它响应 HttpTrigger
。此客户端从原始请求中提取一些信息,然后继续触发 Orchestrator 函数 (HelloOrchestrator.cs
) 传递一些提取的信息:
复杂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 和 returning 一个 JsonProduct
有效负载(简单的 POCO 描述,你猜对了,一个标题),使用名为 HelloOrchestrator.APICall
的 ActivityTigger
使 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 有效负载和 return 合并回原发者。
我遇到的问题
因此,当我的 List<JsonProduct>
从 HelloOrchestrator.Run
返回 return 时,我收到以下 NullReferenceException
在此 Gist 上找到的(大堆栈跟踪) 并且我从 Orchestrator Client.
以下证明 output
returned 在 运行 时确实有一个 object:
难道是因为JsonProduct
的复杂性(再次求模型类here)?我问,因为当我将 Orchestrator Function 换成更简单的模型结构时,我 不会 收到 500,我收到我的 JSON 有效负载。
此示例显示了 Simple Orchestrator 函数 HelloOrchestrator.cs
、returning 一个简单的 TestToDo.cs
(Gist for model) flat object 不会出错:
简单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 的东西中使用以下内容(F5 后):
当你 运行 它时,在类似 Postman 的东西中使用以下内容(在 F5 之后):
http://localhost:7071/api/orchestrators/E1_Todo/wait?timeout=20&retryInterval=0.25
查看您发布的调用堆栈,NullReferenceException
似乎是 DurableOrchestrationClient
class 中的错误。查看代码(您可以找到 here)似乎有可能,如果您使用的查询字符串无法正确解析,则可能出现空引用。
您提到您正在使用以下 URL 进行测试:
我想知道最后两个字符 (&N
) 是否是问题的根源。是否可以对 &
进行编码或将其完全删除以隔离问题?
无论如何,如果您能在这里记录一个问题就太好了:https://github.com/Azure/azure-functions-durable-extension/issues