Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

Step 8 - Send messages

View Markdown

From the caller's side, the messaging Operations are just Operations. They are called through the same generated stub as requestApproval, with the same type checking.

The caller does not know that one is a Signal and one is an Update. That is the handler's implementation detail, and it can change without breaking callers.

Nudge and decide

{sample code will be here}

What differs between them is what you get back and how long it takes.

remindApprover returns nothing and completes as soon as the Signal is accepted. Accepted is not the same as handled — the Signal is durably recorded and the Workflow will process it, but the Operation does not wait for that. If the caller needs confirmation that the nudge took effect, it needs an Update, not a Signal.

submitDecision returns confirmation that the decision was recorded. This is the point of using an Update: the caller learns the outcome of its own message. Once it succeeds, the approval Workflow unblocks and completes, which resolves the requestApproval Operation that the original caller is still awaiting.

Both require the approval to already be running. A nudge or a decision for a purchase nobody has requested approval for has nothing to reach, and the Operation fails.

Attach information before the approval exists

The next Operation does not have that requirement, and the reason is worth the detail.

Supporting information for a purchase — a justification, a link to a quote, a manager's note — comes from a different system than the one requesting approval. Those two systems run independently, so their messages arrive in whatever order the network and their schedules produce. Sometimes the context arrives first.

A plain Signal cannot handle that. Sending one to a Workflow that does not exist fails, and "fail if the approval has not been requested yet" is the wrong behavior for a message whose whole job is to be available whenever it shows up.

attachApprovalContext uses Signal-with-Start instead. If the approval is already running, the note is delivered to it. If it is not, the approval is started and then the note is delivered. Either order works, and the caller does not have to know which happened.

{sample code will be here}

Signal-with-Start is sync messaging on the Client, so the Operation completes during the handler call and returns nothing. The caller gets no confirmation that a human read the note, only that it was durably attached.

Input needs enough to start the Workflow

Look at the contract from step 1 and attachApprovalContext carries more than it seems to need: the item id, the requester, the amount, and the note. remindApprover gets by with just an approval id.

That is a direct consequence of Signal-with-Start. The Operation might have to start the approval Workflow, and starting it requires whatever the Workflow needs to run. An Operation that can create the thing it messages has to carry enough input to create it.

This is the general rule for any with-Start message: its input is the union of what the message needs and what the Workflow's start needs.

When the approval already exists

Signal-with-Start introduces a case the Service did not have before. attachApprovalContext can create the approval, so by the time anyone calls requestApproval for that purchase, a Workflow with that Id may already be running.

Both Operations derive the same Workflow Id from the same item id, as decided in step 3. That is deliberate — it is what lets them agree on which approval they mean — and it is also what creates the collision.

By default, requestApproval fails in this situation. Starting a Workflow whose Id is already running is an error, and the Nexus Operation fails with it.

That default is not arbitrary strictness. A Workflow-backed Operation has only started successfully once its completion callback is attached to a Workflow. If the start quietly did nothing on a conflict, the Operation would report success with no callback attached, and the caller would wait for a decision that could never be delivered. Failing immediately is better than hanging forever.

The fix is to change the Workflow Id conflict policy on requestApproval from its default to use-existing. With that set, a start against an already-running approval attaches the Operation's completion callback to that Execution instead of failing. The caller then awaits the approval that is already in flight and receives its decision when it completes.

{sample code will be here}

Two things follow from this, and both are useful.

More than one caller can await the same approval. Every caller whose callback is attached is notified when the Workflow completes, so several systems can each call requestApproval for the same purchase and all receive the same decision. The first call creates the approval; the rest attach to it.

The Operation becomes idempotent for callers, not just for retries. Step 3 made the start idempotent against server retries of one request. Use-existing extends that to genuinely separate callers, which is what you want for a purchase that two systems might both submit.

One limit to know: use-existing attaches to a running Execution. If the approval has already completed, there is nothing to attach to, and the call starts a fresh approval rather than returning the old decision. Whoever needs the outcome of a finished approval has to have been attached while it was open, or be told by the handler — which is what the notification in the next step does.

The two Workflow Id policies

Two policies govern a start against a Workflow Id already in use, and they cover cases that never overlap:

  • The Workflow Id conflict policy applies while a Workflow with that Id is running. It defaults to failing with Workflow execution already started — the default this section replaces with use-existing.
  • The Workflow Id reuse policy applies once the previous Workflow with that Id has closed. It defaults to Allow Duplicate, which permits a new Execution.

Setting use-existing changes only the running case. A start against a completed approval falls to the reuse policy and opens a new one. Set the reuse policy as well if a second approval for the same item is not what you want.

Next

Step 9 - Add a Standalone Activity - notify the requester with no Workflow behind it.

Back to the Microservice Development Walkthrough overview.

RESOURCES