Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
id: add-a-standalone-activity
title: Step 9 - Add a Standalone Activity
sidebar_label: 9. Add a Standalone Activity
description: Back the notification Nexus Operation with a Standalone Activity instead of a Workflow, with no wrapper Workflow required.
toc_max_heading_level: 4
keywords:
- nexus standalone activity
- activity backed operation
- start activity
- notification activity
tags:
- Nexus
- Java SDK
---

The last Operation is `notifyRequester`, which tells the requester their approval was `APPROVED` or `DENIED`.

This one is not a Workflow. It is a single outbound notification with no state, nothing to wait for, and nothing to orchestrate — the [Standalone Activity](/nexus/standalone-activity) shape chosen in [step 3](/develop/java/nexus/development-walkthrough/choose-backing-implementation).

## Write the Activity

The Activity is an ordinary Activity. In this walkthrough it is a placeholder that does nothing — no email is sent. Real logic would call an email provider, push to a notification service, or write to an outbox.

Nothing about it is Nexus-specific. The same Activity could be called from a Workflow.

`{sample code will be here}`

## Back the Operation with it

Use `TemporalOperationHandler` as with every other Operation, but call `startActivity` on the Nexus-aware Client instead of `startWorkflow`. The Operation starts an Activity Execution with no parent Workflow and completes when the Activity returns.

Before Activity-backed Operations, this Operation would have needed a Workflow whose only job was to call this one Activity — a wrapper with its own Event History and Workflow Id, providing nothing.

`{sample code will be here}`

### Options an Activity-backed Operation requires

`StartActivityOptions` needs two things that a Workflow-called Activity does not, because there is no parent Workflow to supply them:

- **An Activity Id**, unique within the Namespace.
- **A Task Queue.** It does not have to be the Endpoint's target Task Queue, so notifications can run on their own Worker fleet.

Derive the Activity Id from the Nexus request Id to make the start idempotent. The server retries a Nexus start request with the same request Id, so each retry targets the same Activity Id instead of sending a second notification.

That last point matters here more than usual. A duplicate Workflow start is usually harmless; a duplicate notification is a second email to a real person.

## Register the Activity on the Worker

Add the Activity implementation to the same Worker that hosts the Nexus Service. An Activity-backed Operation needs no Workflow implementation registered for it.

`{sample code will be here}`

## Cancellation needs heartbeating

For this notification the point is moot — it finishes immediately. But it is worth knowing before you write a longer Activity-backed Operation, because the behavior differs from a Workflow-backed one.

A Workflow is interrupted by a cancellation request. An Activity is not: the server records the request, and the Worker only finds out on its next heartbeat. An Activity that never heartbeats runs until it completes or hits its start-to-close timeout, however many cancellation requests arrive.

Making a long-running Activity-backed Operation cancellable takes three settings together — heartbeating from the Activity, a heartbeat timeout, and maximum attempts of 1 so a cancelled attempt is not retried. See [Cancellation requires heartbeating](/nexus/standalone-activity#cancellation-requires-heartbeating).

## Next

[Call the Standalone Activity](/develop/java/nexus/development-walkthrough/call-the-standalone-activity).

:::tip RESOURCES

- [Nexus Standalone Activity](/nexus/standalone-activity) for the full concept and options.
- [Standalone Activity](/standalone-activity) for Activity Executions outside a Workflow.
- [Java: Standalone Activities](/develop/java/activities/standalone-activities) for the SDK API.

:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
id: add-messaging
title: Step 7 - Add messaging
sidebar_label: 7. Add messaging
description: Expose Signal, Query, and Update on the approval Workflow as Nexus Operations using the Nexus-aware Client.
toc_max_heading_level: 4
keywords:
- nexus signal
- nexus query
- nexus update
- workflow message passing
- temporal operation handler
tags:
- Nexus
- Java SDK
---

The approval blocks waiting for a decision. Now give callers a way to interact with it while it waits.

Three Operations get added, one per [message](/sending-messages) type. Which message type to use is determined by what the caller needs back, not by preference.

| Operation | Message type | Why this type |
| --- | --- | --- |
| `remindApprover` | Signal | Fire-and-forget. The caller does not need a response, only for the nudge to happen. |
| `getApprovalStatus` | Query | Reads state without changing it. Never blocks, never writes. |
| `submitDecision` | Update | Changes state *and* returns a result the caller needs — confirmation the decision was recorded. |

## Add the handlers to the Workflow

On the Workflow, add a Signal handler that increments the reminder count, a Query handler that returns the current progress, and an Update handler that records the decision and unblocks the wait.

The Update is what ends the approval. It records `APPROVED` or `DENIED`, which satisfies the condition the Workflow is blocked on, and the Workflow then returns that decision as its result.

`{sample code will be here}`

Two constraints apply to the Query handler. It must not block, and it must not mutate Workflow state — a Query is served by replaying history, so anything it changes is invisible and anything it waits on stalls the Query. Return only what is already in memory.

## Expose them as Nexus Operations

All three use `TemporalOperationHandler`, but they divide along the line described in [Nexus SDK V2](/nexus/sdk-v2#the-nexus-aware-client): Signal and Query are **sync side effects**, and Update is an **async backing**.

### Signal and Query

Reach these through `client.getWorkflowClient()` on the Nexus-aware Client, then return `TemporalOperationResult.sync(...)`. The Operation completes immediately, during the handler call.

You can perform as many sync side effects as you want in one handler. Using the injected Client rather than your own is what gets the message linked back to the caller.

`{sample code will be here}`

### Update

Use `client.startWorkflowUpdate(...)`, which is an async backing: the Operation completes when the Update completes, and its result is delivered through the Nexus completion callback. If the Update happens to come back already complete — a retried request, or one that failed validation — the result returns synchronously instead.

Because it is an async backing, there is at most one per Operation invocation. A handler can still combine it with sync side effects.

`{sample code will be here}`

:::caution Query linking is not complete

Query works through `client.getWorkflowClient()`, but [bidirectional linking](/nexus/execution-debugging#bi-directional-linking) for Query is still in progress and is not yet available in any SDK. A Query sent from a handler is not connected to the caller in the UI the way a Signal is.

The Operation behaves correctly; only the observability link is missing. See [Nexus SDK V2](/nexus/sdk-v2) for current status.

:::

## Keep the responsibilities separate

It is worth restating why there are three Operations rather than one flexible one, because collapsing them is a common mistake.

`getApprovalStatus` reports **in-flight progress only** — whether a decision is still pending, and how many reminders have gone out. It does not return the final decision. The decision is the result of `requestApproval`, which the caller is already awaiting from [step 6](/develop/java/nexus/development-walkthrough/call-the-service).

Using a Query to fetch the outcome would mean polling for something that is already being pushed, and it would break once the [Retention Period](/temporal-service/temporal-server#retention-period) expires and the history the Query replays is gone.

## Next

[Send messages](/develop/java/nexus/development-walkthrough/send-messages) from the caller.

:::tip RESOURCES

- [Workflow message passing](/encyclopedia/workflow-message-passing) for Signals, Queries, and Updates.
- [Handling messages](/handling-messages) for handler constraints, including Query restrictions.
- [Nexus SDK V2](/nexus/sdk-v2) for the sync side effect and async backing distinction.

:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
id: call-the-service
title: Step 6 - Call the Service
sidebar_label: 6. Call the Service
description: Call the approval Nexus Service from a caller Workflow in another Namespace using the generated Service interface.
toc_max_heading_level: 4
keywords:
- nexus caller workflow
- call nexus operation
- cross namespace
- nexus service stub
tags:
- Nexus
- Java SDK
---

Call `requestApproval` from a Workflow in the caller Namespace. The caller knows the Endpoint name and the contract, and nothing else about the handler.

## Use the generated interface as a stub

In Java, the generated Service interface works directly as a Nexus Service stub. Create it inside the caller Workflow with the Endpoint name and Operation options, then call its methods as if they were local.

Because the stub is the generated interface, the call is type-checked against the contract at compile time. A field the contract does not have will not compile, and a payload the contract forbids is rejected by the generated validator before it reaches the wire.

`{sample code will be here}`

## Await the decision

`requestApproval` returns `APPROVED` or `DENIED`. That value is the approval Workflow's return value, delivered to the caller through the Nexus completion callback when the Workflow finishes.

The caller does not poll. It awaits the Operation, and the wait is durable — the caller Workflow can be evicted, the Worker can restart, and the result still arrives.

Set a schedule-to-close timeout that reflects how long an approval can legitimately take. A human approval measured in days needs a timeout in days; the default is not going to be right. See [Nexus Operations](/nexus/operations) for the timeout model.

## Callers in other languages

The caller here is Java, but nothing about the handler requires that.

:::note For reviewers

Every sample in this walkthrough is generated from the contract written in [step 1](/develop/java/nexus/development-walkthrough/define-the-data-contract), so a caller in any supported language talks to this same Java handler without changes on either side. Readers working in Go, Python, or TypeScript will be able to find a caller for their language in that language's sample repository rather than porting this one by hand.

To generate a caller for another language from this contract, see [Generate code](/nexus/client-code-generator#generate-code) in the Nexus Client Code Generator documentation.

:::

## Calling without a caller Workflow

A caller Workflow is the usual pattern and the one this walkthrough uses, because a Workflow gives the call durability and lets you orchestrate around it.

If you only need to run one Operation and have nothing to orchestrate, a Client can start an Operation directly with no caller Workflow at all. That is a [Standalone Nexus Operation](/standalone-nexus-operation), and it uses the same Service contract, the same handler, and the same Endpoint — only the caller side differs. See [Java: Standalone Operations](/develop/java/nexus/standalone-operations).

## Next

The Service can start an approval and return a decision. [Add messaging](/develop/java/nexus/development-walkthrough/add-messaging) so callers can interact with an approval while it is pending.

:::tip RESOURCES

- [Nexus Operations](/nexus/operations) for the Operation lifecycle and timeouts.
- [Java Nexus feature guide](/develop/java/nexus/feature-guide) for the caller API.
- [Nexus Client Code Generator](/nexus/client-code-generator) for generating callers in other languages.

:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
id: call-the-standalone-activity
title: Step 10 - Call the Standalone Activity
sidebar_label: 10. Call the Standalone Activity
description: Call the Activity-backed notification Operation from a caller Workflow and complete the approval flow end to end.
toc_max_heading_level: 4
keywords:
- call nexus operation
- activity backed operation
- nexus caller workflow
tags:
- Nexus
- Java SDK
---

Call `notifyRequester` once the decision is final. From the caller's side there is nothing new to learn, which is the point of this step.

## The caller cannot tell the difference

`notifyRequester` is called exactly like `requestApproval`: through the same generated Service stub, with the same Endpoint, the same type checking, and the same error handling.

Nothing in the caller reveals that this Operation is backed by an Activity and the other by a Workflow. That is the contract doing its job. The handler team could later replace the notification Activity with a Workflow that retries across providers and escalates on failure, and no caller would change.

`{sample code will be here}`

## Complete the flow

With all ten steps in place, the caller Workflow runs the whole approval:

1. Call `requestApproval` and await it. The Operation starts the approval Workflow in the handler Namespace.
2. While it is pending, other systems call `remindApprover` to nudge and `getApprovalStatus` to report progress.
3. Someone calls `submitDecision` with `APPROVED` or `DENIED`. The Update records it, confirms to that caller, and unblocks the approval Workflow.
4. The approval Workflow returns the decision, which resolves the `requestApproval` Operation the original caller has been awaiting.
5. The caller calls `notifyRequester` with the decision, backed by the notification Activity.

`{sample code will be here}`

Every step crossed a Namespace boundary, and the caller never learned a Workflow Id, a Task Queue, or which primitive backed any Operation.

## Trace it end to end

Open the caller Workflow in the UI and follow the links. Because the handlers used the Nexus-aware Client, each Operation is connected to the Execution it started or messaged in the handler Namespace, and you can move between the two Namespaces in one view.

The exception is Query: [linking for Query is still in progress](/nexus/sdk-v2), so `getApprovalStatus` will not show a link yet.

## Where to go next

The Service is complete but minimal. Natural extensions:

- **Timeouts and escalation.** Give the approval a deadline and escalate or auto-deny when it passes.
- **Split the Workers.** Run the Nexus Service, the approval Workflow, and the notification Activity on separate Worker fleets. See [Nexus patterns](/nexus/patterns).
- **Callers in other languages.** Generate a caller from the same contract in Go, Python, or TypeScript. See [Nexus Client Code Generator](/nexus/client-code-generator).
- **Standalone invocation.** Call an Operation from a Client with no caller Workflow. See [Standalone Nexus Operation](/standalone-nexus-operation).

Before running this against anything real, read [Debugging, common pitfalls, and tips](/develop/java/nexus/development-walkthrough/debugging-and-tips).

:::tip RESOURCES

- [Nexus execution debugging](/nexus/execution-debugging) for tracing across Namespaces.
- [Nexus patterns](/nexus/patterns) for Worker and Service topology.
- [Nexus Standalone Activity](/nexus/standalone-activity) for Activity-backed Operations.

:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
id: choose-backing-implementation
title: Step 3 - Choose the backing implementation
sidebar_label: 3. Choose the backing implementation
description: Decide whether each Nexus Operation is backed by a Standalone Activity, a Workflow, or an Entity Workflow.
toc_max_heading_level: 4
keywords:
- nexus operation backing
- entity workflow
- standalone activity
- workflow backed operation
tags:
- Nexus
- Java SDK
---

The contract says nothing about what runs behind an Operation. That is deliberate — it is the handler's private decision, and it can change later without touching callers.

There are three shapes to choose from, and picking the wrong one is the most common source of trouble later.

## Standalone Activity

One step, no waiting, no state. Call an external API, run a computation, send a notification.

The Operation starts an Activity Execution with no parent Workflow, and completes when the Activity returns. You get retries and a durable record without a wrapper Workflow that exists only to call one Activity.

The tradeoff is that an Activity has no Workflow's ability to receive messages or hold state, and cancellation only works if the Activity heartbeats. See [Nexus Standalone Activity](/nexus/standalone-activity).

## Workflow

More than one step, or any need for durable intermediate state.

The Operation starts a Workflow and completes when that Workflow returns, so the Workflow's return value is the Operation's result. Use this when the work orchestrates several Activities, needs a timer, or needs to survive a Worker restart partway through.

## Entity Workflow

A Workflow that represents one long-lived thing and stays available for interaction while it runs.

The distinguishing feature is that it accepts [messages](/sending-messages) — Signals, Queries, and Updates — against a stable Workflow Id derived from the entity it represents. It is still a Workflow-backed Operation; "entity" describes how you use it, not a separate mechanism.

## The choice for the approval Service

The approval problem needs both.

| Operation | Backing | Why |
| --- | --- | --- |
| `requestApproval` | Entity Workflow | Blocks for a human decision, holds the reminder count, and must accept messages while pending |
| `notifyRequester` | Standalone Activity | One outbound notification, no state, nothing to wait for |

An approval is a textbook entity: it exists for a while, it has identity, and other systems interact with it during its lifetime. Backing it with an Activity would not work at all — an Activity cannot block for a human and cannot receive a Signal.

The notification is the opposite. It is a single side effect with nothing to orchestrate, so a Workflow would add an Event History and a Workflow Id for no benefit.

## Give the entity a stable Id

An Entity Workflow needs a Workflow Id derived from the entity, not a random one, so that later messages can find it. Deriving the approval's Workflow Id from the approval id means a caller that knows the approval id can reach the right Execution.

This also makes the start idempotent. A retried Nexus start request targets the same Workflow Id rather than starting a second approval.

## Next

[Implement the Service](/develop/java/nexus/development-walkthrough/implement-the-service) with a Workflow-backed Operation.

:::tip RESOURCES

- [Nexus Standalone Activity](/nexus/standalone-activity) for Activity-backed Operations.
- [Workflow message passing](/encyclopedia/workflow-message-passing) for what makes a Workflow interactive.
- [Nexus patterns](/nexus/patterns) for Service and Worker topology choices.

:::
Loading