正确使用 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.APICallActivityTigger 使 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 后):

http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=W%26N&N

当你 运行 它时,在类似 Postman 的东西中使用以下内容(在 F5 之后):

http://localhost:7071/api/orchestrators/E1_Todo/wait?timeout=20&retryInterval=0.25

查看您发布的调用堆栈,NullReferenceException 似乎是 DurableOrchestrationClient class 中的错误。查看代码(您可以找到 here)似乎有可能,如果您使用的查询字符串无法正确解析,则可能出现空引用。

您提到您正在使用以下 URL 进行测试:

http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=W%26N&N

我想知道最后两个字符 (&N) 是否是问题的根源。是否可以对 & 进行编码或将其完全删除以隔离问题?

无论如何,如果您能在这里记录一个问题就太好了:https://github.com/Azure/azure-functions-durable-extension/issues