diff --git a/basics/basics-java/README.md b/basics/basics-java/README.md index b0b1997b..f5622a29 100644 --- a/basics/basics-java/README.md +++ b/basics/basics-java/README.md @@ -25,6 +25,11 @@ about how they work and how they can be run. ./gradlew -PmainClass=durable_execution_compensation.RoleUpdateService run ``` +* **[Workflows](workflows/SignupWorkflow.java):** Workflows are durable execution tasks that can + be submitted and awaited. They have an identity and can be signaled and queried + through durable promises. The example is a user-signup flow that takes multiple + operations, including verifying the email address. + * **[Virtual Objects](virtual_objects/GreeterObject.java):** Stateful serverless objects to manage durable consistent state and state-manipulating logic. ```shell diff --git a/basics/basics-java/src/main/java/utils/ExampleStubs.java b/basics/basics-java/src/main/java/utils/ExampleStubs.java index 589b41f2..f1bc122e 100644 --- a/basics/basics-java/src/main/java/utils/ExampleStubs.java +++ b/basics/basics-java/src/main/java/utils/ExampleStubs.java @@ -74,4 +74,9 @@ public static String setUserPermissions(String userId, String permissions) { public static void provisionResources(String userId, String role, String resources){} + public static void createUserEntry(User user){ + + } + public static void sendEmailWithLink(String email, String secret){ + } } diff --git a/basics/basics-java/src/main/java/utils/User.java b/basics/basics-java/src/main/java/utils/User.java new file mode 100644 index 00000000..905a75fb --- /dev/null +++ b/basics/basics-java/src/main/java/utils/User.java @@ -0,0 +1,31 @@ +package utils; + +public class User { + + private String name; + private String email; + + public User( + String email, + String name + ){ + this.email = email; + this.name = name; + } + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/basics/basics-java/src/main/java/workflows/SignupWorkflow.java b/basics/basics-java/src/main/java/workflows/SignupWorkflow.java new file mode 100644 index 00000000..8ba1458f --- /dev/null +++ b/basics/basics-java/src/main/java/workflows/SignupWorkflow.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH + * + * This file is part of the Restate Examples for the Node.js/TypeScript SDK, + * which is released under the MIT license. + * + * You can find a copy of the license in the file LICENSE + * in the root directory of this repository or package or at + * https://github.com/restatedev/examples/blob/main/LICENSE + */ +package workflows; + +import dev.restate.sdk.JsonSerdes; +import dev.restate.sdk.SharedWorkflowContext; +import dev.restate.sdk.WorkflowContext; +import dev.restate.sdk.annotation.Shared; +import dev.restate.sdk.annotation.Workflow; +import dev.restate.sdk.common.DurablePromiseKey; +import dev.restate.sdk.common.StateKey; +import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; +import utils.User; + +import static utils.ExampleStubs.createUserEntry; +import static utils.ExampleStubs.sendEmailWithLink; + +// +// A simple workflow for a user signup and email verification. +// +// - the main workflow is in the run() method +// - any number of other methods can be added to implement interactions +// with the workflow. +// +// Workflow instances always have a unique ID that identifies the workflow execution. +// Each workflow instance (ID) can run only once (to success or failure). +// +@Workflow +public class SignupWorkflow { + + // References to K/V state and promises stored in Restate + private static final DurablePromiseKey EMAIL_CLICKED = + DurablePromiseKey.of("email_clicked", JsonSerdes.STRING); + private static final StateKey ONBOARDING_STATUS = + StateKey.of("status", JsonSerdes.STRING); + + @Workflow + public boolean run(WorkflowContext ctx, User user) { + + // Durably executed action; write to other system + ctx.run(() -> createUserEntry(user)); + + // Store some K/V state; can be retrieved from other handlers + ctx.set(ONBOARDING_STATUS, "Created user"); + + // Sent user email with verification link + String secret = ctx.random().nextUUID().toString(); + ctx.run(() -> sendEmailWithLink(user.getEmail(), secret)); + ctx.set(ONBOARDING_STATUS, "Verifying user"); + + // Wait until user clicked email verification link + // Resolved or rejected by the other handlers + String clickSecret = + ctx.promise(EMAIL_CLICKED) + .awaitable() + .await(); + ctx.set(ONBOARDING_STATUS, "Link clicked"); + + return clickSecret.equals(secret); + } + + + @Shared + public void click(SharedWorkflowContext ctx, String secret) { + // Resolve the promise with the result secret + ctx.promiseHandle(EMAIL_CLICKED).resolve(secret); + } + + @Shared + public String getStatus(SharedWorkflowContext ctx) { + // Get the onboarding status of the user + return ctx.get(ONBOARDING_STATUS).orElse("Unknown"); + } + + + public static void main(String[] args) { + RestateHttpEndpointBuilder.builder() + .bind(new SignupWorkflow()) + .buildAndListen(); + } +} \ No newline at end of file diff --git a/basics/basics-typescript/src/3_workflows.ts b/basics/basics-typescript/src/3_workflows.ts index 841d4831..1c7254b5 100644 --- a/basics/basics-typescript/src/3_workflows.ts +++ b/basics/basics-typescript/src/3_workflows.ts @@ -35,12 +35,12 @@ const myWorkflow = restate.workflow({ ctx.set("stage", "Creating User"); // use all the standard durable execution features here - await ctx.run(() => createUserEntry({userId, name})); + await ctx.run(() => createUserEntry({name, email})); ctx.set("stage", "Email Verification"); // send the email with the verification secret - const secret = await ctx.run(() => crypto.randomUUID()); + const secret = ctx.rand.uuidv4(); ctx.run(() => sendEmailWithLink({email, secret})); try { diff --git a/basics/basics-typescript/src/utils/workflow_stubs.ts b/basics/basics-typescript/src/utils/workflow_stubs.ts index aa62c3db..6c4a669a 100644 --- a/basics/basics-typescript/src/utils/workflow_stubs.ts +++ b/basics/basics-typescript/src/utils/workflow_stubs.ts @@ -9,6 +9,6 @@ * https://github.com/restatedev/examples/blob/main/LICENSE */ -export async function createUserEntry(entry: { userId: string; name: string }) {} +export async function createUserEntry(entry: { name: string, email: string }) {} export async function sendEmailWithLink(details: { email: string; secret: string }) {}