2

I'm learning how to use Azure Durable Functions and I've come across a problem. I'm posting a message to a queue and waiting for it, then logging it to trace, but I'm not sure where to get the InstanceId from.

Here's what I have so far:

public static class Go
{
    private const string ReceivedMessage = "received-message";

    [FunctionName(nameof(Go))]
    public static async Task Run(
        [OrchestrationTrigger] DurableOrchestrationContext context,
        TraceWriter log)
    {
        var message = await context.WaitForExternalEvent<string>(ReceivedMessage);

        log.Info($"Received message: {message}");
    }

    [FunctionName(nameof(GetMessageFromQueue))]
    public static async Task GetMessageFromQueue(
        [QueueTrigger("messages")] string message,
        [OrchestrationClient] DurableOrchestrationClient client)
    {
        await client.RaiseEventAsync("InstanceId", ReceivedMessage, message); // <- here
    }
}

For completeness, here's the rest of the code that I have. The HttpStartSingle class is in the same project as the one above and the Program class is just a normal console application that I use to start the context:

public static class HttpStartSingle
{
    [FunctionName(nameof(HttpStartSingle))]
    public static async Task<HttpResponseMessage> RunSingle(
        [HttpTrigger(
            AuthorizationLevel.Function,
            "get", "post",
            Route = "orchestrators/{functionName}")]
        HttpRequestMessage req,
        [OrchestrationClient] DurableOrchestrationClient starter,
        string functionName,
        TraceWriter log)
    {
        var eventData = await req.Content.ReadAsAsync<object>();
        var instanceId = await starter.StartNewAsync(functionName, eventData);

        return starter.CreateCheckStatusResponse(req, instanceId);
    }
}

public class Program
{
    public static async Task Main(string[] args)
    {
        Thread.Sleep(TimeSpan.FromSeconds(5));

        var request = WebRequest.Create("http://localhost:7071/api/orchestrators/Go");

        request.Timeout = Convert.ToInt32(TimeSpan.FromMinutes(1).TotalSeconds);
        request.ContentLength = 0;
        request.Method = "POST";

        var json = string.Empty;

        using (var response = await request.GetResponseAsync())
        using (var stream = response.GetResponseStream())
        {
            if (stream != null)
            {
                using (var reader = new StreamReader(stream, Encoding.UTF8))
                {
                    if (reader.Peek() > -1)
                    {
                        json = await reader.ReadToEndAsync();
                    }
                }
            }
        }

        Console.WriteLine(json);

        var storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
        var queueuClient = storageAccount.CreateCloudQueueClient();
        var queue = queueuClient.GetQueueReference("messages");

        await queue.CreateIfNotExistsAsync();
        await queue.AddMessageAsync(new CloudQueueMessage("This is a test message"));

        Console.ReadKey();
    }
}

Update:

The above code is just really a proof of concept to see if Durable Functions meets my requirements. I'm not sure so far, however here is what I'm looking to do:

I will have two queues.

Queue A will be seeded with a small number of A's, which I intend to recycle.

Queue B will have an indeterminate (for all intents and purposes infinite) number of B's pushed to it by an external source.

I want the orchestrator to wait for inputs from both queues, then send A and B to another function which will produce a number of B's. That function will then push the B's to Queue B and then push the A back to Queue A. The process then repeats.

I'd prefer the processing to be parallel based on the number of A's I have. As I see it, I have two options:

A) Create one Orchestrator created with an InstanceId taken from a constant. Then, the functions that listen to the A and B Queues will know what InstanceId to use for the RaiseEventAsync call.

Problem: Not parallel.

B) Wrap an instanceId in an A. Create one Orchestrator per A. A knows which Orchestrator to use and now each Orchestrator can just listen to Queue B.

Problem: B's don't know what InstanceId to use for the RaiseEventAsync call.

Aaron
  • 622
  • 7
  • 15
  • Do all messages from that queue belong to the same orchestration ID? Or should that differ per message? – Mikhail Shilkov Feb 02 '18 at 09:17
  • @Mikhail I can't give you a direct answer, because I don't understand the question. However I only intend to send messages from the `GetMessageFromQueue` function to the `Go` function. – Aaron Feb 02 '18 at 09:33
  • Ok, the question is whether you will have just one Instance ID or multiple at the same time? – Mikhail Shilkov Feb 02 '18 at 09:35
  • @Mikhail I don't know. How can I find that out? – Aaron Feb 02 '18 at 09:38
  • Ah, it looks like `StartNewAsync` can take an `InstanceId`. If I have that as a constant value available to the console application and the function, I can probably pass it through to the orchestrator to use. I'll try that when I get home. – Aaron Feb 02 '18 at 09:53
  • 1
    That's why I was asking :) If you need to have many of those per your requirement, you'll need to pass it as part of a queue message. – Mikhail Shilkov Feb 02 '18 at 09:57
  • I only want one orchestrator. That's why I've created it as a singleton. I expect it to be a long running process that takes inputs from functions watching various queues, combines them and sends them off to other functions to do work. – Aaron Feb 02 '18 at 09:59
  • Does your Console.WriteLine(json); call actually write something out right now? I'd be surprised if it does based on the fact that CreateCheckStatusResponse is supposed to simply return a 202 with a Location header, but, if it does, can you include that output in your question? – Drew Marsh Feb 02 '18 at 18:01
  • Yes it returns the expected results. I can just as easily start the Orchestrator by changing it to trigger from a queue though. In fact that's what I eventually did. Still stuck though. Exploring other possibilities for now but no success so far. – Aaron Feb 03 '18 at 17:37

1 Answers1

1

Whatever puts the message on the queue that is going to trigger your GetMessageFromQueue would need to understand what instance it's trying to target and I would then expect that you include the instance ID in the message itself.

In the specific scenario you laid out above, I would actually expect your HttpTrigger based function to return a body that includes the instance ID. Right now you're returning the HttpResponseMessage that's created by CreateCheckStatusResponse, but this is documented as returning a 202 status with a Location header that tells people where they can check on the status of that instance of the orchestration going forward. While this information might be useful to handback as well, you really need to be handing back the instance ID.

One way would be to create your own HttpResponseMessage that contains a strongly typed message body that includes the instance ID as a property. If you did that, then the calling program would be able to pull that ID out of the response body and then include it in the message they put onto the queue so that your GetMessageFromQueue function can pull it out of there to pass to RaiseEventAsync.

Drew Marsh
  • 33,111
  • 3
  • 82
  • 100
  • Got it. Let me explain the situation. I will have two queues. Queue A will be seeded with a small number of A's, which I intend to recycle. Queue B will have an indeterminate (for all intents and purposes infinite) number of B's pushed to it by an external source. I want the orchestrator to wait for inputs from both queues, then send A and B to another function which will produce a number of B's. It will then push the B's to Queue B and then push the A back to Queue A. The process then repeats. – Aaron Feb 03 '18 at 08:09
  • I'd prefer the processing to be parallel based on the number of A's I have. As I see it, I have two options: A) Create one orchestrator created with an instanceId taken from a constant. Then, the functions that listen to the A and B Queues will know what instanceId to use for the `RaiseEventAsync` call. Problem: Not parallel. B) Wrap an instanceId in an A. Create one orchestrator per A. A knows which orchestrator to use and now each orchestrator can just listen to Queue B. Problem: B's don't know what instanceId to use for the `RaiseEventAsync` call. I feel like I'm in Catch 22. – Aaron Feb 03 '18 at 08:17
  • Ok, let me think about this a little bit. In the meantime, I suggest you update your original question with these details so anyone else coming across the question doesn't miss them since they help paint a much clearer picture of exactly what you're aiming to accomplish. – Drew Marsh Feb 03 '18 at 16:42