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.
You implement your business logic in handlers that 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:
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 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
Durable RPCs and queues
Durable promises and timers
Durable promises and timers
Consistent K/V state
Consistent K/V state
Journaling actions
- TypeScript
- Java
- Go
- Python
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);}
@VirtualObjectpublic class OrderWorkflow {public static final StateKey<StatusEnum> STATUS = StateKey.of("status", 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(Boolean.class,() -> 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(Void.class);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);}}
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}
@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")