diff --git a/code_snippets/java/src/main/java/develop/workflows/UserManagementService.java b/code_snippets/java/src/main/java/develop/workflows/UserManagementService.java index 5e6e9598..068cf446 100644 --- a/code_snippets/java/src/main/java/develop/workflows/UserManagementService.java +++ b/code_snippets/java/src/main/java/develop/workflows/UserManagementService.java @@ -4,7 +4,6 @@ import dev.restate.sdk.annotation.Handler; import dev.restate.sdk.annotation.VirtualObject; - @VirtualObject public class UserManagementService { // diff --git a/code_snippets/kotlin/build.gradle.kts b/code_snippets/kotlin/build.gradle.kts index 48614a40..d4992102 100644 --- a/code_snippets/kotlin/build.gradle.kts +++ b/code_snippets/kotlin/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { // Restate SDK implementation("dev.restate:sdk-api-kotlin:$restateVersion") + implementation("dev.restate:sdk-common:$restateVersion") implementation("dev.restate:sdk-http-vertx:$restateVersion") implementation("dev.restate:sdk-lambda:$restateVersion") diff --git a/code_snippets/kotlin/src/main/kotlin/develop/workflows/SignupWorkflow.kt b/code_snippets/kotlin/src/main/kotlin/develop/workflows/SignupWorkflow.kt new file mode 100644 index 00000000..0a2126d9 --- /dev/null +++ b/code_snippets/kotlin/src/main/kotlin/develop/workflows/SignupWorkflow.kt @@ -0,0 +1,64 @@ +package develop.workflows + +import dev.restate.sdk.annotation.Shared +import dev.restate.sdk.annotation.Workflow +import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder +import dev.restate.sdk.kotlin.* +import develop.workflows.Utils.Companion.sendEmailWithLink +import kotlinx.serialization.Serializable + +// +@Workflow +class SignupWorkflow { + + companion object { + private val EMAIL_CLICKED = KtDurablePromiseKey.json("email_clicked") + private val STATUS = KtStateKey.json("status") + } + + @Workflow + suspend fun run(ctx: WorkflowContext, email: Email): Boolean { + val secret = ctx.random().nextUUID().toString() + ctx.set(STATUS, "Generated secret") + + ctx.runBlock ("send email") { + sendEmailWithLink(email, secret) + } + + val clickSecret = ctx.promise(EMAIL_CLICKED) + .awaitable() + .await() + ctx.set(STATUS, "Clicked email") + + return clickSecret == secret + } + + @Shared + suspend fun click(ctx: SharedWorkflowContext, secret: String) { + ctx.promiseHandle(EMAIL_CLICKED).resolve(secret) + } + + @Shared + suspend fun getStatus(ctx: SharedWorkflowContext): String? { + return ctx.get(STATUS) + } +} + +fun main() { + RestateHttpEndpointBuilder + .builder() + .bind(SignupWorkflow()) + .buildAndListen() +} +// + +@Serializable +data class Email(val email: String) + +class Utils { + companion object { + fun sendEmailWithLink(email: Email, secret: String){ + + } + } +} \ No newline at end of file diff --git a/code_snippets/kotlin/src/main/kotlin/develop/workflows/UserManagementService.kt b/code_snippets/kotlin/src/main/kotlin/develop/workflows/UserManagementService.kt new file mode 100644 index 00000000..c0e6780d --- /dev/null +++ b/code_snippets/kotlin/src/main/kotlin/develop/workflows/UserManagementService.kt @@ -0,0 +1,27 @@ +package develop.workflows + +import dev.restate.sdk.annotation.Handler; +import dev.restate.sdk.annotation.VirtualObject; +import dev.restate.sdk.kotlin.ObjectContext + +@VirtualObject +class UserManagementService { + // + @Handler + suspend fun setup(ctx: ObjectContext, email: Email) { + // focus(1:3) + val result = SignupWorkflowClient.fromContext(ctx, "someone") + .run(email) + .await() + + } + + @Handler + suspend fun queryStatus(ctx: ObjectContext) { + // focus(1:3) + val status = SignupWorkflowClient.fromContext(ctx, "someone") + .getStatus() + .await() + } + // +} diff --git a/code_snippets/kotlin/src/main/kotlin/develop/workflows/Utils.kt b/code_snippets/kotlin/src/main/kotlin/develop/workflows/Utils.kt new file mode 100644 index 00000000..62b63dce --- /dev/null +++ b/code_snippets/kotlin/src/main/kotlin/develop/workflows/Utils.kt @@ -0,0 +1,2 @@ +package develop.workflows + diff --git a/code_snippets/kotlin/src/main/kotlin/develop/workflows/WorkflowSubmitter.kt b/code_snippets/kotlin/src/main/kotlin/develop/workflows/WorkflowSubmitter.kt new file mode 100644 index 00000000..7f4ecd2f --- /dev/null +++ b/code_snippets/kotlin/src/main/kotlin/develop/workflows/WorkflowSubmitter.kt @@ -0,0 +1,43 @@ +package develop.workflows + +import dev.restate.sdk.client.Client +import dev.restate.sdk.client.SendResponse + +class WorkflowSubmitter { + + suspend fun submitWorkflow(email: Email){ + // + val restate = Client.connect("http://localhost:8080") + val handle: SendResponse = SignupWorkflowClient + .fromClient(restate, "someone") + .submit(email) + // + + // + val status = SignupWorkflowClient + .fromClient(restate, "someone") + .getStatus() + // + + // + + // + + // + // Option 1: attach and wait for result + val result = SignupWorkflowClient + .fromClient(restate, "someone") + .workflowHandle() + .attach() + + // Option 2: peek to check if ready + val peekOutput = SignupWorkflowClient + .fromClient(restate, "someone") + .workflowHandle() + .output + if (peekOutput.isReady) { + val result2 = peekOutput.value + } + // + } +} \ No newline at end of file diff --git a/docs/develop/java/workflows.mdx b/docs/develop/java/workflows.mdx index 2a76f49e..bee0e22e 100644 --- a/docs/develop/java/workflows.mdx +++ b/docs/develop/java/workflows.mdx @@ -3,6 +3,8 @@ sidebar_position: 7 description: "Find out how you can implement workflows with the Restate SDK." --- +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; import Admonition from '@theme/Admonition'; # Workflows @@ -24,7 +26,9 @@ A workflow can be seen as a special type of [Virtual Object](/concepts/services# ## Implementing workflows Have a look at the code example to get a better understanding of how workflows are implemented: - + + + ### The `run` handler @@ -32,7 +36,7 @@ Have a look at the code example to get a better understanding of how workflows a This handler has access to the same SDK features as Service and Virtual Object handlers. For example, use [`ctx.run`](/develop/java/journaling-results#journaled-actions) to log intermediate results in Restate and avoid re-execution on replay. - ```java SignupWorkflow.java mark=9:25 + ```java SignupWorkflow.java mark=8:23 CODE_LOAD::java/src/main/java/develop/workflows/SignupWorkflow.java ``` @@ -45,7 +49,7 @@ Have a look at the code example to get a better understanding of how workflows a Every workflow execution can be seen as a new object, so the state is isolated to a single workflow execution. The state can only be mutated by the `run` handler of the workflow. The other handlers can only read the state. - ```java SignupWorkflow.java mark=31:34 + ```java SignupWorkflow.java mark=30:33 ``` --- @@ -59,7 +63,7 @@ Have a look at the code example to get a better understanding of how workflows a 1. Create a promise in your `run` handler that is durable and distributed 2. Resolve or reject the promise in any other handler in the workflow. This can be done at most one time. - ```java SignupWorkflow.java mark=18,26:29 + ```java SignupWorkflow.java mark=17:19,25:28 ``` --- @@ -69,18 +73,72 @@ Have a look at the code example to get a better understanding of how workflows a You serve workflows in the same way as Services and Virtual Objects: by binding them to an [HTTP endpoint](/develop/java/serving#creating-an-http-endpoint) or [AWS Lambda handler](/develop/java/serving#creating-a-lambda-handler). Make sure you [register the endpoint or Lambda handler](/operate/registration) in Restate before invoking it. - ```java SignupWorkflow.java mark=36:40 - + ```java SignupWorkflow.java mark=35:39 ``` + + + + + ### The `run` handler + + Every workflow needs a `run` handler. + This handler has access to the same SDK features as Service and Virtual Object handlers. + For example, use [`ctx.run`](/develop/java/journaling-results#journaled-actions) to log intermediate results in Restate and avoid re-execution on replay. + + ```kotlin SignupWorkflow.kt mark=9:24 + CODE_LOAD::kotlin/src/main/kotlin/develop/workflows/SignupWorkflow.kt + ``` + + --- + + ### Querying workflows + + Similar to Virtual Objects, you can retrieve the [K/V state](/develop/java/state#retrieving-state) of workflows via the other handlers defined in the workflow definition, + For example, here we expose the status of the workflow to external clients. + Every workflow execution can be seen as a new object, so the state is isolated to a single workflow execution. + The state can only be mutated by the `run` handler of the workflow. The other handlers can only read the state. + + ```kotlin SignupWorkflow.kt mark=31:34 + ``` + + --- + + ### Signaling workflows + + You can use Durable Promises to interact with your running workflows: to let the workflow block until an event occurs, or to send a signal / information into or out of a running workflow. + These promises are durable and distributed, meaning they survive crashes and can be resolved or rejected by any handler in the workflow. + + Do the following: + 1. Create a promise in your `run` handler that is durable and distributed + 2. Resolve or reject the promise in any other handler in the workflow. This can be done at most one time. + + ```kotlin SignupWorkflow.kt mark=18:20,26:29 + ``` + + --- + + ### Serving and registering workflows + + You serve workflows in the same way as Services and Virtual Objects: by binding them to an [HTTP endpoint](/develop/java/serving#creating-an-http-endpoint) or [AWS Lambda handler](/develop/java/serving#creating-a-lambda-handler). + Make sure you [register the endpoint or Lambda handler](/operate/registration) in Restate before invoking it. + + ```kotlin SignupWorkflow.kt mark=38:41 + ``` + + + + + [Check out some examples of workflows-as-code with Restate on the use case page](/use-cases/workflows). ## Submitting workflows with SDK clients - + + [**Submit**](/develop/java/clients): This returns a handle to the workflow once it has been registered in Restate. @@ -104,16 +162,49 @@ Have a look at the code example to get a better understanding of how workflows a [**Attach/peek**](/develop/java/clients#retrieve-result-of-invocations-and-workflows): - This lets you retrieve the result of a workflow or check if it's finished. - Use `.getOutput()` to peek whether the result is ready. + This lets you attach to a workflow and wait for it to finish, or to peek whether the result is ready. ```java CODE_LOAD::java/src/main/java/develop/workflows/WorkflowSubmitter.java#- ``` + + + + [**Submit**](/develop/java/clients): + This returns a handle to the workflow once it has been registered in Restate. + You can only submit once per workflow ID (here `"someone"`). + + ```kotlin + CODE_LOAD::kotlin/src/main/kotlin/develop/workflows/WorkflowSubmitter.kt#- + ``` + + + + [**Query/signal**](/develop/java/clients): + Call the other handlers of the workflow in the same way as for Virtual Object handlers. + Use `send()` for one-way calls. + + ```kotlin + CODE_LOAD::kotlin/src/main/kotlin/develop/workflows/WorkflowSubmitter.kt#- + ``` + + + + + [**Attach/peek**](/develop/java/clients#retrieve-result-of-invocations-and-workflows): + This lets you attach to a workflow and wait for it to finish, or to peek whether the result is ready. + + ```kotlin + CODE_LOAD::kotlin/src/main/kotlin/develop/workflows/WorkflowSubmitter.kt#- + ``` + + + ## Submitting workflows from a Restate service - + + [**Submit/query/signal**](/develop/java/service-communication): @@ -122,10 +213,26 @@ Have a look at the code example to get a better understanding of how workflows a Use `.send()` for to call the handler without waiting for the result. You can only call the `run` handler (submit) once per workflow ID (here `"someone"`). - ```java + ```kotlin CODE_LOAD::java/src/main/java/develop/workflows/UserManagementService.java ``` + + + + + [**Submit/query/signal**](/develop/java/service-communication): + Use the generated client to call any workflow handler in the same way as for Services and Virtual Objects. + This returns the result of the workflow/handler once it has finished. + Use `.send()` for to call the handler without waiting for the result. + You can only call the `run` handler (submit) once per workflow ID (here `"someone"`). + + ```kotlin + CODE_LOAD::kotlin/src/main/kotlin/develop/workflows/UserManagementService.kt + ``` + + + ## Submitting workflows over HTTP