⚠️ DISCLAIMER: This is an unofficial, experimental SDK and is not officially supported by Restate. Use at your own risk. For production use, please wait for the official Restate Ruby SDK release or consult the official Restate documentation.
The Restate SDK for Ruby provides a complete framework for building resilient, distributed applications using durable execution primitives.
Restate is a system for easily building resilient applications using distributed durable building blocks. It provides:
- Durable Execution: Automatic state persistence and replay
- Virtual Objects: Keyed, stateful services with exclusive access guarantees
- Workflows: Long-running orchestrations with durable promises
- Reliable Communication: Service-to-service calls with automatic retries
- Idempotency: Built-in deduplication of operations
Add this line to your application's Gemfile:
gem 'restate-sdk-ruby'And then execute:
bundle installOr install it yourself:
gem install restate-sdk-rubyThe SDK includes a Rust-based native extension that provides the Restate VM. To build it:
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# The extension will be built automatically during gem installation
# Or build manually:
cd ext/restate_sdk_ruby_core
cargo build --releaserequire 'restate'
# Basic stateless service
class Greeter < Restate::BasicService
handler :greet
def greet(input)
name = input["name"]
"Hello, #{name}!"
end
endRestate.configure do |config|
config.bind = "http://localhost:4100"
config.ingress_url = "http://localhost:8080"
config.admin_url = "http://localhost:9070"
end
Restate.endpoint.define do
mount Greeter
endThe SDK provides a Rack application. You can use any Rack-compatible server, but Falcon is recommended for production due to its HTTP/2 and fiber-based concurrency support:
# config.ru
require 'restate'
require_relative 'services'
run Restate.endpoint.to_rack_appOr use Falcon directly:
#!/usr/bin/env -S falcon host
# config/falcon.rb
require 'falcon/environment/server'
require 'restate'
require_relative 'services'
service "restate" do
include Falcon::Environment::Server
count 1
url { "http://localhost:4100" }
middleware do
Falcon::Server.middleware(Restate.endpoint.to_rack_app, verbose:, cache:)
end
endRun with: falcon serve -c config/falcon.rb
# From within another Restate handler (durable)
result = Greeter.call.greet(name: "World")
# From outside Restate (using HTTP client)
result = Restate.client.service("Greeter").greet(name: "World")Stateless services for independent, concurrent invocations:
class EmailService < Restate::BasicService
handler :send_email
def send_email(input)
to = input["to"]
subject = input["subject"]
# Durable execution - only runs once even on retries
run("send_via_api") do
EmailAPI.send(to: to, subject: subject)
end
end
endKeyed services with durable state and exclusive access per key:
class Counter < Restate::VirtualObject
state :count, Integer, default: 0
# Exclusive handler - can mutate state
handler :increment
def increment(amount = 1)
self.count += amount
count
end
# Shared handler - read-only, concurrent access
shared :get
def get
count
end
end
# Invoke with a key
Counter.call("user-123").increment(5)
value = Counter.call("user-123").getWorkflows with durable promises for complex, long-running processes:
class OrderWorkflow < Restate::Workflow
state :status, String, default: "pending"
# Main workflow entrypoint
def run(input)
order_id = input["order_id"]
# Wait for external confirmation
payment = promise("payment").value
self.status = "processing"
# Call other services durably
InventoryService.call.reserve(order_id: order_id)
ShippingService.call.ship(order_id: order_id)
self.status = "completed"
end
# Shared handlers for querying/completing
shared :get_status
def get_status
status
end
shared :confirm_payment
def confirm_payment(payment_info)
promise("payment").resolve(payment_info)
end
end
# Start workflow
OrderWorkflow.send("order-123").run(order_id: "order-123")
# Query status
status = OrderWorkflow.call("order-123").get_status
# Complete payment
OrderWorkflow.call("order-123").confirm_payment(amount: 100)Execute non-deterministic operations durably:
handler :process
def process(input)
# Result is journaled - block only runs once
api_result = run("fetch_data") do
ExternalAPI.fetch(input["id"])
end
# Process the result
process_data(api_result)
end# Sleep for 30 seconds
sleep(30)
# Or use a Time object
sleep_until(Time.now + 1.hour)# Call another service and wait
result = OtherService.call.method_name(input)
# Send one-way (fire and forget)
OtherService.send.method_name(input)
# Send with delay
OtherService.send(after: 1.hour).method_name(input)
# Call with idempotency key
OtherService.call.idempotency_key("unique-id").method_name(input)Create external completion points:
handler :start_task
def start_task(input)
awakeable = awakeable
# Send awakeable ID to external system
ExternalQueue.enqueue(awakeable_id: awakeable.id, task: input)
# Wait for external completion
result = awakeable.value
result
endComplete from outside:
Restate.client.resolve_awakeable(awakeable_id, payload)Restate.configure do |config|
# Local endpoint binding (where this service listens)
config.bind = "http://localhost:4100"
# URL Restate uses to reach this endpoint
config.advertise_url = "http://host.docker.internal:4100"
# Restate ingress URL (for invoking services)
config.ingress_url = "http://localhost:8080"
# Restate admin API URL (for registration)
config.admin_url = "http://localhost:9070"
# Auto-register on startup
config.create_deployment_on_start = true
# Identity verification keys (optional)
config.identity_keys = ["your-signing-key"]
# Custom JSON serializer (defaults to Oj)
config.default_json_serializer = CustomSerializer
endAdd custom middleware to the Rack stack:
middleware = Restate::MiddlewareStack.new
middleware.use Rack::CommonLogger
middleware.use MyCustomMiddleware, option: "value"
app = middleware.build(Restate.endpoint.to_rack_app)
run appThe SDK includes automatic Rails integration:
# config/initializers/restate.rb
Restate.configure do |config|
config.bind = "http://localhost:#{ENV.fetch('RESTATE_ENDPOINT_PORT', 4100)}"
config.ingress_url = ENV.fetch("RESTATE_INGRESS_URL", "http://localhost:8080")
config.admin_url = ENV.fetch("RESTATE_ADMIN_URL", "http://localhost:9070")
config.create_deployment_on_start = Rails.env.development?
end# config/services.rb
Restate.endpoint.define do
mount Greeter
mount Counter
mount OrderWorkflow
endServices are automatically loaded and reloaded in development.
For production use, your server must support:
- HTTP/2 (required for bidirectional streaming)
- Stream hijacking (for Restate protocol)
- Fiber-based concurrency (recommended for performance)
- Falcon (recommended) - Full HTTP/2 support with fiber concurrency
- Puma (limited) - HTTP/1.1 only, request-response mode
- WEBrick (development only) - Simple but not production-ready
See examples/ directory for configuration examples.
# Install dependencies
bundle install
# Run all tests
bundle exec rake test
# Run a specific test file
bundle exec ruby -Ilib:test test/utils_test.rbCore SDK tests are passing. Some complex tests that require the native Rust extension (VM-dependent tests like context_test.rb, handler_test.rb, etc.) are temporarily skipped with .skip extension.
These tests validate:
- ✅ Service definitions (BasicService, VirtualObject, Workflow)
- ✅ Configuration and utilities
- ✅ Codecs (JSON, Bytes, Void)
- ✅ Retry policies
- ⏭️ VM integration tests (skipped - require compiled native extension)
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Build the extension
cd lib/restate/ext
cargo build --release# Install dependencies
bundle install
# Build native extension
cargo build --release
# Run tests
bundle exec rake test
# Run a single test
bundle exec ruby test/basic_service_test.rbSee the examples/ directory for complete working examples:
examples/falcon/- Falcon server configurationexamples/basic_service/- Simple stateless serviceexamples/virtual_object/- Stateful counterexamples/workflow/- Order processing workflow
Bug reports and pull requests are welcome on GitHub at https://github.com/restatedev/sdk-ruby.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.