When it comes to resurrecting a continuation and returning to a prior flow of control, the most critical issue is the ability to accurately identify the continuation to be resumed based on an external event. Any application beyond a simple example will likely have multiple continuations persisted, and the means for mapping some event to a continuation cannot be a dynamic aspect of the running program that made the continuations in the first place. If this were the case, then continuations would have little value, as their useful lifetime would be scoped to the lifetime of the running program that made them. If that program were to stop, then the continuations would all be worthless.
For a moment, imagine the manual process a small retail business goes through when restocking inventory. They identify what they need, send off an order, wait for the package to arrive, and then place the items on their shelves. This sequence of events describes a workflow, a series of actions performed over time, where each action is triggered by some event, either an external stimulus (out of stock, package arrives) or the completion of a prior action (sending off the order, stocking the shelves).
The business does not cease to function while waiting for the order; in fact they may completely forget about the order and be involved with other issues. At some point the package containing the order is received (we’re not going to deal with faults yet), and that package is inspected to locate the invoice it contains. The invoice in turn contains the information necessary to uniquely identify the original order, and through this, continuity is established between the action of mailing off the order and receiving the package. The business could conceivably have changed location, owners, and staff during the time the order was outstanding, yet the package could still be received in a new location, by new people, and recognized as part of an existing workflow.
The key element in this process for most business is the order number, which serves to identify the workflow, and is echoed on the invoice received with the package that fills the order. This identifier allows the workflow to be suspended, placed in permanent storage (the file cabinet), with the knowledge that (in all but exceptional cases) an external trigger (receiving a package) containing a unique identifier (the order number) will allow the workflow to be resumed.
This is exactly the behavior we want in an electronic workflow system. If we had a document authoring system, where one employee writes a report, a manager approves it, and another distributes it to an audience, we’d like to be able to deal rationally with the stages where the workflow may be interrupted for prolonged periods of time, for example when the manager takes the report home over the weekend to read it. It is not reasonable to require that a single instance of an application guarantee its own lifetime over these breaks of unknown duration, and the situation is common enough that custom coding every application to handle the problem on its own would add significant unnecessary cost to the applications.
Lets go back to the code of the previous posting, specifically to the line where the continuation for the stock check event is created;
Form1._continuationManager.Add(new Continuation("stockcheck_response_" + _partNumber, CheckCompleted));
Note the identity being assigned to the continuation, the constant string “stockcheck_response_” with the part number of the stock item appended. We know that any previous action, whether it was showing instructions to someone on the screen for a manual action to be performed, or sending a message to an external service, will be in the context of a request to check the stock level of some item. So it isn’t unreasonable to assume any returning answer can be accompanied by this same information. In short, if a sales associate goes to count the number of items in stock, they don’t just return and blurt out ‘42’, they know that number is in the context of a stock check, and they know the item the number applies to.
Now, imagine we have a stock checking piece of software that requires people in our warehouse, in some other state and time zone, go physically to the location where items are stored and count the items on hand. So our software first sends some text to a fax machine in the warehouse, telling them we want to know the number of PartABC items on hand. At some point somebody collects the fax, walks out to where the items are stored, and counts them. They then call into an automated phone system, select the menu option that indicates they’re responding to a stock check request, key in the part number, and then the amount on hand.
Lets add a few more twists. Our stock checking application at headquarters, having nothing else to do after issuing the initial request, terminates after saving the continuation in durable storage. Our phone system is smart, and when the response arrives, will regularly attempt to contact our application and deliver the information. When the application starts up again at headquarters, possibly several days later, the phone system notices this, and sends it a message that identifies itself as a response to a request for a stock check, and carries the part number and the quantity on hand.
Our stock checking application, built with the knowledge that it uses continuations, reloads all continuations from durable storage each time it starts. It then waits passively, either for someone to start a new workflow, or for a continuation to be resumed. In this scenario, it receives the message from the phone system. The first thing it can determine is that the message is a trigger for resuming a continuation because the message identifies itself as a response, which implies that there was an original request. The message also contains a unique identifier, the part number, and the combination of these two pieces of information uniquely identifies a previously persisted communication, generated by the line shown above. This is all the information needed to resume the workflow.
In conclusion, assuming there is a mechanism to automatically save and restore continuations from durable storage whenever the program starts and stops, and it can receive sufficient information to identify a continuation from an external source, then this new programming paradigm can be applied. Specifically, whenever a programs workflow effectively transfers to some external system via a message, and will not return until some response from that external system is received, a continuation can be established that identifies where the workflow is to be resumed when the response is received. At that point the programs immediate job is done.
In the next posting I’ll cover the specifics of resuming the workflow, and how workflows are saved and loaded from durable storage.