Skip to content

Commit

Permalink
Client page update (#404)
Browse files Browse the repository at this point in the history
* Fix workflow page TS

* Improvements to TS workflows page

* Update workflows page TS

* Add link to examples

* Update workflows page TS

* Update querying description on TS workflows page

* Update workflows page and service communication page

* Cleanup workflow clients

* Move client pages to the SDK docs

* Update Java workflow page for getOutput

* Fix broken links
  • Loading branch information
gvdongen authored Jun 7, 2024
1 parent d75993d commit 7751826
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 402 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dev.restate.sdk.client.Client;
import dev.restate.sdk.client.SendResponse;
import dev.restate.sdk.common.Output;

public class WorkflowSubmitter {

Expand All @@ -20,10 +21,20 @@ public void submitWorkflow(Email email) {
// <end_query>

// <start_interact>
// Option 1: attach and wait for result
boolean result = SignupWorkflowClient
.fromClient(restate, "someone")
.workflowHandle()
.attach();

// Option 2: peek to check if ready
Output<Boolean> peekOutput = SignupWorkflowClient
.fromClient(restate, "someone")
.workflowHandle()
.getOutput();
if(peekOutput.isReady()){
boolean result2 = peekOutput.getValue();
}
// <end_interact>

}
Expand Down
67 changes: 6 additions & 61 deletions code_snippets/java/src/main/java/operate/invocations/Ingress.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ public void myOneWayCallHandler(Context ctx) {
GreetCounterObjectClient.fromClient(rs, "Mary")
.send()
.greet("Hi");

MyWorkflowClient.fromClient(rs, "wf-id-1")
.submit("input");
// <end_one_way_call_java>
}

Expand Down Expand Up @@ -70,77 +67,25 @@ public void attach() {
Client rs = Client.connect("http://localhost:8080");
SendResponse handle = GreeterServiceClient.fromClient(rs)
.send()
// mark
.greet("Hi", CallRequestOptions.DEFAULT.withIdempotency("abcde"));

// ... do something else ...

// Attach later to retrieve the result
// withClass(1:3) highlight-line
// Option 1: Attach later to retrieve the result
// mark(1:3)
String greeting = rs
.invocationHandle(handle.getInvocationId(), JsonSerdes.STRING)
.attach();
// <end_service_attach>
}

public void peekAtOutput(){
// <start_service_peek>
Client rs = Client.connect("http://localhost:8080");
SendResponse handle = GreeterServiceClient.fromClient(rs)
.send()
.greet("Hi", CallRequestOptions.DEFAULT.withIdempotency("abcde"));

// ... do something else ...

// Peek to see if the result is ready
// withClass(1:3) highlight-line
Output<String> output = rs
.invocationHandle(handle.getInvocationId(), JsonSerdes.STRING)
.getOutput();

if (output.isReady()) {
String result = output.getValue();
}
// <end_service_peek>
}

public void latchOntoWorkflow(){

// <start_workflow_attach>
Client rs = Client.connect("http://localhost:8080");
SendResponse handle = MyWorkflowClient
.fromClient(rs, "wf-id-1")
.submit("input");

// If you have access to the workflow handle:
// withClass(1:3) highlight-line
String response = rs
.invocationHandle(handle.getInvocationId(), JsonSerdes.STRING)
.attach();

// If you do not have access to the workflow handle, use the workflow ID:
String response2 = MyWorkflowClient.fromClient(rs, "wf-id-1")
// withClass(1:2) highlight-line
.workflowHandle()
.attach();
// <end_workflow_attach>

// <start_workflow_peek>
// If you have access to the workflow handle:
// withClass(1:3) highlight-line
// Option 2: Peek to see if the result is ready
// mark(1:3)
Output<String> output = rs
.invocationHandle(handle.getInvocationId(), JsonSerdes.STRING)
.getOutput();

if (output.isReady()) {
String result = output.getValue();
}

// If you do not have access to the workflow handle, use the workflow ID:
Output<String> output2 = MyWorkflowClient.fromClient(rs, "wf-id-1")
// withClass(1:2) highlight-line
.workflowHandle()
.getOutput();
// <end_workflow_peek>

// <end_service_attach>
}
}
2 changes: 1 addition & 1 deletion code_snippets/ts/src/develop/workflows/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default restate.object({
handlers: {
// <start_here>
signUpUser: async (ctx: ObjectContext, email: string) => {
// focus(1:2)
// focus(1:3)
const result = await ctx
.workflowClient<SignUpWorkflow>({name: "signup"}, "someone")
.run({email});
Expand Down
17 changes: 15 additions & 2 deletions code_snippets/ts/src/develop/workflows/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ async function signUpUser(user: User){
// <start_submit>
// import * as restate from "@restatedev/restate-sdk-clients";
const rs = restate.connect({url: "http://localhost:8080"});
await rs.workflowClient<SignUpWorkflow>({name: "signup"}, "someone")
const handle = await rs
.workflowClient<SignUpWorkflow>({name: "signup"}, "someone")
.workflowSubmit({email: user.email});
// <end_submit>

Expand All @@ -18,9 +19,21 @@ async function signUpUser(user: User){
// <end_query>

// <start_attach>
const result = await rs
// Option 1: attach and wait for result with handle
const result1 = await rs.result(handle);

// Option 2: attach and wait for result with workflow ID
const result2 = await rs
.workflowClient<SignUpWorkflow>({name: "signup"}, "someone")
.workflowAttach();

// Option 3: peek to check if ready with workflow ID
const peekOutput = await rs
.workflowClient<SignUpWorkflow>({name: "signup"}, "someone")
.workflowOutput();
if(peekOutput.ready){
const result3 = peekOutput.result;
}
// <end_attach>

}
57 changes: 7 additions & 50 deletions code_snippets/ts/src/operate/invocations/ingress.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,48 @@
import {greetCounterObject, greeterService} from "./utils";
import {SendOpts} from "@restatedev/restate-sdk-clients";
import {myWorkflow} from "../../concepts/invocations/utils";

// <start_clients_import>
import * as clients from "@restatedev/restate-sdk-clients";
// <end_clients_import>

const myPlainTSFunction = async () => {
// <start_rpc_call_node>
// import * as clients from "@restatedev/restate-sdk-clients";
const rs = clients.connect({url: "http://localhost:8080"});
const greet = await rs.serviceClient(greeterService)
.greet({greeting: "Hi"});

const count = await rs.objectClient(greetCounterObject, "Mary")
.greet({greeting: "Hi"});

// You cannot **submit** workflows via request-response with the clients.
// You can call the other handlers of workflows via request-response.
const response = await rs.workflowClient(myWorkflow, "wf-id-1")
.myOtherHandler({input: "Hi"});
// <end_rpc_call_node>
}

const myPlainTSFunction2 = async () => {
// <start_one_way_call_node>
// import * as clients from "@restatedev/restate-sdk-clients";
const rs = clients.connect({url: "http://localhost:8080"});
await rs.serviceSendClient(greeterService)
.greet({greeting: "Hi"});

await rs.objectSendClient(greetCounterObject, "Mary")
.greet({greeting: "Hi"});

await rs.workflowClient(myWorkflow, "wf-id-1")
.workflowSubmit({input: "Hi"});
// You cannot yet do one-way calls to other handlers of the workflows
// <end_one_way_call_node>
}

const myPlainTSFunction3 = async () => {
// <start_delayed_call_node>
// import * as clients from "@restatedev/restate-sdk-clients";
const rs = clients.connect({url: "http://localhost:8080"});
await rs.serviceSendClient(greeterService)
.greet({greeting: "Hi"}, SendOpts.from({ delay: 1000 }));

await rs.objectSendClient(greetCounterObject, "Mary")
.greet({greeting: "Hi"}, SendOpts.from({ delay: 1000 }));

// You cannot call any workflow handlers via delayed calls with the clients.
// <end_delayed_call_node>
}

const servicesIdempotent = async () => {
const request = {greeting: "Hi"}
// <start_service_idempotent>
const rs = clients.connect({url: "http://localhost:8080"});
// <start_service_idempotent>
await rs.serviceSendClient(greeterService)
// withClass highlight-line
.greet(request, SendOpts.from({ idempotencyKey: "abcde" }));
Expand All @@ -63,49 +52,17 @@ const servicesIdempotent = async () => {
const servicesAttach = async () => {
const request = {greeting: "Hi"}
// <start_service_attach>
// import * as clients from "@restatedev/restate-sdk-clients";
const rs = clients.connect({url: "http://localhost:8080"});
// Send a message
const handle = await rs.serviceSendClient(greeterService)
// mark
.greet(request, SendOpts.from({ idempotencyKey: "abcde" }));

// ... do something else ...

// Attach later to retrieve the result
// withClass highlight-line
// mark
const response = await rs.result(handle);
// <end_service_attach>
}

const workflowAttach = async () => {
// <start_workflow_attach>
const rs = clients.connect({url: "http://localhost:8080"});

// Submit the workflow
const handle = await rs.workflowClient(myWorkflow, "wf-id-1")
.workflowSubmit({input: "Hi"});

// ... do something else ...

// Attach by using the handle:
// withClass highlight-line
const result = await rs.result(handle);

// Or, attach by using the workflow ID (from another service):
// withClass highlight-line
const result2 = await rs.workflowClient(myWorkflow, "wf-id-1").workflowAttach();
// <end_workflow_attach>
}

const workflowPeek = async () => {
// <start_workflow_peek>
const rs = clients.connect({url: "http://localhost:8080"});

// Peek at the output by using the workflow ID (from another service):
// withClass highlight-line
const output = await rs.workflowClient(myWorkflow, "wf-id-1").workflowOutput();
// If the workflow is ready, do something with the result
if (output.ready) {
console.log(output.result);
}
// <end_workflow_peek>
}
113 changes: 113 additions & 0 deletions docs/develop/java/clients.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
sidebar_position: 13
description: "Use the clients to invoke handlers programmatically."
---

import Admonition from '@theme/Admonition';
import {Step} from "../../../src/components/Stepper";

# Clients
The Restate SDK client library lets you invoke Restate handlers from anywhere in your application.
Use this only in non-Restate services without access to the Restate Context.

<Admonition type="note" title={"Use the Restate Context"}>
Always [invoke handlers via the context](/develop/java/service-communication), if you have access to it.
Restate then attaches information about the invocation to the parent invocation.
</Admonition>

## Invoking handlers with the SDK clients

Invoke a handler programmatically with the SDK clients as follows:

<Step stepLabel="1" title="Add the dependency to your project">
```kotlin
implementation("dev.restate:sdk-common:VAR::TYPESCRIPT_SDK_VERSION")
```
</Step>
<Step stepLabel="2" title={<span><a href={"/operate/registration"}>Register the service</a> you want to invoke.</span>}/>
<Step stepLabel="3" title="Connect to Restate and invoke the handler with your preferred semantics">
<CH.Spotlight start={1} className="durable-promises">

```java
CODE_LOAD::java/src/main/java/operate/invocations/Ingress.java#<start_rpc_java>-<end_rpc_java>
```

---

**Request-response invocations** allow you to wait on a response from the handler.

---

```java
CODE_LOAD::java/src/main/java/operate/invocations/Ingress.java#<start_one_way_call_java>-<end_one_way_call_java>
```

**One-way invocations** allow you to send a message without waiting for a response.

---

```java
CODE_LOAD::java/src/main/java/operate/invocations/Ingress.java#<start_delayed_call_java>-<end_delayed_call_java>
```

**Delayed invocations** allow you to schedule an invocation for a later point in time.

</CH.Spotlight>
</Step>

<Admonition type="info" title="Invoking workflows">
[Have a look at the workflow documentation to learn what's different when invoking workflows.](/develop/java/workflows#submitting-workflows-with-sdk-clients)
</Admonition>
## Invoke a handler idempotently
To make a service call idempotent, you can use the idempotency key feature.
Add the idempotency key [to the header](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/) via:
```java
CODE_LOAD::java/src/main/java/operate/invocations/Ingress.java#<start_service_idempotent>-<end_service_idempotent>
```
After the invocation completes, Restate persists the response for a retention period of one day (24 hours).
If you re-invoke the service with the same idempotency key within 24 hours, Restate sends back the same response and doesn't re-execute the request to the service.

<Admonition type="tip" title={"Make any service call idempotent by using Restate"}>
By using Restate and an idempotency key, you can make any service call idempotent, without any extra code or setup.
This is a very powerful feature to ensure that your system stays consistent and doesn't perform the same operation multiple times.
</Admonition>
<Admonition type="note" title={"Adding headers to the request"}>
The call options, with which we set the idempotency key, also let you add other headers to the request.
</Admonition>
<details className={"grey-details"}>
<summary> Tuning retention time</summary>
<CH.Section>
You can tune the [retention time](focus://3) on a service-level by using the [Admin API](focus://1[15:28]) ([docs](/references/admin-api#tag/service/operation/modify_service)):
```shell
curl -X PATCH localhost:9070/services/MyService \
-H 'content-type: application/json' \
-d '{"idempotency_retention": "2days"}'
```
The [retention time](focus://3) is in [humantime format](https://docs.rs/humantime/latest/humantime/).
</CH.Section>
</details>
## Retrieve result of invocations and workflows
You can use the client library to retrieve the results of invocations **with an idempotency key** or workflows.
You can:
- Attach to an ongoing invocation and retrieve the result when it finishes.
- Peek at the output of a running invocation or workflow, to check if it has finished and optionally retrieve the result.
You can use the invocation ID to attach or peek at a service/object invocation that used an idempotency key:
```java
CODE_LOAD::java/src/main/java/operate/invocations/Ingress.java#<start_service_attach>-<end_service_attach>
```
<Admonition type={"info"} title={"Attaching to workflows"}>
[Have a look at the workflow documentation to learn how to attach to workflow executions.](/develop/java/workflows#submitting-workflows-with-sdk-clients)
</Admonition>
Loading

0 comments on commit 7751826

Please sign in to comment.