Coming from Temporal
Are you coming from Temporal?
This guide is meant to help you understand the differences and similarities between Temporal and Resonate.
Durability
One of Temporal’s core value propositions is Durable Execution. Essentially, Durable Execution is the ability for a function to recover in another process after a hard failure and resume from where it left off.
Resonate also provides Durable Execution.
In Resonate, if your process crashes, your function can recover and resume from where it left off in another process.
Programming model
If you have experience with Temporal, then you are familiar with thinking in terms of Workflows and Activities.
Resonate does not have proprietary function types such as Workflows and Activities; it just uses functions.
Resonate promotes a procedural programming model that enables you to compose functions indefinitely and recursively.
Consider this factorial example. How might you implement it in Temporal?
- Python
- TypeScript
See it in action: example-recursive-factorial-py
@resonate.register
def factorial(ctx: Context, n: int) -> int:
if n <= 1:
return 1
r = yield ctx.run(factorial, n - 1).options(id=f"factorial-{n-1}")
return n * r
def main():
result = factorial.run("factorial-5", n=5)
print(result)
See it in action: example-recursive-factorial-ts
function* factorial(ctx: Context, n: number): Generator<any, number, any> {
if (n <= 1) {
return 1;
}
const result = yield* ctx.run(factorial, n - 1);
return n * result;
}
const factorialR = resonate.register("factorial", factorial);
async function main() {
const result = await factorialR.run("factorial-5", 5);
console.log(result);
}
Zero-dependency development
In Temporal, you always need a Temporal Service for your Worker to connect to, whether that is a local development instance or a production instance.
Resonate offers a zero-dependency development experience, where a Worker (single node application) can run without connecting to a Resonate Server.
- Python
- TypeScript
from resonate import Resonate
resonate = Resonate.local()
#...
import { Resonate } from "@resonatehq/sdk";
const resonate = Resonate.local();
// ...
Human-in-the-loop
In Temporal, you can use Signals to unblock a Workflow Execution with input from a human.
With Resonate, you can achieve the same effect by creating a Durable Promise that is not attached to any function execution, and then await on it. Then, you can resolve it from anywhere, optionally sending data into the function while doing so.
- Python
- TypeScript
See it in action: example-human-in-the-loop-py
# worker.py
@resonate.register()
def foo(ctx: Context) -> None:
blocking_promise = yield ctx.promise()
# ...
# wait for the promise to be resolved
data = yield blocking_promise
# ...
# client.py
def main():
# ...
resonate.promises.resolve(
id=promise_id,
data=json.dumps(data)
)
See it in action: example-human-in-the-loop-ts
function* foo(context: Context) {
const blockingPromise = yield* context.promise();
// ...
// wait for the promise to be resolved
data = yield* blockingPromise;
// ...
}
// client.ts
await resonate.promises.resolve({ id: promiseId, data: JSON.stringify(data) });
Distributed execution
In Temporal, you can run multiple Workers to handle the load of your application. You use Task Queue Names to identify which Workers should handle which Workflows and Activities.
In Resonate, you can register workers in groups and send invocations to the groups. Resonate will automatically load balance the invocations across the Workers in the group.
- Python
- TypeScript
See it in action: example-load-balancing-py
# worker.py
resonate = Resonate.remote(
group="workers"
# ...
)
# client.py
resonate = Resonate.remote(
group="client"
)
def main():
# ...
result = resonate.options(target="poll://any@workers").rpc(promise_id, "function_name", params)
See it in action: example-load-balancing-ts
// worker.ts
const resonate = Resonate.remote({
group: "workers",
});
//client.ts
const resonate = Resonate.remote({
group: "client",
});
async function main() {
resonate.rpc(
promiseId,
"function_name",
params,
resonate.options({
target: "poll://any@workers",
})
);
}
Decentralized architecture
In Temporal, any given Workflow Execution is tied to a specific Temporal Service. This forces a star-like topology (centralized system) where all Workers in an application connect to a single Temporal Service.
Additionally, Temporal requires you to size your Temporal Service ahead of time to handle the load of all Workers in your application. If you size it incorrectly, you have to migrate to a new setup, which is a complex process.
Resonate applications, on the other hand, can use multiple Resonate Servers to durably store data (Durable Promises), which can be added dynamically as the application scales.
- Temporal
- Resonate
System complexity
The level of system complexity is perhaps the biggest difference between Resonate and Temporal.
Resonate at its core believes in simplicity.
This belief drives many of the design decisions in Resonate, and has resulted in a much smaller API surface than Temporal.
This smaller API surface and desire for simplicity have also resulted in a much smaller set of components.
For example, Temporal SDKs have tens of thousands of lines of code needed to manage the complexity of their implementation. Resonate SDKs, on the other hand, have but a few hundred lines of code.
A Temporal Service is a complex system consisting of multiple individual components, each consisting of tens of thousands of lines of code, that need to be configured, deployed, and scaled independently. A Resonate Server, on the other hand, is a single binary that is easy to deploy. And the Resonate system can scale by just adding more Resonate Servers, each with an underlying database.
Additionally, Temporal exposes many complex APIs to the developer which they are required to use for failure detection, load balancing, and more. Resonate's belief in simplicity means that much of this complexity is pushed into the platform and automatically handled for you.
For example, Temporal requires the developer to work with at least 4 different timeouts to detect and handle Activity Execution failures. It also requires the developer to manually develop heartbeats in the Activity, and tune the performance of Workers. Whereas, Resonate exposes a single timeout to the developer for the resolution of the Durable Promise, and it automatically handles failure detection, heartbeating, and load balancing across Workers.
Ultimately, as a developer, you will find many areas where Resonate has chosen to promote a specific model for the sake of simplicity, and to avoid indulging in what we believe to be unnecessary complexity.