Trait ContextClient

Source
pub trait ContextClient<'ctx>: SealedContext<'ctx> {
    // Provided methods
    fn request<Req, Res>(
        &self,
        request_target: RequestTarget,
        req: Req,
    ) -> Request<'ctx, Req, Res> { ... }
    fn invocation_handle(
        &self,
        invocation_id: String,
    ) -> impl InvocationHandle + 'ctx { ... }
    fn service_client<C>(&self) -> C
       where C: IntoServiceClient<'ctx> { ... }
    fn object_client<C>(&self, key: impl Into<String>) -> C
       where C: IntoObjectClient<'ctx> { ... }
    fn workflow_client<C>(&self, key: impl Into<String>) -> C
       where C: IntoWorkflowClient<'ctx> { ... }
}
Expand description

§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.

You can do request-response calls to Services, Virtual Objects, and Workflows, in the following way:

    // To a Service:
    let service_response = ctx
        .service_client::<MyServiceClient>()
        .my_handler(String::from("Hi!"))
        .call()
        .await?;

    // To a Virtual Object:
    let object_response = ctx
        .object_client::<MyVirtualObjectClient>("Mary")
        .my_handler(String::from("Hi!"))
        .call()
        .await?;

    // To a Workflow:
    let workflow_result = ctx
        .workflow_client::<MyWorkflowClient>("my-workflow-id")
        .run(String::from("Hi!"))
        .call()
        .await?;
    ctx.workflow_client::<MyWorkflowClient>("my-workflow-id")
        .interact_with_workflow()
        .call()
        .await?;
  1. Create a service client.
    • For Virtual Objects, you also need to supply the key of the Virtual Object you want to call, here "Mary".
    • For Workflows, you need to supply a workflow ID that is unique per workflow execution.
  2. Specify the handler you want to call and supply the request.
  3. Await the call to retrieve the response.

No need for manual retry logic: Restate proxies all the calls and logs them in the journal. In case of failures, Restate takes care of retries, so you don’t need to implement this yourself here.

§Sending messages

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

    // To a Service:
    ctx.service_client::<MyServiceClient>()
        .my_handler(String::from("Hi!"))
        .send();

    // To a Virtual Object:
    ctx.object_client::<MyVirtualObjectClient>("Mary")
        .my_handler(String::from("Hi!"))
        .send();

    // To a Workflow:
    ctx.workflow_client::<MyWorkflowClient>("my-workflow-id")
        .run(String::from("Hi!"))
        .send();
    ctx.workflow_client::<MyWorkflowClient>("my-workflow-id")
        .interact_with_workflow()
        .send();

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:
    ctx.service_client::<MyServiceClient>()
        .my_handler(String::from("Hi!"))
        .send_after(Duration::from_millis(5000));

    // To a Virtual Object:
    ctx.object_client::<MyVirtualObjectClient>("Mary")
        .my_handler(String::from("Hi!"))
        .send_after(Duration::from_millis(5000));

    // To a Workflow:
    ctx.workflow_client::<MyWorkflowClient>("my-workflow-id")
        .run(String::from("Hi!"))
        .send_after(Duration::from_millis(5000));
    ctx.workflow_client::<MyWorkflowClient>("my-workflow-id")
        .interact_with_workflow()
        .send_after(Duration::from_millis(5000));

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:

    ctx.object_client::<MyVirtualObjectClient>("Mary")
        .my_handler(String::from("I'm call A!"))
        .send();
    ctx.object_client::<MyVirtualObjectClient>("Mary")
        .my_handler(String::from("I'm call B!"))
        .send();
}

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.

§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.

Provided Methods§

Source

fn request<Req, Res>( &self, request_target: RequestTarget, req: Req, ) -> Request<'ctx, Req, Res>

Create a Request.

Source

fn invocation_handle( &self, invocation_id: String, ) -> impl InvocationHandle + 'ctx

Create an InvocationHandle from an invocation id.

Source

fn service_client<C>(&self) -> C
where C: IntoServiceClient<'ctx>,

Create a service client. The service client is generated by the restate_sdk_macros::service macro with the same name of the trait suffixed with Client.


#[restate_sdk::service]
trait MyService {
  async fn handle() -> HandlerResult<()>;
}

let client = ctx.service_client::<MyServiceClient>();

// Do request
let result = client.handle().call().await;

// Just send the request, don't wait the response
client.handle().send();

// Schedule the request to be executed later
client.handle().send_after(Duration::from_secs(60));
Source

fn object_client<C>(&self, key: impl Into<String>) -> C
where C: IntoObjectClient<'ctx>,

Create an object client. The object client is generated by the restate_sdk_macros::object macro with the same name of the trait suffixed with Client.


#[restate_sdk::object]
trait MyObject {
  async fn handle() -> HandlerResult<()>;
}

let client = ctx.object_client::<MyObjectClient>("my-key");

// Do request
let result = client.handle().call().await;

// Just send the request, don't wait the response
client.handle().send();

// Schedule the request to be executed later
client.handle().send_after(Duration::from_secs(60));
Source

fn workflow_client<C>(&self, key: impl Into<String>) -> C
where C: IntoWorkflowClient<'ctx>,

Create an workflow client. The workflow client is generated by the restate_sdk_macros::workflow macro with the same name of the trait suffixed with Client.


#[restate_sdk::workflow]
trait MyWorkflow {
  async fn handle() -> HandlerResult<()>;
}

let client = ctx.workflow_client::<MyWorkflowClient>("my-key");

// Do request
let result = client.handle().call().await;

// Just send the request, don't wait the response
client.handle().send();

// Schedule the request to be executed later
client.handle().send_after(Duration::from_secs(60));

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§

Source§

impl<'ctx, CTX: SealedContext<'ctx>> ContextClient<'ctx> for CTX