diff --git a/templates/bun/src/index.ts b/templates/bun/src/index.ts index 94d991d5..8232beea 100644 --- a/templates/bun/src/index.ts +++ b/templates/bun/src/index.ts @@ -1,20 +1,27 @@ -import { Context, endpoint, service } from "@restatedev/restate-sdk/fetch"; +import * as restate from "@restatedev/restate-sdk/fetch"; +import {sendNotification, sendReminder} from "./utils"; -// Template of a Restate service and handler -// -// Have a look at the TS QuickStart: https://docs.restate.dev/get_started/quickstart?sdk=ts -// +const handler = restate + .endpoint() + .bind( + restate.service({ + name: "Greeter", + handlers: { + greet: async (ctx: restate.Context, name: string) => { -const greeter = service({ - name: "Greeter", - handlers: { - greet: async (ctx: Context, greeting: string) => { - return `${greeting}!`; - }, - }, -}); + // Durably execute a set of steps; resilient against failures + const greetingId = ctx.rand.uuidv4(); + await ctx.run(() => sendNotification(greetingId, name)); + await ctx.sleep(1000); + await ctx.run(() => sendReminder(greetingId)); -const handler = endpoint().bind(greeter).handler(); + // Respond to caller + return `You said hi to ${name}!`; + }, + }, + }), + ) + .handler(); const server = Bun.serve({ port: 9080, diff --git a/templates/bun/src/utils.ts b/templates/bun/src/utils.ts new file mode 100644 index 00000000..df9bc2b8 --- /dev/null +++ b/templates/bun/src/utils.ts @@ -0,0 +1,19 @@ + +// You can remove this file. +// It's only purpose is providing stubs for the template. + +export function sendNotification(greetingId: string, name: string) { + if (Math.random() < 0.5) { // 50% chance of failure + console.error(`👻 Failed to send notification: ${greetingId} - ${name}`); + throw new Error(`Failed to send notification ${greetingId} - ${name}`); + } + console.log(`Notification sent: ${greetingId} - ${name}`); +} + +export function sendReminder(greetingId: string) { + if (Math.random() < 0.5) { // 50% chance of failure + console.error(`👻 Failed to send reminder: ${greetingId}`); + throw new Error(`Failed to send reminder: ${greetingId}`); + } + console.log(`Reminder sent: ${greetingId}`); +} \ No newline at end of file diff --git a/templates/cloudflare-worker/src/index.ts b/templates/cloudflare-worker/src/index.ts index c1340170..41c2ea20 100644 --- a/templates/cloudflare-worker/src/index.ts +++ b/templates/cloudflare-worker/src/index.ts @@ -1,17 +1,23 @@ -import { Context, endpoint, service } from "@restatedev/restate-sdk-cloudflare-workers/fetch"; +import * as restate from "@restatedev/restate-sdk-cloudflare-workers/fetch"; +import { sendNotification, sendReminder } from "./utils.js"; -// Template of a Restate service and handler -// -// Have a look at the TS QuickStart: https://docs.restate.dev/get_started/quickstart?sdk=ts -// +export default restate + .endpoint() + .bind( + restate.service({ + name: "greeter", + handlers: { + greet: async (ctx: restate.Context, name: string) => { + // Durably execute a set of steps; resilient against failures + const greetingId = ctx.rand.uuidv4(); + await ctx.run(() => sendNotification(greetingId, name)); + await ctx.sleep(1000); + await ctx.run(() => sendReminder(greetingId)); -const greeter = service({ - name: "greeter", - handlers: { - greet: async (ctx: Context, greeting: string) => { - return `${greeting}!`; - }, - }, -}); - -export default endpoint().bind(greeter).handler(); + // Respond to caller + return `You said hi to ${name}!`; + }, + }, + }), + ) + .handler(); diff --git a/templates/cloudflare-worker/src/utils.ts b/templates/cloudflare-worker/src/utils.ts new file mode 100644 index 00000000..eb892f77 --- /dev/null +++ b/templates/cloudflare-worker/src/utils.ts @@ -0,0 +1,20 @@ +// You can remove this file. +// It's only purpose is providing stubs for the template. + +export function sendNotification(greetingId: string, name: string) { + if (Math.random() < 0.5) { + // 50% chance of failure + console.error(`👻 Failed to send notification: ${greetingId} - ${name}`); + throw new Error(`Failed to send notification ${greetingId} - ${name}`); + } + console.log(`Notification sent: ${greetingId} - ${name}`); +} + +export function sendReminder(greetingId: string) { + if (Math.random() < 0.5) { + // 50% chance of failure + console.error(`👻 Failed to send reminder: ${greetingId}`); + throw new Error(`Failed to send reminder: ${greetingId}`); + } + console.log(`Reminder sent: ${greetingId}`); +} diff --git a/templates/deno/main.ts b/templates/deno/main.ts index e695a61d..fc00f8d9 100644 --- a/templates/deno/main.ts +++ b/templates/deno/main.ts @@ -1,23 +1,26 @@ -import { - Context, - endpoint, - service, -} from "npm:@restatedev/restate-sdk@^1.4.0/fetch"; +import * as restate from "npm:@restatedev/restate-sdk/fetch"; +import { sendNotification, sendReminder } from "./utils.ts"; -// Template of a Restate service and handler -// -// Have a look at the TS QuickStart: https://docs.restate.dev/get_started/quickstart?sdk=ts -// +const handler = restate + .endpoint() + .bind( + restate.service({ + name: "Greeter", + handlers: { + greet: async (ctx: restate.Context, name: string) => { + // Durably execute a set of steps; resilient against failures + const greetingId = ctx.rand.uuidv4(); + await ctx.run(() => sendNotification(greetingId, name)); + await ctx.sleep(1000); + await ctx.run(() => sendReminder(greetingId)); -const greeter = service({ - name: "Greeter", - handlers: { - greet: async (ctx: Context, greeting: string) => { - return `${greeting}!`; - }, - }, -}); - -const handler = endpoint().bind(greeter).bidirectional().handler(); + // Respond to caller + return `You said hi to ${name}!`; + }, + }, + }), + ) + .bidirectional() + .handler(); Deno.serve({ port: 9080 }, handler.fetch); diff --git a/templates/deno/utils.ts b/templates/deno/utils.ts new file mode 100644 index 00000000..eb892f77 --- /dev/null +++ b/templates/deno/utils.ts @@ -0,0 +1,20 @@ +// You can remove this file. +// It's only purpose is providing stubs for the template. + +export function sendNotification(greetingId: string, name: string) { + if (Math.random() < 0.5) { + // 50% chance of failure + console.error(`👻 Failed to send notification: ${greetingId} - ${name}`); + throw new Error(`Failed to send notification ${greetingId} - ${name}`); + } + console.log(`Notification sent: ${greetingId} - ${name}`); +} + +export function sendReminder(greetingId: string) { + if (Math.random() < 0.5) { + // 50% chance of failure + console.error(`👻 Failed to send reminder: ${greetingId}`); + throw new Error(`Failed to send reminder: ${greetingId}`); + } + console.log(`Reminder sent: ${greetingId}`); +} diff --git a/templates/go/go.mod b/templates/go/go.mod index 73cf07fe..a8b33a48 100644 --- a/templates/go/go.mod +++ b/templates/go/go.mod @@ -2,12 +2,17 @@ module github.com/restatedev/examples/templates/go go 1.22.5 -require github.com/restatedev/sdk-go v0.11.0 +require github.com/restatedev/sdk-go v0.12.0 require ( + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/templates/go/go.sum b/templates/go/go.sum index 5846a31a..ae879152 100644 --- a/templates/go/go.sum +++ b/templates/go/go.sum @@ -1,27 +1,36 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/restatedev/sdk-go v0.11.0 h1:5pN5XSlTxJBWHZEuY0fZPorKU6xncz5CPGnnCChgjxg= -github.com/restatedev/sdk-go v0.11.0/go.mod h1:6gHoU8pyP7YQfFWxKG2u94u/TSGen0qN7BWowWNDw4Y= +github.com/restatedev/sdk-go v0.12.0 h1:M9bdcsHvJALcg27U0s6V7HD1qNVFPh60XhM5H/n2zlQ= +github.com/restatedev/sdk-go v0.12.0/go.mod h1:Xalv67a5uOgGcbz7U1BgZQydCrsmENq2RAeTwTGXHng= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/templates/go/greeter.go b/templates/go/greeter.go index 06f451ec..b939349d 100644 --- a/templates/go/greeter.go +++ b/templates/go/greeter.go @@ -1,14 +1,33 @@ package main import ( - "fmt" - restate "github.com/restatedev/sdk-go" + "time" ) // Greeter is a struct which represents a Restate service; reflection will turn exported methods into service handlers type Greeter struct{} -func (Greeter) Greet(ctx restate.Context, greeting string) (string, error) { - return fmt.Sprintf("%s!", greeting), nil +func (Greeter) Greet(ctx restate.Context, name string) (string, error) { + // Durably execute a set of steps; resilient against failures + greetingId := restate.Rand(ctx).UUID().String() + + if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { + return restate.Void{}, SendNotification(greetingId, name) + }); err != nil { + return "", err + } + + if err := restate.Sleep(ctx, 1*time.Second); err != nil { + return "", err + } + + if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { + return restate.Void{}, SendReminder(greetingId) + }); err != nil { + return "", err + } + + // Respond to caller + return "You said hi to " + name + "!", nil } diff --git a/templates/go/utils.go b/templates/go/utils.go new file mode 100644 index 00000000..cf93dee5 --- /dev/null +++ b/templates/go/utils.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "math/rand" +) + +func SendNotification(greetingId string, name string) error { + if rand.Float32() < 0.5 { // 50% chance of failure + fmt.Printf("👻 Failed to send notification: %s - %s\n", greetingId, name) + return fmt.Errorf("failed to send notification: %s - %s", greetingId, name) + } + fmt.Printf("Notification sent: %s - %s\n", greetingId, name) + return nil +} + +func SendReminder(greetingId string) error { + if rand.Float32() < 0.5 { // 50% chance of failure + fmt.Printf("👻 Failed to send reminder: %s\n", greetingId) + return fmt.Errorf("failed to send reminder: %s", greetingId) + } + fmt.Printf("Reminder sent: %s\n", greetingId) + return nil +} diff --git a/templates/java-gradle/build.gradle.kts b/templates/java-gradle/build.gradle.kts index bf0b63f5..1b1981d0 100644 --- a/templates/java-gradle/build.gradle.kts +++ b/templates/java-gradle/build.gradle.kts @@ -35,4 +35,10 @@ application { tasks.named("test") { useJUnitPlatform() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } } \ No newline at end of file diff --git a/templates/java-gradle/src/main/java/my/example/Greeter.java b/templates/java-gradle/src/main/java/my/example/Greeter.java index 9ed68256..b0689535 100644 --- a/templates/java-gradle/src/main/java/my/example/Greeter.java +++ b/templates/java-gradle/src/main/java/my/example/Greeter.java @@ -1,14 +1,3 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * 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/ - */ - package my.example; import dev.restate.sdk.Context; @@ -16,21 +5,29 @@ import dev.restate.sdk.annotation.Service; import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; -/** - * Template of a Restate service and handler - * Have a look at the Java QuickStart to learn how to run this: https://docs.restate.dev/get_started/quickstart?sdk=java - */ +import java.time.Duration; + +import static my.example.Utils.sendNotification; +import static my.example.Utils.sendReminder; + @Service public class Greeter { - @Handler - public String greet(Context ctx, String greeting) { - return "Hello " + greeting; - } + @Handler + public String greet(Context ctx, String name) { + // Durably execute a set of steps; resilient against failures + String greetingId = ctx.random().nextUUID().toString(); + ctx.run(() -> sendNotification(greetingId, name)); + ctx.sleep(Duration.ofMillis(1000)); + ctx.run(() -> sendReminder(greetingId)); + + // Respond to caller + return "You said hi to " + name + "!"; + } - public static void main(String[] args) { - RestateHttpEndpointBuilder.builder() - .bind(new Greeter()) - .buildAndListen(); - } + public static void main(String[] args) { + RestateHttpEndpointBuilder.builder() + .bind(new Greeter()) + .buildAndListen(); + } } diff --git a/templates/java-gradle/src/main/java/my/example/Utils.java b/templates/java-gradle/src/main/java/my/example/Utils.java new file mode 100644 index 00000000..ab2141f7 --- /dev/null +++ b/templates/java-gradle/src/main/java/my/example/Utils.java @@ -0,0 +1,22 @@ +package my.example; + +// You can remove this file. +// It's only purpose is providing stubs for the template. + +class Utils { + public static void sendNotification(String greetingId, String name){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send notification: " + greetingId + " - " + name); + throw new RuntimeException("Failed to send notification: " + greetingId + " - " + name); + } + System.out.println("Notification sent: " + greetingId + " - " + name); + } + + public static void sendReminder(String greetingId){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send reminder: " + greetingId); + throw new RuntimeException("Failed to send reminder: " + greetingId); + } + System.out.println("Reminder sent: " + greetingId); + } +} \ No newline at end of file diff --git a/templates/java-gradle/src/test/java/my/example/GreeterTest.java b/templates/java-gradle/src/test/java/my/example/GreeterTest.java index f7954fe7..4000af49 100644 --- a/templates/java-gradle/src/test/java/my/example/GreeterTest.java +++ b/templates/java-gradle/src/test/java/my/example/GreeterTest.java @@ -29,6 +29,6 @@ void testGreet(@RestateClient Client ingressClient) { var client = GreeterClient.fromClient(ingressClient); String response = client.greet("Francesco"); - assertEquals(response, "Hello Francesco"); + assertEquals(response, "You said hi to Francesco!"); } } diff --git a/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java b/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java index c27923af..b96c30e9 100644 --- a/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java +++ b/templates/java-maven-quarkus/src/main/java/org/acme/Greeter.java @@ -1,14 +1,3 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * 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/ - */ - package org.acme; import dev.restate.sdk.Context; @@ -18,6 +7,11 @@ import jakarta.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.config.inject.ConfigProperty; +import java.time.Duration; + +import static org.acme.Utils.sendNotification; +import static org.acme.Utils.sendReminder; + @ApplicationScoped @Service public class Greeter { @@ -25,7 +19,14 @@ public class Greeter { @ConfigProperty(name = "greetingPrefix") String greetingPrefix; @Handler - public String greet(Context ctx, String greeting) { - return greetingPrefix + greeting; + public String greet(Context ctx, String name) { + // Durably execute a set of steps; resilient against failures + String greetingId = ctx.random().nextUUID().toString(); + ctx.run(() -> sendNotification(greetingId, name)); + ctx.sleep(Duration.ofMillis(1000)); + ctx.run(() -> sendReminder(greetingId)); + + // Respond to caller + return "You said " + greetingPrefix + " to " + name + "!"; } } diff --git a/templates/java-maven-quarkus/src/main/java/org/acme/Utils.java b/templates/java-maven-quarkus/src/main/java/org/acme/Utils.java new file mode 100644 index 00000000..9474e34d --- /dev/null +++ b/templates/java-maven-quarkus/src/main/java/org/acme/Utils.java @@ -0,0 +1,22 @@ +package org.acme; + +// You can remove this file. +// It's only purpose is providing stubs for the template. + +class Utils { + public static void sendNotification(String greetingId, String name){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send notification: " + greetingId + " - " + name); + throw new RuntimeException("Failed to send notification: " + greetingId + " - " + name); + } + System.out.println("Notification sent: " + greetingId + " - " + name); + } + + public static void sendReminder(String greetingId){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send reminder: " + greetingId); + throw new RuntimeException("Failed to send reminder: " + greetingId); + } + System.out.println("Reminder sent: " + greetingId); + } +} \ No newline at end of file diff --git a/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java b/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java index 78d264a4..ded0a2e2 100644 --- a/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java +++ b/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Greeter.java @@ -1,14 +1,3 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * 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/ - */ - package com.example.restatestarter; import dev.restate.sdk.Context; @@ -16,9 +5,11 @@ import dev.restate.sdk.springboot.RestateService; import org.springframework.beans.factory.annotation.Value; -/** - * Template of a Restate service and handler. - */ +import java.time.Duration; + +import static com.example.restatestarter.Utils.sendNotification; +import static com.example.restatestarter.Utils.sendReminder; + @RestateService public class Greeter { @@ -26,8 +17,14 @@ public class Greeter { private String greetingPrefix; @Handler - public String greet(Context ctx, String person) { - return greetingPrefix + person; - } + public String greet(Context ctx, String name) { + // Durably execute a set of steps; resilient against failures + String greetingId = ctx.random().nextUUID().toString(); + ctx.run(() -> sendNotification(greetingId, name)); + ctx.sleep(Duration.ofMillis(1000)); + ctx.run(() -> sendReminder(greetingId)); + // Respond to caller + return "You said " + greetingPrefix + " to " + name + "!"; + } } diff --git a/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Utils.java b/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Utils.java new file mode 100644 index 00000000..b522647d --- /dev/null +++ b/templates/java-maven-spring-boot/src/main/java/com/example/restatestarter/Utils.java @@ -0,0 +1,22 @@ +package com.example.restatestarter; + +// You can remove this file. +// It's only purpose is providing stubs for the template. + +class Utils { + public static void sendNotification(String greetingId, String name){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send notification: " + greetingId + " - " + name); + throw new RuntimeException("Failed to send notification: " + greetingId + " - " + name); + } + System.out.println("Notification sent: " + greetingId + " - " + name); + } + + public static void sendReminder(String greetingId){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send reminder: " + greetingId); + throw new RuntimeException("Failed to send reminder: " + greetingId); + } + System.out.println("Reminder sent: " + greetingId); + } +} \ No newline at end of file diff --git a/templates/java-maven-spring-boot/src/main/resources/application.properties b/templates/java-maven-spring-boot/src/main/resources/application.properties index a2d62b72..0372747d 100644 --- a/templates/java-maven-spring-boot/src/main/resources/application.properties +++ b/templates/java-maven-spring-boot/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=restate-starter -greetingPrefix=Hello Mr. +greetingPrefix=hi # Restate SDK port (to expose Greeter) restate.sdk.http.port=9080 diff --git a/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java b/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java index 107037c4..656b34a8 100644 --- a/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java +++ b/templates/java-maven-spring-boot/src/test/java/com/example/restatestarter/GreeterTest.java @@ -19,7 +19,7 @@ @SpringBootTest( classes = Greeter.class, - properties = {"greetingPrefix=Ciao "}) + properties = {"greetingPrefix=ciao"}) @RestateTest public class GreeterTest { @@ -30,6 +30,6 @@ public class GreeterTest { void greet(@RestateClient Client ingressClient) { var client = GreeterClient.fromClient(ingressClient); - assertThat(client.greet("Francesco")).isEqualTo("Ciao Francesco"); + assertThat(client.greet("Francesco")).isEqualTo("You said ciao to Francesco!"); } } diff --git a/templates/java-maven/src/main/java/my/example/Greeter.java b/templates/java-maven/src/main/java/my/example/Greeter.java index 9ed68256..77253007 100644 --- a/templates/java-maven/src/main/java/my/example/Greeter.java +++ b/templates/java-maven/src/main/java/my/example/Greeter.java @@ -1,14 +1,3 @@ -/* - * Copyright (c) 2024 - Restate Software, Inc., Restate GmbH - * - * This file is part of the Restate examples, - * 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/ - */ - package my.example; import dev.restate.sdk.Context; @@ -16,16 +5,24 @@ import dev.restate.sdk.annotation.Service; import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder; -/** - * Template of a Restate service and handler - * Have a look at the Java QuickStart to learn how to run this: https://docs.restate.dev/get_started/quickstart?sdk=java - */ +import java.time.Duration; + +import static my.example.Utils.sendNotification; +import static my.example.Utils.sendReminder; + @Service public class Greeter { @Handler - public String greet(Context ctx, String greeting) { - return "Hello " + greeting; + public String greet(Context ctx, String name) { + // Durably execute a set of steps; resilient against failures + String greetingId = ctx.random().nextUUID().toString(); + ctx.run(() -> sendNotification(greetingId, name)); + ctx.sleep(Duration.ofMillis(1000)); + ctx.run(() -> sendReminder(greetingId)); + + // Respond to caller + return "You said hi to " + name + "!"; } public static void main(String[] args) { diff --git a/templates/java-maven/src/main/java/my/example/Utils.java b/templates/java-maven/src/main/java/my/example/Utils.java new file mode 100644 index 00000000..ab2141f7 --- /dev/null +++ b/templates/java-maven/src/main/java/my/example/Utils.java @@ -0,0 +1,22 @@ +package my.example; + +// You can remove this file. +// It's only purpose is providing stubs for the template. + +class Utils { + public static void sendNotification(String greetingId, String name){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send notification: " + greetingId + " - " + name); + throw new RuntimeException("Failed to send notification: " + greetingId + " - " + name); + } + System.out.println("Notification sent: " + greetingId + " - " + name); + } + + public static void sendReminder(String greetingId){ + if (Math.random() < 0.5) { // 50% chance of failure + System.out.println("👻 Failed to send reminder: " + greetingId); + throw new RuntimeException("Failed to send reminder: " + greetingId); + } + System.out.println("Reminder sent: " + greetingId); + } +} \ No newline at end of file diff --git a/templates/java-maven/src/test/java/my/example/GreeterTest.java b/templates/java-maven/src/test/java/my/example/GreeterTest.java index f7954fe7..4000af49 100644 --- a/templates/java-maven/src/test/java/my/example/GreeterTest.java +++ b/templates/java-maven/src/test/java/my/example/GreeterTest.java @@ -29,6 +29,6 @@ void testGreet(@RestateClient Client ingressClient) { var client = GreeterClient.fromClient(ingressClient); String response = client.greet("Francesco"); - assertEquals(response, "Hello Francesco"); + assertEquals(response, "You said hi to Francesco!"); } } diff --git a/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt b/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt index f2ada3ed..62a63545 100644 --- a/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt +++ b/templates/kotlin-gradle/src/main/kotlin/my/example/Greeter.kt @@ -4,18 +4,22 @@ import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.Service import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder import dev.restate.sdk.kotlin.Context +import dev.restate.sdk.kotlin.runBlock +import kotlin.time.Duration.Companion.seconds -/** - * Template of a Restate service and handler - * Have a look at the Kotlin QuickStart to learn how to run this: https://docs.restate.dev/get_started/quickstart?sdk=kotlin - */ @Service class Greeter { @Handler - suspend fun greet(ctx: Context, greeting: String): String { + suspend fun greet(ctx: Context, name: String): String { + // Durably execute a set of steps; resilient against failures + val greetingId = ctx.random().nextUUID().toString() + ctx.runBlock { sendNotification(greetingId, name) } + ctx.sleep(1.seconds) + ctx.runBlock { sendReminder(greetingId) } - return greeting + // Respond to caller + return "You said hi to $name!"; } } diff --git a/templates/kotlin-gradle/src/main/kotlin/my/example/utils.kt b/templates/kotlin-gradle/src/main/kotlin/my/example/utils.kt new file mode 100644 index 00000000..c406abf9 --- /dev/null +++ b/templates/kotlin-gradle/src/main/kotlin/my/example/utils.kt @@ -0,0 +1,17 @@ +package my.example + +fun sendNotification(greetingId: String, name: String) { + if (Math.random() < 0.5) { // 50% chance of failure + println("👻 Failed to send notification: $greetingId - $name") + throw Error("Failed to send notification: $greetingId - $name") + } + println("Notification sent: $greetingId - $name") +} + +fun sendReminder(greetingId: String) { + if (Math.random() < 0.5) { // 50% chance of failure + println("👻 Failed to send reminder: $greetingId") + throw Error("Failed to send reminder: $greetingId") + } + println("Reminder sent: $greetingId") +} \ No newline at end of file diff --git a/templates/python/example.py b/templates/python/example.py index c797e353..d3d0b6fc 100644 --- a/templates/python/example.py +++ b/templates/python/example.py @@ -1,20 +1,22 @@ -# -# Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH -# -# This file is part of the Restate examples, -# which is released under the MIT license. -# -# You can find a copy of the license in file LICENSE in the root -# directory of this repository or package, or at -# https://github.com/restatedev/sdk-typescript/blob/main/LICENSE - -from restate import Service, Context +import uuid import restate +from datetime import timedelta +from restate import Service, Context +from pydantic_models import GreetingRequest, Greeting +from utils import send_notification, send_reminder + +greeter = Service("Greeter") -greeter = Service("greeter") @greeter.handler() -async def greet(ctx: Context, name: str) -> str: - return f"Hello {name}!" +async def greet(ctx: Context, req: GreetingRequest) -> Greeting: + # Durably execute a set of steps; resilient against failures + greeting_id = await ctx.run("generate UUID", lambda: str(uuid.uuid4())) + await ctx.run("send notification", lambda: send_notification(greeting_id, req.name)) + await ctx.sleep(timedelta(seconds=1)) + await ctx.run("send reminder", lambda: send_reminder(greeting_id)) + + # Respond to caller + return Greeting(message=f"You said hi to {req.name}!") app = restate.app(services=[greeter]) diff --git a/templates/python/pydantic_models.py b/templates/python/pydantic_models.py new file mode 100644 index 00000000..39b371a3 --- /dev/null +++ b/templates/python/pydantic_models.py @@ -0,0 +1,12 @@ +# You can remove this file. +# It's only purpose is providing pydantic models for the template. +from pydantic import BaseModel + + +# You can also just use a typed dict, without Pydantic +class GreetingRequest(BaseModel): + name: str + + +class Greeting(BaseModel): + message: str diff --git a/templates/python/requirements.txt b/templates/python/requirements.txt index 9561699a..a0112ec3 100644 --- a/templates/python/requirements.txt +++ b/templates/python/requirements.txt @@ -1,2 +1,3 @@ hypercorn -restate_sdk==0.4.1 \ No newline at end of file +restate_sdk==0.4.1 +pydantic \ No newline at end of file diff --git a/templates/python/utils.py b/templates/python/utils.py new file mode 100644 index 00000000..885c55a2 --- /dev/null +++ b/templates/python/utils.py @@ -0,0 +1,18 @@ +import random + +# You can remove this file. +# It's only purpose is providing stubs for the template. + + +def send_notification(greeting_id: str, name: str): + if random.random() < 0.5: # 50% chance of failure + print(f"👻 Failed to send notification: {greeting_id} - {name}") + raise Exception(f"Failed to send notification: {greeting_id} - {name}") + print(f"Notification sent: {greeting_id} - {name}") + + +def send_reminder(greeting_id: str): + if random.random() < 0.5: # 50% chance of failure + print(f"👻 Failed to send reminder: {greeting_id}") + raise Exception(f"Failed to send reminder: {greeting_id}") + print(f"Reminder sent: {greeting_id}") diff --git a/templates/rust-shuttle/.gitignore b/templates/rust-shuttle/.gitignore index 4a28e9fa..0f138877 100644 --- a/templates/rust-shuttle/.gitignore +++ b/templates/rust-shuttle/.gitignore @@ -2,6 +2,8 @@ # will have compiled files and executables debug/ target/ +.idea +*.iml # These are backup files generated by rustfmt **/*.rs.bk diff --git a/templates/rust-shuttle/Cargo.lock b/templates/rust-shuttle/Cargo.lock index 4e7e6e19..c407b1fb 100644 --- a/templates/rust-shuttle/Cargo.lock +++ b/templates/rust-shuttle/Cargo.lock @@ -1468,8 +1468,10 @@ dependencies = [ name = "restate-shuttle-example" version = "0.0.1" dependencies = [ + "anyhow", "hyper 1.4.1", "hyper-util", + "rand", "restate-sdk", "shuttle-runtime", "tokio", diff --git a/templates/rust-shuttle/Cargo.toml b/templates/rust-shuttle/Cargo.toml index 705d71dd..1097fd10 100644 --- a/templates/rust-shuttle/Cargo.toml +++ b/templates/rust-shuttle/Cargo.toml @@ -10,3 +10,5 @@ shuttle-runtime = "0.47.0" hyper = { version = "1.4", features = ["http1", "server"] } hyper-util = { version = "0.1.7", features = ["http1", "server", "server-graceful"] } tracing = "0.1" +rand = "0.8.5" +anyhow = "1.0.86" diff --git a/templates/rust-shuttle/README.md b/templates/rust-shuttle/README.md index 8447ac80..b3cc635a 100644 --- a/templates/rust-shuttle/README.md +++ b/templates/rust-shuttle/README.md @@ -2,4 +2,14 @@ Sample project configuration of a Restate service using Rust and [shuttle.rs](https://www.shuttle.dev/). -Have a look at the [Rust Quickstart guide](https://docs.restate.dev/get_started/quickstart?sdk=rust) for more information on how to use this project. \ No newline at end of file +Have a look at the [Rust Quickstart guide](https://docs.restate.dev/get_started/quickstart?sdk=rust) for more information on how to use this project. + +Run with: +```shell +cargo shuttle run --port 9080 +``` + +Register the service with: +```shell +restate deployments register http://localhost:9080 --use-http1.1 +``` \ No newline at end of file diff --git a/templates/rust-shuttle/src/main.rs b/templates/rust-shuttle/src/main.rs index 4e5d131b..7bdca4fb 100644 --- a/templates/rust-shuttle/src/main.rs +++ b/templates/rust-shuttle/src/main.rs @@ -1,8 +1,11 @@ -use restate_sdk::prelude::*; - // Restate shuttle integration mod restate_shuttle; +mod utils; + +use restate_sdk::prelude::*; use restate_shuttle::RestateShuttleEndpoint; +use utils::{send_notification, send_reminder}; +use std::time::Duration; #[restate_sdk::service] trait Greeter { @@ -12,7 +15,14 @@ trait Greeter { struct GreeterImpl; impl Greeter for GreeterImpl { - async fn greet(&self, _: Context<'_>, name: String) -> HandlerResult { + async fn greet(&self, mut ctx: Context<'_>, name: String) -> HandlerResult { + // Durably execute a set of steps; resilient against failures + let greeting_id = ctx.rand_uuid().to_string(); + ctx.run(|| send_notification(&greeting_id, &name)).await?; + ctx.sleep(Duration::from_millis(1000)).await?; + ctx.run(|| send_reminder(&greeting_id)).await?; + + // Respond to caller Ok(format!("Greetings {name}")) } } diff --git a/templates/rust-shuttle/src/utils.rs b/templates/rust-shuttle/src/utils.rs new file mode 100644 index 00000000..698c24dd --- /dev/null +++ b/templates/rust-shuttle/src/utils.rs @@ -0,0 +1,21 @@ +use rand::random; +use anyhow::{anyhow, Result}; +use restate_sdk::errors::HandlerError; + +pub async fn send_notification(greeting_id: &str, name: &str) -> Result<(), HandlerError> { + if random::() < 0.5 { + println!("👻 Failed to send notification: {} - {}", greeting_id, name); + return Err(HandlerError::from(anyhow!("Failed to send notification: {} - {}", greeting_id, name))); + } + println!("Notification sent: {} - {}", greeting_id, name); + Ok(()) +} + +pub async fn send_reminder(greeting_id: &str)-> Result<(), HandlerError> { + if random::() < 0.5 { + println!("👻 Failed to send reminder: - {}", greeting_id); + return Err(HandlerError::from(anyhow!("Failed to send reminder: {}", greeting_id))); + } + println!("Reminder sent: {}", greeting_id); + Ok(()) +} \ No newline at end of file diff --git a/templates/rust/.gitignore b/templates/rust/.gitignore index 4a28e9fa..ade7254a 100644 --- a/templates/rust/.gitignore +++ b/templates/rust/.gitignore @@ -2,6 +2,8 @@ # will have compiled files and executables debug/ target/ +.idea/ +*.iml # These are backup files generated by rustfmt **/*.rs.bk diff --git a/templates/rust/Cargo.lock b/templates/rust/Cargo.lock index 4fa8143a..c241afeb 100644 --- a/templates/rust/Cargo.lock +++ b/templates/rust/Cargo.lock @@ -828,6 +828,8 @@ dependencies = [ name = "restate-example" version = "0.0.1" dependencies = [ + "anyhow", + "rand", "restate-sdk", "tokio", "tracing-subscriber", diff --git a/templates/rust/Cargo.toml b/templates/rust/Cargo.toml index 0e93698e..b67cf5f8 100644 --- a/templates/rust/Cargo.toml +++ b/templates/rust/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" restate-sdk = "0.3" tokio = { version = "1", features = ["full"] } tracing-subscriber = "0.3" +rand = "0.8.5" +anyhow = "1.0.86" diff --git a/templates/rust/src/main.rs b/templates/rust/src/main.rs index 9e0911ae..50214173 100644 --- a/templates/rust/src/main.rs +++ b/templates/rust/src/main.rs @@ -1,4 +1,8 @@ +mod utils; + use restate_sdk::prelude::*; +use std::time::Duration; +use utils::{send_notification, send_reminder}; #[restate_sdk::service] trait Greeter { @@ -8,7 +12,14 @@ trait Greeter { struct GreeterImpl; impl Greeter for GreeterImpl { - async fn greet(&self, _: Context<'_>, name: String) -> HandlerResult { + async fn greet(&self, mut ctx: Context<'_>, name: String) -> HandlerResult { + // Durably execute a set of steps; resilient against failures + let greeting_id = ctx.rand_uuid().to_string(); + ctx.run(|| send_notification(&greeting_id, &name)).await?; + ctx.sleep(Duration::from_millis(1000)).await?; + ctx.run(|| send_reminder(&greeting_id)).await?; + + // Respond to caller Ok(format!("Greetings {name}")) } } diff --git a/templates/rust/src/utils.rs b/templates/rust/src/utils.rs new file mode 100644 index 00000000..698c24dd --- /dev/null +++ b/templates/rust/src/utils.rs @@ -0,0 +1,21 @@ +use rand::random; +use anyhow::{anyhow, Result}; +use restate_sdk::errors::HandlerError; + +pub async fn send_notification(greeting_id: &str, name: &str) -> Result<(), HandlerError> { + if random::() < 0.5 { + println!("👻 Failed to send notification: {} - {}", greeting_id, name); + return Err(HandlerError::from(anyhow!("Failed to send notification: {} - {}", greeting_id, name))); + } + println!("Notification sent: {} - {}", greeting_id, name); + Ok(()) +} + +pub async fn send_reminder(greeting_id: &str)-> Result<(), HandlerError> { + if random::() < 0.5 { + println!("👻 Failed to send reminder: - {}", greeting_id); + return Err(HandlerError::from(anyhow!("Failed to send reminder: {}", greeting_id))); + } + println!("Reminder sent: {}", greeting_id); + Ok(()) +} \ No newline at end of file diff --git a/templates/typescript/src/app.ts b/templates/typescript/src/app.ts index 35162316..1d2337a7 100644 --- a/templates/typescript/src/app.ts +++ b/templates/typescript/src/app.ts @@ -1,21 +1,23 @@ import * as restate from "@restatedev/restate-sdk"; +import { sendNotification, sendReminder } from "./utils"; -// Template of a Restate service and handler -// -// Have a look at the TS QuickStart to learn how to run this: https://docs.restate.dev/get_started/quickstart?sdk=ts -// - -const greet = async (ctx: restate.Context, greeting: string) => { - return `${greeting}!`; -}; - -// Create the Restate server to accept requests restate .endpoint() .bind( restate.service({ name: "Greeter", - handlers: { greet }, + handlers: { + greet: async (ctx: restate.Context, name: string) => { + // Durably execute a set of steps; resilient against failures + const greetingId = ctx.rand.uuidv4(); + await ctx.run(() => sendNotification(greetingId, name)); + await ctx.sleep(1000); + await ctx.run(() => sendReminder(greetingId)); + + // Respond to caller + return `You said hi to ${name}!`; + }, + }, }), ) .listen(9080); diff --git a/templates/typescript/src/utils.ts b/templates/typescript/src/utils.ts new file mode 100644 index 00000000..eb892f77 --- /dev/null +++ b/templates/typescript/src/utils.ts @@ -0,0 +1,20 @@ +// You can remove this file. +// It's only purpose is providing stubs for the template. + +export function sendNotification(greetingId: string, name: string) { + if (Math.random() < 0.5) { + // 50% chance of failure + console.error(`👻 Failed to send notification: ${greetingId} - ${name}`); + throw new Error(`Failed to send notification ${greetingId} - ${name}`); + } + console.log(`Notification sent: ${greetingId} - ${name}`); +} + +export function sendReminder(greetingId: string) { + if (Math.random() < 0.5) { + // 50% chance of failure + console.error(`👻 Failed to send reminder: ${greetingId}`); + throw new Error(`Failed to send reminder: ${greetingId}`); + } + console.log(`Reminder sent: ${greetingId}`); +}