Durable Building Blocks
Distributed systems are inherently complex and failures are inevitable. Almost any application is a distributed system, since they are composed of different components that communicate over the network (e.g. services, databases, queues, etc). With every component, the number of possible failure scenarios increases: network partitions, hardware failures, timeouts, race conditions etc. Building reliable applications is a challenging task.
Restate lets you write distributed applications that are resilient to failures. It does this by providing a distributed, durable version of common building blocks.
For these building blocks, Restate handles failure recovery, idempotency, state, and consistency. This way, you can implement otherwise tricky patterns in a few lines of code without worrying about these concerns.
Restate lets you implement your business logic in handlers. These handlers have access to these building blocks via the Restate SDK, that is loaded as a dependency.
Let's have a look at a handler that processes food orders:
- TypeScript
- Java
- Go
- Python
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.
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.
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.
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.
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.
async function process(ctx: ObjectContext, order: Order) {// 1. Set statusctx.set("status", Status.CREATED);// 2. Handle paymentconst token = ctx.rand.uuidv4();const paid = await ctx.run(() =>paymentClnt.charge(order.id, token, order.totalCost));if (!paid) {ctx.set("status", Status.REJECTED);return;}// 3. Wait until the requested preparation timectx.set("status", Status.SCHEDULED);await ctx.sleep(order.deliveryDelay);// 4. Trigger preparationconst preparationPromise = ctx.awakeable();await ctx.run(() => restaurant.prepare(order.id, preparationPromise.id));ctx.set("status", Status.IN_PREPARATION);await preparationPromise.promise;ctx.set("status", Status.SCHEDULING_DELIVERY);// 5. Find a driver and start deliveryawait ctx.objectClient(deliveryManager, order.id).startDelivery(order);ctx.set("status", Status.DELIVERED);}
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.
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.
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.
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.
Journaling actions
Store the result of an action in Restate. The result gets replayed in case of failures and the function is not executed again.
@VirtualObjectpublic class OrderWorkflow {public static final StateKey<StatusEnum> STATUS =StateKey.of("status", JacksonSerdes.of(StatusEnum.class));@Handlerpublic void process(ObjectContext ctx, OrderRequest order) {String id = order.getOrderId();ctx.set(STATUS, StatusEnum.CREATED);// 2. Handle paymentString token = ctx.random().nextUUID().toString();boolean paid =ctx.run(JsonSerdes.BOOLEAN,() -> PaymentClient.charge(id, token, order.getTotalCost()));if (!paid) {ctx.set(STATUS, StatusEnum.REJECTED);return;}// 3. Schedule preparationctx.set(STATUS, StatusEnum.SCHEDULED);ctx.sleep(Duration.ofMillis(order.getDeliveryDelay()));// 4. Trigger preparationvar awakeable = ctx.awakeable(Serde.VOID);ctx.run(() -> RestaurantClient.prepare(id, awakeable.id()));ctx.set(STATUS, StatusEnum.IN_PREPARATION);awakeable.await();ctx.set(STATUS, StatusEnum.SCHEDULING_DELIVERY);// 5. Find a driver and start deliveryDeliveryManagerClient.fromContext(ctx, id).startDelivery(order).await();ctx.set(STATUS, StatusEnum.DELIVERED);}}
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.
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.
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.
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.
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.
func (OrderProcessor) Process(ctx restate.ObjectContext, order Order) error {// 1. Set statusrestate.Set(ctx, "status", Status_CREATED)// 2. Handle paymenttoken := 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 timerestate.Set(ctx, "status", Status_SCHEDULED)if err := restate.Sleep(ctx, order.DeliveryDelay); err != nil {return err}// 4. Trigger preparationpreparationAwakeable := restate.Awakeable[restate.Void](ctx)if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return restate.Void{}, restaurant.Prepare(order.Id, preparationAwakeable.Id())}); err != nil {return err}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 deliveryif _, err := restate.Object[restate.Void](ctx, "DeliveryManager", order.Id, "StartDelivery").Request(order); err != nil {return err}restate.Set(ctx, "status", Status_DELIVERED)return nil}
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.
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.
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.
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.
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.
@order_workflow.handler()async def process(ctx: ObjectContext, order: Order):# 1. Set statusctx.set("status", "CREATED")# 2. Handle paymenttoken = await ctx.run("token", lambda: str(uuid.uuid4()))async def pay():await payment_client.charge(order["id"], token, order["total_cost"])paid = await ctx.run("payment", pay)if not paid:ctx.set("status", "REJECTED")return# 3. Wait until the requested preparation timectx.set("status", "SCHEDULED")await ctx.sleep(timedelta(milliseconds=order["delivery_delay"]))# 4. Trigger preparationpreparation_id, preparation_promise = ctx.awakeable()await ctx.run("prepare",lambda: restaurant.prepare(order["id"], preparation_id))ctx.set("status", "IN_PREPARATION")await preparation_promisectx.set("status", "SCHEDULING_DELIVERY")# 5. Find a driver and start deliveryawait ctx.object_call(start_delivery, key=order["id"], arg=order)ctx.set("status", "DELIVERED")