Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement shared context & various improvements #11

Merged
merged 4 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- [X] Sleep
- [x] Run
- [x] Awakeable
- [x] Shared object handlers
- [ ] Workflows

## Basic usage

Expand Down Expand Up @@ -63,8 +65,6 @@ Trying adding the same tickets again should return `false` since they are alread
Finally checkout

```bash
curl localhost:8080/UserSession/azmy/Checkout \
-H 'content-type: application/json' \
-d 'null'
curl localhost:8080/UserSession/azmy/Checkout
#{"response":true}
```
159 changes: 159 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package restate

import (
"context"
"log/slog"
"time"

"github.com/restatedev/sdk-go/internal/futures"
"github.com/restatedev/sdk-go/internal/options"
"github.com/restatedev/sdk-go/internal/rand"
)

type Context interface {
RunContext

// Rand returns a random source which will give deterministic results for a given invocation
// The source wraps the stdlib rand.Rand but with some extra helper methods
// This source is not safe for use inside .Run()
Rand() *rand.Rand

// Sleep for the duration d
Sleep(d time.Duration)
// After is an alternative to Context.Sleep which allows you to complete other tasks concurrently
// with the sleep. This is particularly useful when combined with Context.Select to race between
// the sleep and other Selectable operations.
After(d time.Duration) After

// Service gets a Service accessor by service and method name
// Note: use the CallAs helper function to deserialise return values
Service(service, method string, opts ...options.CallOption) CallClient

// Object gets a Object accessor by name, key and method name
// Note: use the CallAs helper function to receive serialised values
Object(object, key, method string, opts ...options.CallOption) CallClient

// Run runs the function (fn), storing final results (including terminal errors)
// durably in the journal, or otherwise for transient errors stopping execution
// so Restate can retry the invocation. Replays will produce the same value, so
// all non-deterministic operations (eg, generating a unique ID) *must* happen
// inside Run blocks.
// Note: use the RunAs helper function to get typed output values instead of providing an output pointer
Run(fn func(RunContext) (any, error), output any, opts ...options.RunOption) error

// Awakeable returns a Restate awakeable; a 'promise' to a future
// value or error, that can be resolved or rejected by other services.
// Note: use the AwakeableAs helper function to avoid having to pass a output pointer to Awakeable.Result()
Awakeable(options ...options.AwakeableOption) Awakeable
// ResolveAwakeable allows an awakeable (not necessarily from this service) to be
// resolved with a particular value.
ResolveAwakeable(id string, value any, options ...options.ResolveAwakeableOption) error
// ResolveAwakeable allows an awakeable (not necessarily from this service) to be
// rejected with a particular error.
RejectAwakeable(id string, reason error)

// Select returns an iterator over blocking Restate operations (sleep, call, awakeable)
// which allows you to safely run them in parallel. The Selector will store the order
// that things complete in durably inside Restate, so that on replay the same order
// can be used. This avoids non-determinism. It is *not* safe to use goroutines or channels
// outside of Context.Run functions, as they do not behave deterministically.
Select(futs ...futures.Selectable) Selector
}

// Awakeable is the Go representation of a Restate awakeable; a 'promise' to a future
// value or error, that can be resolved or rejected by other services.
type Awakeable interface {
// Id returns the awakeable ID, which can be stored or sent to a another service
Id() string
// Result blocks on receiving the result of the awakeable, storing the value it was
// resolved with in output or otherwise returning the error it was rejected with.
// It is *not* safe to call this in a goroutine - use Context.Select if you
// want to wait on multiple results at once.
// Note: use the AwakeableAs helper function to avoid having to pass a output pointer
Result(output any) error
futures.Selectable
}

type CallClient interface {
// RequestFuture makes a call and returns a handle on a future response
RequestFuture(input any) (ResponseFuture, error)
// Request makes a call and blocks on getting the response which is stored in output
Request(input any, output any) error
SendClient
}

type SendClient interface {
// Send makes a one-way call which is executed in the background
Send(input any, delay time.Duration) error
}

type ResponseFuture interface {
// Response blocks on the response to the call and stores it in output, or returns the associated error
// It is *not* safe to call this in a goroutine - use Context.Select if you
// want to wait on multiple results at once.
Response(output any) error
futures.Selectable
}

// Selector is an iterator over a list of blocking Restate operations that are running
// in the background.
type Selector interface {
// Remaining returns whether there are still operations that haven't been returned by Select().
// There will always be exactly the same number of results as there were operations
// given to Context.Select
Remaining() bool
// Select blocks on the next completed operation
Select() futures.Selectable
}

// RunContext methods are the only methods safe to call from inside a .Run()
type RunContext interface {
context.Context

// Log obtains a handle on a slog.Logger which already has some useful fields (invocationID and method)
// By default, this logger will not output messages if the invocation is currently replaying
// The log handler can be set with `.WithLogger()` on the server object
Log() *slog.Logger
}

// After is a handle on a Sleep operation which allows you to do other work concurrently
// with the sleep.
type After interface {
// Done blocks waiting on the remaining duration of the sleep.
// It is *not* safe to call this in a goroutine - use Context.Select if you
// want to wait on multiple results at once.
Done()
futures.Selectable
}

type ObjectContext interface {
Context
KeyValueReader
KeyValueWriter
}

type ObjectSharedContext interface {
Context
KeyValueReader
}

type KeyValueReader interface {
// Get gets value associated with key and stores it in value
// If key does not exist, this function returns ErrKeyNotFound
// Note: Use GetAs generic helper function to avoid passing in a value pointer
Get(key string, value any, options ...options.GetOption) error
// Keys returns a list of all associated key
Keys() []string
// Key retrieves the key for this virtual object invocation. This is a no-op and is
// always safe to call.
Key() string
}

type KeyValueWriter interface {
// Set sets a value against a key, using the provided codec (defaults to JSON)
Set(key string, value any, options ...options.SetOption) error
// Clear deletes a key
Clear(key string)
// ClearAll drops all stored state associated with key
ClearAll()
}
Loading
Loading