Skip to main content

Service Communication

A handler can call another handler and wait for the response (request-response), or it can send a message without waiting for the response.

Request-response calls

Request-response calls are requests where the client waits for the response.

To a Service:


response, err := restate.Service[string](ctx, "MyService", "MyHandler").
Request("Hi")
if err != nil {
return err
}

To a Service:


response, err := restate.Service[string](ctx, "MyService", "MyHandler").
Request("Hi")
if err != nil {
return err
}

To a Virtual Object:


response, err := restate.Object[string](ctx, "MyVirtualObject", "Mary", "MyHandler").
Request("Hi")
if err != nil {
return err
}

To a Virtual Object:


response, err := restate.Object[string](ctx, "MyVirtualObject", "Mary", "MyHandler").
Request("Hi")
if err != nil {
return err
}

  1. Create a service client and supply the name of the service and method you want to call, as well as a type parameter for the output type. For Virtual Objects, you also need to supply the key of the Virtual Object you want to call, here "Mary".
  2. Call Request() with the input data to make a synchronous request which will block on the response.
No need for manual retry logic

These calls are proxied by Restate, and get logged in the journal. In case of failures, Restate takes care of retries.

Prefer a bit more type-safety?

The Go SDK also supports defining handlers and input/output types using code generation from Protocol Buffers. This will also generate typed clients which can be used to safely call other methods. See Code Generation for more details.

Deadlocks with Virtual Objects

Request-response calls to Virtual Objects can lead to deadlocks, in which the Virtual Object remains locked and can't process any more requests. Some example cases:

  • Cross deadlock between Virtual Object A and B: A calls B, and B calls A, both using same keys.
  • Cyclical deadlock: A calls B, and B calls C, and C calls A again.

In this situation, you can use the CLI to unblock the Virtual Object manually by cancelling invocations.

Sending messages

Handlers can send messages (a.k.a. one-way calls, or fire-and-forget calls), as follows:

To a Service:


restate.ServiceSend(ctx, "MyService", "MyHandler").Send("Hi")

To a Service:


restate.ServiceSend(ctx, "MyService", "MyHandler").Send("Hi")

To a Virtual Object:


restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").Send("Hi")

To a Virtual Object:


restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").Send("Hi")

No need for message queues

Without Restate, you would usually put a message queue in between the two services, to guarantee the message delivery. Restate eliminates the need for a message queue because Restate durably logs the request and makes sure it gets executed.

Delayed calls

A delayed call is a one-way call that gets executed after a specified delay.

To schedule a delayed call, send a message with a delay parameter, as follows:

To a Service:


restate.ServiceSend(ctx, "MyService", "MyHandler").
Send("Hi", restate.WithDelay(5*time.Second))

To a Service:


restate.ServiceSend(ctx, "MyService", "MyHandler").
Send("Hi", restate.WithDelay(5*time.Second))

To a Virtual Object:


restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").
Send("Hi", restate.WithDelay(5*time.Second))

To a Virtual Object:


restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").
Send("Hi", restate.WithDelay(5*time.Second))

Scheduling async tasks

You can also use this functionality to schedule async tasks. Restate will make sure the task gets executed at the desired time.

Ordering guarantees in Virtual Objects

Invocations to a Virtual Object are executed serially. Invocations will execute in the same order in which they arrive at Restate. For example, assume a handler calls the same Virtual Object twice:


restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").Send("I'm call A")
restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").Send("I'm call B")

It is guaranteed that call A will execute before call B. It is not guaranteed though that call B will be executed immediately after call A, as invocations coming from other handlers/sources, could interleave these two calls.