Skip to content

Commit

Permalink
Update basics
Browse files Browse the repository at this point in the history
  • Loading branch information
gvdongen committed Jan 8, 2025
1 parent 9ae9312 commit e85dbb8
Show file tree
Hide file tree
Showing 25 changed files with 288 additions and 131 deletions.
7 changes: 3 additions & 4 deletions go/basics/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Examples of the basic concepts for Restate in TypeScript / JavaScript
# The basic concepts of Restate in Go

The examples here showcase the most basic building blocks of Restate. **Durable Execution**,
**Durable Promises**, and **Virtual Objects**, and the **Workflows** abstraction built on top
Expand All @@ -9,15 +9,14 @@ about how they work and how they can be run.

### Examples

* **[Durable Execution](part0/durableexecution.go):** Running code cleanly
* **[Services & Durable Execution](part0/durableexecution.go):** Running code cleanly
to the end in the presence of failures. Automatic retries and recovery of previously
finished actions. The example applies creates a subscription to movie streaming services
by first creating a recurring payment and then adding the subscriptions.

* **[Building blocks](part1/buildingblocks.go):** Restate gives you a durable version
of common building blocks like queues, promises, RPC, state, and timers.
This example shows a handler which processes payment failure events from a payment provider.
The handler reminds the customer for 3 days to update their payment details, and otherwise cancels the subscriptions.
This example shows a reference of the API and what you can do with it.

* **[Virtual Objects](part2/virtualobjects.go):** Stateful serverless objects
to manage durable consistent state and state-manipulating logic.
Expand Down
23 changes: 13 additions & 10 deletions go/basics/part0/durableexecution.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ import (
"os"
)

// Restate lets you implement resilient applications.
// Restate ensures handler code runs to completion despite failures:
// Restate helps you implement resilient applications:
// - Automatic retries
// - Restate tracks the progress of execution, and prevents re-execution of completed work on retries
// - Regular code and control flow, no custom DSLs
// Applications consist of services with handlers that can be called over HTTP or Kafka.
// - Tracking progress of execution and preventing re-execution of completed work on retries
// - Providing durable building blocks like timers, promises, and messaging: recoverable and revivable anywhere
//
// Applications consist of services with handlers that can be called over HTTP or Kafka.
// Handlers can be called at http://restate:8080/ServiceName/handlerName
// Restate persists HTTP requests to this handler and manages execution.
//
// Restate persists and proxies HTTP requests to handlers and manages their execution.
// The SDK lets you implement handlers with regular code and control flow, no custom DSLs.
// Whenever a handler uses the Restate Context, an event gets persisted in Restate's log.
// After a failure, this log gets replayed to recover the state of the handler.

type SubscriptionRequest struct {
UserID string `json:"userId"`
Expand All @@ -27,19 +30,19 @@ type SubscriptionRequest struct {
type SubscriptionService struct{}

func (SubscriptionService) Add(ctx restate.Context, req SubscriptionRequest) error {
// Stable idempotency key: Restate persists the result of
// all `ctx` actions and recovers them after failures
// Restate persists the result of all `ctx` actions and recovers them after failures
// For example, generate a stable idempotency key:
paymentId := restate.Rand(ctx).UUID().String()

// Retried in case of timeouts, API downtime, etc.
// restate.Run persists results of successful actions and skips execution on retries
// Failed actions (timeouts, API downtime, etc.) get retried
payRef, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) {
return CreateRecurringPayment(req.CreditCard, paymentId)
})
if err != nil {
return err
}

// Persists successful subscriptions and skip them on retries
for _, subscription := range req.Subscriptions {
if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {
return restate.Void{}, CreateSubscription(req.UserID, subscription, payRef)
Expand Down
17 changes: 17 additions & 0 deletions go/basics/part1/buildingblocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,24 @@ type SubscriptionRequest struct {
UserID string `json:"userId"`
}

/*
* RESTATE's DURABLE BUILDING BLOCKS
*
* Restate turns familiar programming constructs into recoverable, distributed building blocks.
* They get persisted in Restate, survive failures, and can be revived on another process.
*
* No more need for retry/recovery logic, K/V stores, workflow orchestrators,
* scheduler services, message queues, ...
*
* The run handler below shows a catalog of these building blocks.
* Look at the other examples in this project to see how to use them in examples.
*/

type MyService struct{}

// This handler can be called over HTTP at http://restate:8080/myService/handlerName
// Use the context to access Restate's durable building blocks

func (MyService) Run(ctx restate.Context) error {
// 1. IDEMPOTENCY: Add an idempotency key to the header of your requests
// Restate deduplicates calls automatically. Nothing to do here.
Expand All @@ -34,6 +50,7 @@ func (MyService) Run(ctx restate.Context) error {
// Awakeables: block the workflow until notified by another handler
awakeable := restate.Awakeable[string](ctx)
// Wait on the result
// If the process crashes while waiting, Restate will recover the promise somewhere else
result, err := awakeable.Result()
if err != nil {
return err
Expand Down
3 changes: 2 additions & 1 deletion go/basics/part2/virtualobjects.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ const COUNTER_KEY = "counter"
// Virtual Objects are services that hold K/V state. Its handlers interact with the object state.
// An object is identified by a unique id - only one object exists per id.
//
// To guarantee state consistency, only one handler is executed at a time per Virtual Object (ID).
//
// Handlers are stateless executors.
// Restate proxies requests to it and attaches the object's state to the request.
// Virtual Objects then have their K/V state locally accessible without requiring any database
// connection or lookup. State is exclusive, and atomically committed with the
// method execution. It is always consistent with the progress of the execution.
//
// Virtual Objects are Stateful (Serverless) constructs.
//

type GreeterObject struct{}

Expand Down
10 changes: 8 additions & 2 deletions go/basics/part3/workflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ type User struct {
Email string
}

// Implement long-running workflows with Restate.
// For example, a user signup and email verification workflow.
// Workflow are a special type of Virtual Object with a run handler that runs once per ID.
// Workflows are stateful and can be interacted with via queries (getting data out of the workflow)
// and signals (pushing data to the workflow).
//
// Workflows are used to model long-running flows, such as user onboarding, order processing, etc.
// Workflows have the following handlers:
// - Main workflow in run() method
// - Additional methods interact with the workflow.
// Each workflow instance has a unique ID and runs only once (to success or failure).

type SignupWorkflow struct{}

// --- The workflow logic ---

func (SignupWorkflow) Run(ctx restate.WorkflowContext, user User) (bool, error) {
// workflow ID = user ID; workflow runs once per user
userId := restate.Key(ctx)
Expand Down
19 changes: 12 additions & 7 deletions java/basics/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Examples of the basic concepts for Restate in Java
# The basic concepts of Restate in Java

The examples here showcase the most basic building blocks of Restate. **Durable Execution**,
**Durable Promises**, and **Virtual Objects**, and the **Workflows** abstraction built on top
Expand All @@ -9,26 +9,31 @@ about how they work and how they can be run.

### Examples

* **[Basic Durable Execution:](src/main/java/durable_execution/SubscriptionService.java):** Running code cleanly
* **[Services & Durable Execution](src/main/java/durable_execution/SubscriptionService.java)):** Running code cleanly
to the end in the presence of failures. Automatic retries and recovery of previously
finished actions. The example applies a series of updates and permission setting changes
to user's profile.
finished actions. The example applies creates a subscription to movie streaming services
by first creating a recurring payment and then adding the subscriptions.

* **[Building blocks](src/main/java/building_blocks/MyService.java):** Restate gives you a durable version
of common building blocks like queues, promises, RPC, state, and timers.
This example shows a reference of the API and what you can do with it.

* **[Virtual Objects](src/main/java/virtual_objects/GreeterObject.java):** Stateful serverless objects
to manage durable consistent state and state-manipulating logic.

* **[Workflows](src/main/java/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](src/main/java/virtual_objects/GreeterObject.java):** Stateful serverless objects
to manage durable consistent state and state-manipulating logic.

### Running the examples

1. [Start the Restate Server](https://docs.restate.dev/develop/local_dev) in a separate shell:
`restate-server`

2. Start the relevant example:
- `./gradlew -PmainClass=durable_execution.SubscriptionService run` for the Durable Execution example
- The building blocks example is not runnable and more like a reference of what you can do with the API
- `./gradlew -PmainClass=workflows.SignupWorkflow run` for the Workflows example
- `./gradlew -PmainClass=virtual_objects.GreeterObject run` for the Virtual Objects example

Expand Down
79 changes: 79 additions & 0 deletions java/basics/src/main/java/building_blocks/MyService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package building_blocks;

import dev.restate.sdk.Context;
import dev.restate.sdk.JsonSerdes;
import dev.restate.sdk.annotation.Handler;
import dev.restate.sdk.annotation.Service;
import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder;
import utils.SubscriptionService;

import java.time.Duration;
import java.util.UUID;

import static utils.ExampleStubs.chargeBankAccount;

/*
* RESTATE's DURABLE BUILDING BLOCKS
*
* Restate turns familiar programming constructs into recoverable, distributed building blocks.
* They get persisted in Restate, survive failures, and can be revived on another process.
*
* No more need for retry/recovery logic, K/V stores, workflow orchestrators,
* scheduler services, message queues, ...
*
* The run handler below shows a catalog of these building blocks.
* Look at the other examples in this project to see how to use them in examples.
*/

@Service
public class MyService {

// This handler can be called over HTTP at http://restate:8080/myService/handlerName
// Use the context to access Restate's durable building blocks
@Handler
public void run(Context ctx) throws Exception {
// 1. IDEMPOTENCY: Add an idempotency key to the header of your requests
// Restate deduplicates calls automatically. Nothing to do here.

// 2. DURABLE RPC: Call other services without manual retry and deduplication logic
// Restate persists all requests and ensures execution till completion
String result = SubscriptionServiceClient.fromContext(ctx, "my-sub-123").create("my-request").await();

// 3. DURABLE MESSAGING: send (delayed) messages to other services without deploying a message broker
// Restate persists the timers and triggers execution
SubscriptionServiceClient.fromContext(ctx, "my-sub-123").send().create("my-request");

// 4. DURABLE PROMISES: tracked by Restate, can be moved between processes and survive failures
// Awakeables: block the workflow until notified by another handler
var awakeable = ctx.awakeable(JsonSerdes.STRING);
// Wait on the promise
// If the process crashes while waiting, Restate will recover the promise somewhere else
String greeting = awakeable.await();
// Another process can resolve the awakeable via its ID
ctx.awakeableHandle(awakeable.id()).resolve(JsonSerdes.STRING, "hello");

// 5. DURABLE TIMERS: sleep or wait for a timeout, tracked by Restate and recoverable
// When this runs on FaaS, the handler suspends and the timer is tracked by Restate
// Example of durable recoverable sleep
// If the service crashes two seconds later, Restate will invoke it after another 3 seconds
ctx.sleep(Duration.ofSeconds(5));
// Example of waiting on a promise (call/awakeable/...) or a timeout
awakeable.await(Duration.ofSeconds(5000));
// Example of scheduling a handler for later on
SubscriptionServiceClient.fromContext(ctx, "my-sub-123").send(Duration.ofDays(1)).cancel();

// 7. PERSIST RESULTS: avoid re-execution of actions on retries
// Use this for non-deterministic actions or interaction with APIs, DBs, ...
// For example, generate idempotency keys that are stable across retries
// Then use these to call other APIs and let them deduplicate
String paymentDeduplicationID = UUID.randomUUID().toString();
ctx.run(() -> chargeBankAccount(paymentDeduplicationID, 100));
}

public static void main(String[] args) {
RestateHttpEndpointBuilder.builder()
.bind(new MyService())
.bind(new SubscriptionService())
.buildAndListen();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,34 @@

import static utils.ExampleStubs.*;

// Restate lets you implement resilient applications.
// Restate ensures code runs to completion despite failures:
// Restate helps you implement resilient applications:
// - Automatic retries
// - Restate tracks the progress of execution, and prevents re-execution of completed work on retries
// - Regular code and control flow, no custom DSLs

// Applications consist of services (`@Service`) with handlers (`@Handler`)
// - Tracking progress of execution and preventing re-execution of completed work on retries
// - Providing durable building blocks like timers, promises, and messaging: recoverable and revivable anywhere
//
// Applications consist of services (annotated with `@Service`) with handlers (annotated with `@Handler`)
// that can be called over HTTP or Kafka.
// Handlers can be called at http://restate:8080/ServiceName/handlerName
//
// Restate persists and proxies HTTP requests to handlers and manages their execution.
// The SDK lets you implement handlers with regular code and control flow, no custom DSLs.
// Whenever a handler uses the Restate Context, an event gets persisted in Restate's log.
// After a failure, this log gets replayed to recover the state of the handler.

@Service
public class SubscriptionService {

// Handlers can be called at http://restate:8080/ServiceName/handlerName
// Restate persists HTTP requests to this handler and manages execution.
@Handler
public void add(Context ctx, SubscriptionRequest req) {
// Restate persists the result of all `ctx` actions
// and recovers them after failures
// Restate persists the result of all `ctx` actions and recovers them after failures
// For example, generate a stable idempotency key:
var paymentId = ctx.random().nextUUID().toString();

// Retried in case of timeouts, API downtime, etc.
// ctx.run persists results of successful actions and skips execution on retries
// Failed actions (timeouts, API downtime, etc.) get retried
var payRef = ctx.run(JsonSerdes.STRING, () ->
createRecurringPayment(req.creditCard(), paymentId));

// Persists successful subscriptions and skip them on retries
for (String subscription : req.subscriptions()) {
ctx.run(() -> createSubscription(req.userId(), subscription, payRef));
}
Expand All @@ -46,7 +50,6 @@ public static void main(String[] args) {
}
}


/*
Check the README to learn how to run Restate.
Then invoke this function and see in the log how it recovers.
Expand Down
4 changes: 4 additions & 0 deletions java/basics/src/main/java/utils/ExampleStubs.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ public static void sendEmailWithLink(String userId, User user, String secret){
"curl localhost:8080/SignupWorkflow/{}/click -H 'content-type: application/json' -d '\"{}\"'",
userId, secret);
}

public static void chargeBankAccount(String paymentDeduplicationID, int amount) {
// Implementation here
}
}
20 changes: 20 additions & 0 deletions java/basics/src/main/java/utils/SubscriptionService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package utils;

import dev.restate.sdk.ObjectContext;
import dev.restate.sdk.annotation.Handler;
import dev.restate.sdk.annotation.VirtualObject;

@VirtualObject
public class SubscriptionService {

@Handler
public String create(ObjectContext ctx, String userId) {
// Implementation here
return "SUCCESS";
}

@Handler
public void cancel(ObjectContext ctx) {
System.out.println("Cancelling all subscriptions for user " + ctx.key());
}
}
11 changes: 8 additions & 3 deletions java/basics/src/main/java/virtual_objects/GreeterObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,23 @@
import dev.restate.sdk.common.StateKey;
import dev.restate.sdk.http.vertx.RestateHttpEndpointBuilder;

// Virtual Objects hold K/V state and have methods to interact with the object.
// Virtual Objects are services that hold K/V state. Its handlers interact with the object state.
// An object is identified by a unique id - only one object exists per id.
//
// Virtual Objects have their K/V state locally accessible without requiring any database
// To guarantee state consistency, only one handler is executed at a time per Virtual Object (ID).
//
// Handlers are stateless executors.
// Restate proxies requests to it and attaches the object's state to the request.
// Virtual Objects then have their K/V state locally accessible without requiring any database
// connection or lookup. State is exclusive, and atomically committed with the
// method execution.
// method execution. It is always consistent with the progress of the execution.
//
// Virtual Objects are Stateful (Serverless) constructs.
//
@VirtualObject
public class GreeterObject {

// Reference to the K/V state stored in Restate
private static final StateKey<Integer> COUNT =
StateKey.of("count", JsonSerdes.INT);

Expand Down
7 changes: 6 additions & 1 deletion java/basics/src/main/java/workflows/SignupWorkflow.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@
import static utils.ExampleStubs.createUserEntry;
import static utils.ExampleStubs.sendEmailWithLink;

// Workflow for user signup and email verification.
// Workflow are a special type of Virtual Object with a run handler that runs once per ID.
// Workflows are stateful and can be interacted with via queries (getting data out of the workflow)
// and signals (pushing data to the workflow).
//
// Workflows are used to model long-running flows, such as user onboarding, order processing, etc.
// Workflows have the following handlers:
// - Main workflow in run() method
// - Additional methods interact with the workflow.
// Each workflow instance has a unique ID and runs only once (to success or failure).
Expand Down
Loading

0 comments on commit e85dbb8

Please sign in to comment.