From b6800c873fb4215dfc5d5060adc6fe24a0839321 Mon Sep 17 00:00:00 2001 From: Jack Kleeman Date: Thu, 22 Aug 2024 15:14:24 +0100 Subject: [PATCH] Go in concepts pages --- code_snippets/go/concepts/foodordering.go | 76 ++++++++++++ code_snippets/go/concepts/invocations.go | 50 ++++++++ code_snippets/go/concepts/services/main.go | 115 ++++++++++++++++++ code_snippets/go/concepts/utils.go | 44 +++++++ .../go/concepts/virtualobjects/main.go | 71 +++++++++++ code_snippets/go/develop/kafka.go | 14 +++ docs/concepts/durable_building_blocks.mdx | 53 +++++++- docs/concepts/invocations.mdx | 43 ++++++- docs/concepts/services.mdx | 71 ++++++++++- docs/get_started/tour.mdx | 4 +- docs/invoke/kafka.mdx | 17 ++- docs/use-cases/event-processing.mdx | 5 +- docs/use-cases/microservice-orchestration.mdx | 5 +- src/components/FeatureWidget/index.tsx | 14 +++ 14 files changed, 571 insertions(+), 11 deletions(-) create mode 100644 code_snippets/go/concepts/foodordering.go create mode 100644 code_snippets/go/concepts/invocations.go create mode 100644 code_snippets/go/concepts/services/main.go create mode 100644 code_snippets/go/concepts/utils.go create mode 100644 code_snippets/go/concepts/virtualobjects/main.go create mode 100644 code_snippets/go/develop/kafka.go diff --git a/code_snippets/go/concepts/foodordering.go b/code_snippets/go/concepts/foodordering.go new file mode 100644 index 00000000..c571e4de --- /dev/null +++ b/code_snippets/go/concepts/foodordering.go @@ -0,0 +1,76 @@ +package concepts + +import restate "github.com/restatedev/sdk-go" + +type OrderProcessor struct{} + +// +// +func (OrderProcessor) Process(ctx restate.ObjectContext, order Order) error { + // + // 1. Set status + // + restate.Set(ctx, "status", Status_CREATED) + // + + // 2. Handle payment + // + token := restate.Rand(ctx).UUID().String() + paid, err := restate.Run(ctx, func(ctx restate.RunContext) (bool, error) { + return paymentClnt.Charge(ctx, order.Id, token, order.TotalCost) + }) + if err != nil { + return err + } + // + + if !paid { + // + restate.Set(ctx, "status", Status_REJECTED) + // + return nil + } + + // 3. Wait until the requested preparation time + // + restate.Set(ctx, "status", Status_SCHEDULED) + // + restate.Sleep(ctx, order.DeliveryDelay) + + // 4. Trigger preparation + // + preparationAwakeable := restate.Awakeable[restate.Void](ctx) + // + restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { + return restate.Void{}, restaurant.Prepare(order.Id, preparationAwakeable.Id()) + }) + // + // + // + restate.Set(ctx, "status", Status_IN_PREPARATION) + // + + // + if _, err := preparationAwakeable.Result(); err != nil { + return err + } + // + // + restate.Set(ctx, "status", Status_SCHEDULING_DELIVERY) + // + + // 5. Find a driver and start delivery + // + if _, err := restate.Object[restate.Void](ctx, "DeliveryManager", order.Id, "StartDelivery"). + Request(order); err != nil { + return err + } + // + // + restate.Set(ctx, "status", Status_DELIVERED) + // + + return nil +} + +// diff --git a/code_snippets/go/concepts/invocations.go b/code_snippets/go/concepts/invocations.go new file mode 100644 index 00000000..e0549ed4 --- /dev/null +++ b/code_snippets/go/concepts/invocations.go @@ -0,0 +1,50 @@ +package concepts + +import ( + "time" + + restate "github.com/restatedev/sdk-go" +) + +type MyService struct{} + +/* +// +func (MyService) MyRestateHandler(ctx restate.Context) error { + // focus + greet, err := restate. + // focus + Service[string](ctx, "Greeter", "Greet"). + // focus + Request("Hi") + return err +} + +// + +// +func (MyService) MyRestateHandler(ctx restate.Context) error { + // focus + restate. + // focus + ServiceSend(ctx, "Greeter", "Greet"). + // focus + Send("Hi") + return nil +} + +// +*/ + +// +func (MyService) MyRestateHandler(ctx restate.Context) error { + // focus + restate. + // focus + ServiceSend(ctx, "Greeter", "Greet"). + // focus + Send("Hi", restate.WithDelay(1*time.Second)) + return nil +} + +// diff --git a/code_snippets/go/concepts/services/main.go b/code_snippets/go/concepts/services/main.go new file mode 100644 index 00000000..5d80d23f --- /dev/null +++ b/code_snippets/go/concepts/services/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "context" + "fmt" + "log" + "math/rand" + "os" + + restate "github.com/restatedev/sdk-go" + "github.com/restatedev/sdk-go/server" +) + +type RoleUpdate struct{} + +// +// +func (RoleUpdate) ApplyRoleUpdate(ctx restate.Context, update UpdateRequest) error { + // + + // + success, err := restate.Run(ctx, func(ctx restate.RunContext) (bool, error) { + return applyUserRole(update.UserId, update.Role) + }) + if err != nil { + return err + } + // + // + if !success { + return nil + } + // + + // + for _, permission := range update.Permissions { + // + // + if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { + return restate.Void{}, applyPermission(update.UserId, permission) + }); err != nil { + return err + } + // + // + } + // + + return nil +} + +func main() { + if err := server.NewRestate(). + Bind(restate.Reflect(RoleUpdate{})). + Start(context.Background(), ":9080"); err != nil { + log.Fatal(err) + } +} + +// + +type UserRole struct { + RoleKey string + RoleDescription string +} + +type Permission struct { + PermissionKey string + Setting string +} + +type UpdateRequest struct { + UserId string + Role UserRole + Permissions []Permission +} + +var killProcess = os.Getenv("CRASH_PROCESS") == "true" + +func maybeCrash(probability float64) error { + if rand.Float64() < probability { + log.Println("A failure happened!") + + if killProcess { + log.Fatal("--- CRASHING THE PROCESS ---") + } else { + return fmt.Errorf("A failure happened!") + } + } + return nil +} + +func applyUserRole( + userId string, + userRole UserRole, +) (bool, error) { + if err := maybeCrash(0.3); err != nil { + return false, err + } + log.Printf(`>>> Applied role %s for user %s\n`, userRole.RoleKey, userId) + return true, nil +} + +func applyPermission( + userId string, + permission Permission, +) error { + if err := maybeCrash(0.2); err != nil { + return err + } + log.Printf( + ">>> Applied permission %s:%2 for user %s\n", permission.PermissionKey, permission.Setting, userId, + ) + return nil +} diff --git a/code_snippets/go/concepts/utils.go b/code_snippets/go/concepts/utils.go new file mode 100644 index 00000000..59124420 --- /dev/null +++ b/code_snippets/go/concepts/utils.go @@ -0,0 +1,44 @@ +package concepts + +import ( + "context" + "time" +) + +type Status int + +const ( + Status_NEW Status = iota + Status_CREATED + Status_SCHEDULED + Status_IN_PREPARATION + Status_SCHEDULING_DELIVERY + Status_WAITING_FOR_DRIVER + Status_IN_DELIVERY + Status_DELIVERED + Status_REJECTED + Status_CANCELLED + Status_UNKNOWN +) + +type Order struct { + Id string + TotalCost int + DeliveryDelay time.Duration +} + +type PaymentClient struct{} + +func (PaymentClient) Charge(ctx context.Context, id string, token string, amount int) (bool, error) { + return true, nil +} + +var paymentClnt PaymentClient + +type RestaurantClient struct{} + +func (RestaurantClient) Prepare(id string, cb string) error { + return nil +} + +var restaurant = RestaurantClient{} diff --git a/code_snippets/go/concepts/virtualobjects/main.go b/code_snippets/go/concepts/virtualobjects/main.go new file mode 100644 index 00000000..1328ea92 --- /dev/null +++ b/code_snippets/go/concepts/virtualobjects/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "fmt" + "log" + + restate "github.com/restatedev/sdk-go" + "github.com/restatedev/sdk-go/server" +) + +type Greeter struct{} + +// +// +// +func Greet(ctx restate.ObjectContext, greeting string) (string, error) { + // + // + // + count, err := restate.Get[int](ctx, "count") + if err != nil { + return "", err + } + count++ + restate.Set(ctx, "count", count) + // + // + return fmt.Sprintf( + "%s %s for the %d-th time.", + greeting, restate.Key(ctx), count, + ), nil + // +} + +// +// +func Ungreet(ctx restate.ObjectContext) (string, error) { + // + // + // + count, err := restate.Get[int](ctx, "count") + if err != nil { + return "", err + } + // + if count > 0 { + // + count-- + // + } + // + restate.Set(ctx, "count", count) + // + // + return fmt.Sprintf( + "Dear %s, taking one greeting back: %d.", + restate.Key(ctx), count, + ), nil + // +} + +func main() { + if err := server.NewRestate(). + Bind(restate.Reflect(Greeter{})). + Start(context.Background(), ":9080"); err != nil { + log.Fatal(err) + } +} + +// diff --git a/code_snippets/go/develop/kafka.go b/code_snippets/go/develop/kafka.go new file mode 100644 index 00000000..79965396 --- /dev/null +++ b/code_snippets/go/develop/kafka.go @@ -0,0 +1,14 @@ +package develop + +import restate "github.com/restatedev/sdk-go" + +type KafkaService struct{} + +func (KafkaService) MyEventHandler(ctx restate.Context, name string) error { + _ = + // + ctx.Request().Headers + // + + return nil +} diff --git a/docs/concepts/durable_building_blocks.mdx b/docs/concepts/durable_building_blocks.mdx index f366e54d..a30ab636 100644 --- a/docs/concepts/durable_building_blocks.mdx +++ b/docs/concepts/durable_building_blocks.mdx @@ -130,5 +130,56 @@ Let's have a look at a handler that processes food orders: - + + + + ### Durable functions + Handlers take part in durable execution, meaning that Restate keeps track of their progress and recovers them to the previously reached state in case of failures. + + + ```go orderprocessor.go + CODE_LOAD::go/concepts/foodordering.go?1 + ``` + + --- + + ### Durable RPCs and queues + Handlers can call other handlers in a resilient way, with or without waiting for the response. + When a failure happens, Restate handles retries and recovers partial progress. + + ```go orderprocessor.go + CODE_LOAD::go/concepts/foodordering.go?2 + ``` + + --- + + ### Durable promises and timers + Register promises in Restate to make them resilient to failures (e.g. webhooks, timers). + Restate lets the handler suspend while awaiting the promise, and invokes it again when the result is available. + A great match for function-as-a-service platforms. + + ```go orderprocessor.go + CODE_LOAD::go/concepts/foodordering.go?3 + ``` + + --- + ### Consistent K/V state + Persist application state in Restate with a simple concurrency model and no extra setup. Restate makes sure state remains consistent amid failures. + + + ```go orderprocessor.go + CODE_LOAD::go/concepts/foodordering.go?4 + ``` + --- + + ### Journaling actions + Store the result of an action in Restate. The result gets replayed in case of failures and the action is not executed again. + + ```go orderprocessor.go + CODE_LOAD::go/concepts/foodordering.go?5 + ``` + + + + diff --git a/docs/concepts/invocations.mdx b/docs/concepts/invocations.mdx index 67ff6eee..2c52005a 100644 --- a/docs/concepts/invocations.mdx +++ b/docs/concepts/invocations.mdx @@ -178,6 +178,47 @@ Invocations get a unique identifier. This identifier is used to track the progre Learn more about [service communication](/develop/java/service-communication) and the [SDK clients](/develop/java/clients) in the TypeScript SDK. + + + + **Request-response invocations** allow you to wait on a response from the handler. + + + + ```go restateservice.go + CODE_LOAD::go/concepts/invocations.go#rpc_call + ``` + + + + --- + + **One-way invocations** allow you to trigger an asynchronous action. + + + + ```go restateservice.go + CODE_LOAD::go/concepts/invocations.go#one_way_call + ``` + + + + --- + + **Delayed invocations** allow you to schedule an invocation for a later point in time. + + + + ```go restateservice.go + CODE_LOAD::go/concepts/invocations.go#delayed_call + ``` + + + + + + Learn more about [service communication](/develop/go/service-communication) in the Go SDK. + @@ -329,5 +370,3 @@ If necessary, you can register compensating actions in your handlers to ensure t For cancellations, Restate will gracefully stop the handler by executing all compensation actions. For kills, Restate will immediately stop the handler without executing any compensation actions. - - diff --git a/docs/concepts/services.mdx b/docs/concepts/services.mdx index 46410d57..eba76b07 100644 --- a/docs/concepts/services.mdx +++ b/docs/concepts/services.mdx @@ -23,7 +23,7 @@ This is what a Restate application looks like from a helicopter view: As you can see, handlers are bundled into services. Services run like regular RPC services (e.g. a NodeJS app in a Docker container). -Services can be written in any language for which there is an SDK available (currently [TypeScript](/category/typescript-sdk) and [Java/Kotlin](/category/javakotlin-sdk)). +Services can be written in any language for which there is an SDK available. There are three types of services in Restate: @@ -110,6 +110,44 @@ Services expose a collection of handlers: + + + + + Restate makes sure that **handlers run to completion**, even in the presence of failures. + Restate logs the **results of actions** in the system. + Restate takes care of retries and recovers the handler to the point where it failed. + + ```go + CODE_LOAD::go/concepts/services/main.go?1 + ``` + + --- + + The handlers of services are **independent** and can be **invoked concurrently**. + + ```go + CODE_LOAD::go/concepts/services/main.go?2 + ``` + + --- + + Handlers use the regular code and control flow, no custom DSLs. + + ```go + CODE_LOAD::go/concepts/services/main.go?3 + ``` + + --- + + Service handlers **don't have access to Restate's K/V store**. + + ```go + CODE_LOAD::go/concepts/services/main.go + ``` + + + In the example, we use a Restate service to update different systems and to make sure all updates are applied. @@ -182,6 +220,37 @@ Virtual objects expose a set of handlers with access to K/V state stored in Rest + + + + A virtual object is **uniquely identified and accessed by its key**. + + ```go + CODE_LOAD::go/concepts/virtualobjects/main.go?1 + ``` + + --- + + Each virtual object has access to its own **isolated K/V state**, stored in Restate. + The handlers of a virtual object can read and write to the state of the object. + Restate delivers the state together with the request to the virtual object, so virtual objects have their state locally accessible without requiring any database connection or lookup. + State is exclusive, and atomically committed with the method execution. + + ```go + CODE_LOAD::go/concepts/virtualobjects/main.go?2 + ``` + + --- + + To ensure consistent writes to the state, Restate provides **concurrency guarantees**: at most one handler can execute at a time for a given virtual object. + This can also be used for example to implement a locking mechanism or to ensure single writer to a database row. + + ```go + CODE_LOAD::go/concepts/virtualobjects/main.go?3 + ``` + + + ## Workflows diff --git a/docs/get_started/tour.mdx b/docs/get_started/tour.mdx index d419edc5..d395ec89 100644 --- a/docs/get_started/tour.mdx +++ b/docs/get_started/tour.mdx @@ -1173,7 +1173,7 @@ CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/main/tutorials/ Let the `CartObject/AddTicket` handler call the `CartObject/ExpireTicket` handler with a delay of 15 minutes: -```typescript cartobject.go +```go cartobject.go CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/main/tutorials/tour-of-restate-go/part2/cartobject.go#add_ticket ``` @@ -1940,7 +1940,7 @@ Try it out by printing the idempotency key and then throwing an error: - ```ts checkoutservice.go + ```go checkoutservice.go CODE_LOAD::go/getstarted/tour.go#uuid ``` diff --git a/docs/invoke/kafka.mdx b/docs/invoke/kafka.mdx index ac3a9d3d..f5411084 100644 --- a/docs/invoke/kafka.mdx +++ b/docs/invoke/kafka.mdx @@ -109,4 +109,19 @@ You can invoke handlers via Kafka events, by doing the following: - \ No newline at end of file + + +
+Event metadata + +Each event carries within the _`CODE_LOAD::go/develop/kafka.go#headers`_ map the following entries: + +* `restate.subscription.id`: The subscription identifier, as shown by the [Admin API](../../references/admin-api). +* `kafka.offset`: The record offset. +* `kafka.partition`: The record partition. +* `kafka.timestamp`: The record timestamp. + +
+ +
+ diff --git a/docs/use-cases/event-processing.mdx b/docs/use-cases/event-processing.mdx index 027be7c3..4ed56920 100644 --- a/docs/use-cases/event-processing.mdx +++ b/docs/use-cases/event-processing.mdx @@ -523,11 +523,12 @@ Implement stateful event handlers with Restate. "Read the docs to learn more." ), ts: "/invoke/kafka?sdk=ts", - java: "/invoke/kafka?sdk=java" + java: "/invoke/kafka?sdk=java", + go: "/invoke/kafka?sdk=go" }, { title: 'Need help?', description: "Join the Restate Discord channel", link: {url: "https://discord.gg/skW3AZ6uGd", icon: "/img/discord-icon.svg"} } -]}/> \ No newline at end of file +]}/> diff --git a/docs/use-cases/microservice-orchestration.mdx b/docs/use-cases/microservice-orchestration.mdx index a89ad439..479ebf2f 100644 --- a/docs/use-cases/microservice-orchestration.mdx +++ b/docs/use-cases/microservice-orchestration.mdx @@ -350,11 +350,12 @@ Turn functions into durable handlers with the Restate SDK. "Follow the Tour of Restate to learn more." ), ts: "/get_started/tour?sdk=ts", - java: "/get_started/tour?sdk=java" + java: "/get_started/tour?sdk=java", + go: "/get_started/tour?sdk=go" }, { title: 'Need help?', description: "Join the Restate Discord channel", link: {url: "https://discord.gg/skW3AZ6uGd", icon: "/img/discord-icon.svg"} } -]}/> \ No newline at end of file +]}/> diff --git a/src/components/FeatureWidget/index.tsx b/src/components/FeatureWidget/index.tsx index 5ec2b389..4cdb368d 100644 --- a/src/components/FeatureWidget/index.tsx +++ b/src/components/FeatureWidget/index.tsx @@ -10,6 +10,7 @@ type FeatureItem = { java: string; ts: string; kotlin: string; + go: string; link: { icon: string; url: string }; }; @@ -22,6 +23,7 @@ function Feature({ ts, link, kotlin, + go, }: FeatureItem) { const colWidth = itemsPerRow ? Math.floor(12 / itemsPerRow) : 4; return ( @@ -75,6 +77,18 @@ function Feature({ ) : null} + {go ? ( +
+ + + +
+ ) : null} {link ? (