Microservice Orchestration
The simplest way to build resilient applications.
Restate serves as the resilience and durability layer for applications. Write regular functions and services, and let Restate make them resilient, consistent, and scalable.
Resiliency baked into familiar programming constructs
Consistent application state
Resilient communication and scheduling
Resilient Code Execution and Communication
Familiar programming constructs, but now resilient
Restate transforms the programming building blocks you already know into resilient constructs. Work with objects, functions, and promises as if failures don't exist. Restate can recover them anytime, anywhere.
Familiar programming constructs, but now resilient
Restate transforms the programming building blocks you already know into resilient constructs. Work with objects, functions, and promises as if failures don't exist. Restate can recover them anytime, anywhere.
Persist progress
Execute calls to other systems and APIs and persist their results. On retries, actions aren't re-executed —- previous results are recovered. Chain interactions with multiple systems, and Restate ensures all interactions complete successfully.
Persist progress
Execute calls to other systems and APIs and persist their results. On retries, actions aren't re-executed —- previous results are recovered. Chain interactions with multiple systems, and Restate ensures all interactions complete successfully.
Resilient RPC and messaging
Restate sits like a reverse-proxy or message broker in front of your services, proxying calls and ensuring they're delivered and processed. Make any type of call resilient:
- Request-response calls
- One-way message
- Scheduled tasks
Resilient RPC and messaging
Restate sits like a reverse-proxy or message broker in front of your services, proxying calls and ensuring they're delivered and processed. Make any type of call resilient:
- Request-response calls
- One-way message
- Scheduled tasks
Idempotency for free
Add an idempotency token to your requests and let Restate handle deduplication. Each request runs to completion exactly once.
Idempotency for free
Add an idempotency token to your requests and let Restate handle deduplication. Each request runs to completion exactly once.
- TypeScript
- Java
- Kotlin
- Go
- Python
const subscriptionService = restate.service({name: "SubscriptionService",handlers: {add: async (ctx: restate.Context, req: SubscriptionRequest) => {const paymentId = ctx.rand.uuidv4();const payRef = await ctx.run(() =>createRecurringPayment(req.creditCard, paymentId));for (const subscription of req.subscriptions) {await ctx.run(() =>createSubscription(req.userId, subscription, payRef));}},},})export type SubscriptionService = typeof subscriptionService;restate.endpoint().bind(subscriptionService).listen(9080);
const restateClient = restate.connect({ url: RESTATE_URL });const reservation = await restateClient.serviceClient<SubscriptionService>({ name: "SubscriptionService" }).add( // target handlersubscriptionRequest,Opts.from({ idempotencyKey: requestId }));
@Servicepublic class SubscriptionService {@Handlerpublic void add(Context ctx, SubscriptionRequest req) {var paymentId = ctx.random().nextUUID().toString();var payRef =ctx.run(STRING,() -> createRecurringPayment(req.creditCard(), paymentId));for (String subscription : req.subscriptions()) {ctx.run(() -> createSubscription(req.userId(), subscription, payRef));}}public static void main(String[] args) {RestateHttpEndpointBuilder.builder().bind(new SubscriptionService()).buildAndListen();}}
Client restateClient = Client.connect(RESTATE_URL);SubscriptionServiceClient.fromClient(restateClient).send().add(subscriptionRequest,CallRequestOptions.DEFAULT.withIdempotency(requestId));
@Serviceclass SubscriptionService {@Handlersuspend fun add(ctx: Context, req: SubscriptionRequest) {val paymentId = ctx.random().nextUUID().toString()val payRef = ctx.runBlock { createRecurringPayment(req.creditCard, paymentId) }for (subscription in req.subscriptions) {ctx.runBlock { createSubscription(req.userId, subscription, payRef) }}}}fun main() {RestateHttpEndpointBuilder.builder().bind(SubscriptionService()).buildAndListen()}
val restateClient = Client.connect(Config.RESTATE_URL)SubscriptionServiceClient.fromClient(restateClient).send().add(subscriptionRequest,CallRequestOptions.DEFAULT.withIdempotency(requestId))
type SubscriptionService struct{}func (SubscriptionService) Add(ctx restate.Context, req SubscriptionRequest) error {paymentId := restate.Rand(ctx).UUID().String()payRef, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) {return CreateRecurringPayment(req.CreditCard, paymentId)})if err != nil {return err}for _, subscription := range req.Subscriptions {if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return restate.Void{}, CreateSubscription(req.UserID, subscription, payRef)}); err != nil {return err}}return nil}func main() {if err := server.NewRestate().Bind(restate.Reflect(SubscriptionService{})).Start(context.Background(), ":9080"); err != nil {log.Fatal(err)}}
client := &http.Client{}url := fmt.Sprintf("%s/SubscriptionService/Add", RESTATE_URL)req, err := http.NewRequest("POST", url, subscriptionRequest)if err != nil {return err}req.Header.Set("idempotency-key", requestId)resp, err := client.Do(req)if err != nil {return err}defer resp.Body.Close()
subscription_service = Service("SubscriptionService")@subscription_service.handler()async def add(ctx: Context, req: SubscriptionRequest):payment_id = await ctx.run("payment id", lambda: str(uuid.uuid4()))pay_ref = await ctx.run("recurring payment",lambda: create_recurring_payment(req.credit_card, payment_id))for subscription in req.subscriptions:await ctx.run("subscription",lambda: create_subscription(req.user_id, subscription, pay_ref))app = restate.app([subscription_service])
response = requests.post(url = f"${RESTATE_URL}/SubscriptionService/add",json=subscription_request,headers={"idempotency-key": request_id,"Content-Type": "application/json"})
Regular functions and services, in your existing infrastructure
Your code runs just like before: as Java/NodeJS/... applications on FaaS, Kubernetes, servers, containers.
On-premises or in the cloud. Restate is a single binary that is easy to deploy and operate. Restate meets you where you are.
Sagas and Distributed Transactions
Restate guarantees code runs to completion, enabling you to implement resilient sagas in a try-catch block. Restate handles retries and recovery.
Track compensations
With durable code execution, sagas can be expressed purely in code. As functions execute, undo operations are tracked in a list.
Track compensations
With durable code execution, sagas can be expressed purely in code. As functions execute, undo operations are tracked in a list.
Guaranteed roll back
When an unrecoverable error occurs, previously completed changes are rolled back. Restate ensures all compensation actions run to completion.
Guaranteed roll back
When an unrecoverable error occurs, previously completed changes are rolled back. Restate ensures all compensation actions run to completion.
- TypeScript
- Java
- Kotlin
- Go
- Python
const subscriptionService = restate.service({name: "SubscriptionService",handlers: {add: async (ctx: restate.Context, req: SubscriptionRequest) => {const compensations = [];try {const paymentId = ctx.rand.uuidv4();compensations.push(() => removeRecurringPayment(paymentId))await ctx.run(() => createRecurringPayment(req.creditCard, paymentId));for (const subscription of req.subscriptions) {compensations.push(() => removeSubscription(req.userId, subscription))await ctx.run(() => createSubscription(req.userId, subscription));}} catch (e) {if (e instanceof restate.TerminalError) {for (const compensation of compensations.reverse()) {await ctx.run(() => compensation());}}throw e;}},},})
@Servicepublic class SubscriptionSaga {@Handlerpublic void add(Context ctx, SubscriptionRequest req) {List<Runnable> compensations = new ArrayList<>();try {var paymentId = ctx.random().nextUUID().toString();compensations.add(() -> removeRecurringPayment(paymentId));ctx.run(STRING, () -> createRecurringPayment(req.creditCard(), paymentId));for (String subscription : req.subscriptions()) {compensations.add(() -> removeSubscription(req.userId(), subscription));ctx.run(() -> createSubscription(req.userId(), subscription));}} catch (TerminalException e) {for (Runnable compensation : compensations) {ctx.run(() -> compensation.run());}throw e;}}}
@Serviceclass SubscriptionSaga {@Handlersuspend fun add(ctx: Context, req: SubscriptionRequest) {val compensations: MutableList<suspend () -> Unit> = mutableListOf()try {val paymentId = ctx.random().nextUUID().toString()compensations.add { removeRecurringPayment(paymentId) }ctx.runBlock { createRecurringPayment(req.creditCard, paymentId) }for (subscription in req.subscriptions) {compensations.add { removeSubscription(req.userId, subscription) }ctx.runBlock { createSubscription(req.userId, subscription) }}} catch (e: TerminalException) {compensations.reversed().forEach { ctx.runBlock { it() } }throw TerminalException("Subscription failed")}}}
type SubscriptionSaga struct{}func (SubscriptionSaga) Add(ctx restate.Context, req SubscriptionRequest) (err error) {var compensations []func() error// Run compensations at the end if err != nildefer func() {if err != nil {for _, compensation := range compensations {if _, compErr := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return restate.Void{}, compensation()}); compErr != nil {err = compErr}}}}()paymentId := restate.Rand(ctx).UUID().String()compensations = append(compensations, func() error {return RemoveRecurringPayment(req.CreditCard, paymentId)})_, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return CreateRecurringPayment(req.CreditCard, paymentId)})if err != nil {return}for _, subscription := range req.Subscriptions {compensations = append(compensations, func() error {return RemoveSubscription(req.UserID, subscription)})if _, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return CreateSubscription(req.UserID, subscription)}); err != nil {return}}return nil}
subscription_saga = Service("SubscriptionSaga")@subscription_saga.handler()async def add(ctx: Context, req: SubscriptionRequest):compensations = []try:payment_id = await ctx.run("payment id", lambda: str(uuid.uuid4()))compensations.append(lambda: remove_recurring_payment(payment_id))await ctx.run("recurring payment",lambda: create_recurring_payment(req.credit_card, payment_id))for subscription in req.subscriptions:compensations.append(lambda: remove_subscription(req.user_id, subscription))await ctx.run("subscription",lambda: create_subscription(req.user_id, subscription))except TerminalError as e:for compensation in reversed(compensations):await ctx.run("run compensation", compensation)raise eapp = restate.app([subscription_saga])
Stateful Entities and State Machines
Restate lets you implement stateful services by storing state directly in Restate.
Consistent state
Implement state machines where state transitions are always consistent with your code.
Consistent state
Implement state machines where state transitions are always consistent with your code.
Scalability, concurrency, consistency
Restate guards consistency by ensuring only one handler writes to a single state value at a time. Scale out without worrying about multiple writers, lost updates, race conditions, or inconsistencies.
Scalability, concurrency, consistency
Restate guards consistency by ensuring only one handler writes to a single state value at a time. Scale out without worrying about multiple writers, lost updates, race conditions, or inconsistencies.
Stateful serverless
Run stateful services on serverless infrastructure. Restate attaches the service's K/V state to each request, allowing your handlers to work with local state.
Stateful serverless
Run stateful services on serverless infrastructure. Restate attaches the service's K/V state to each request, allowing your handlers to work with local state.
- TypeScript
- Java
- Kotlin
- Go
- Python
const subscriptionObject = restate.object({name: "SubscriptionObject",handlers: {add: async (ctx: restate.ObjectContext, req: SubscriptionRequest) => {const paymentId = ctx.rand.uuidv4();ctx.set("subscription", "awaiting_payment")const success = await tryPaymentFiveTimes(ctx, req, paymentId);if(!success) {ctx.set("subscription", "payment_failed")return}ctx.set("subscription", "creating_subscription")await ctx.run(() => createSubscription(req.userId, req.subscription));ctx.set("subscription", "created")},},})export const awsLambdaHandler = restate.endpoint().bind(subscriptionObject).handler();
@VirtualObjectpublic class SubscriptionObject {StateKey<String> SUBSCRIPTION = StateKey.of("subscription", STRING);@Handlerpublic void add(ObjectContext ctx, ObjectUtils.SubscriptionRequest req) {ctx.set(SUBSCRIPTION, "awaiting_payment");var paymentId = ctx.random().nextUUID().toString();boolean success =ctx.run(BOOLEAN,() -> createRecurringPayment(req.creditCard(), paymentId));if (!success) {ctx.set(SUBSCRIPTION, "payment_failed");return;}ctx.set(SUBSCRIPTION, "creating_subscription");ctx.run(() -> createSubscription(req.userId(), req.subscription()));ctx.set(SUBSCRIPTION, "created");}}class MyLambdaHandler extends BaseRestateLambdaHandler {@Overridepublic void register(RestateLambdaEndpointBuilder builder) {builder.bind(new SubscriptionService());}}
@VirtualObjectclass SubscriptionObject {companion object {val SUBSCRIPTION = KtStateKey.json<String>("subscription")}@Handlersuspend fun add(ctx: ObjectContext, req: SubscriptionRequest) {ctx.set(SUBSCRIPTION, "awaiting_payment")val paymentId = ctx.random().nextUUID().toString()val success = ctx.runBlock { createRecurringPayment(req.creditCard, paymentId) }if (!success) {ctx.set(SUBSCRIPTION, "payment_failed")return}ctx.set(SUBSCRIPTION, "creating_subscription")ctx.runBlock { createSubscription(req.userId, req.subscription) }ctx.set(SUBSCRIPTION, "created")}}class MyLambdaHandler : BaseRestateLambdaHandler() {override fun register(builder: RestateLambdaEndpointBuilder) {builder.bind(SubscriptionObject())}}
type SubscriptionObject struct{}func (SubscriptionObject) Add(ctx restate.ObjectContext, req SubscriptionRequest) error {restate.Set(ctx, "subscription", "awaiting_payment")paymentId := restate.Rand(ctx).UUID().String()success, err := restate.Run(ctx, func(ctx restate.RunContext) (bool, error) {return CreateRecurringPayment(req.CreditCard, paymentId)})if err != nil {return err}if !success {restate.Set(ctx, "subscription", "payment_failed")return nil}restate.Set(ctx, "subscription", "creating_subscription")if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return restate.Void{}, CreateSubscription(req.UserID, req.Subscription)}); err != nil {return err}restate.Set(ctx, "subscription", "created")return nil}func main() {handler, err := server.NewRestate().Bind(restate.Reflect(SubscriptionObject{})).Bidirectional(false).LambdaHandler()if err != nil {log.Fatal(err)}lambda.Start(handler)}
subscription_object = VirtualObject("SubscriptionService")@subscription_object.handler()async def add(ctx: ObjectContext, req: SubscriptionRequest):payment_id = await ctx.run("payment id", lambda: str(uuid.uuid4()))ctx.set("subscription", "awaiting_payment")success = await ctx.run("recurring payment",lambda: create_recurring_payment(req.credit_card, payment_id))if not success:ctx.set("subscription", "payment_failed")returnctx.set("subscription", "creating_subscription")await ctx.run("subscription",lambda: create_subscription(req.user_id, req.subscription))ctx.set("subscription", "created")aws_lambda_handler = restate.app([subscription_object])
Detailed Observability
Restate tracks communication and execution, giving it a unique position for observability.
Understand what is happening in your distributed applications, by using the UI, the CLI, and the built-in tracing.