<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Bitovi Blog - UX and UI design, JavaScript and Front-end development
Loading

Backend |

Temporal Is a Swiss Army Knife For Your Distributed Systems

All distributed systems have a degree of complexity. Bitovi uses Temporal to handle errors, retries, run batch processes, and simplify your architecture.

Kevin Phillips

Kevin Phillips

Director of Backend Development

Twitter Reddit

Bitovi’s Backend Consulting team has had the pleasure of working with Temporal for several different use cases over the last few years. Temporal has greatly simplified complex distributed systems and helped our team focus more on achieving business goals and to spend less time handling errors, among many other things. Temporal isn’t a silver bullet, but it is helpful in a variety of different situations.

This post will explain many of the ways Temporal can help you build more reliable systems in fewer lines of code.

Note: the example code snippets in this post use the Temporal TypeScript SDK, but Bitovi’s engineers also have experience with other SDKs. Need help using Temporal to make your systems more reliable and easier to maintain? Reach out to us for Temporal consulting.

Error Handling, Retries, and Timeouts

Distributed systems come with complexity. Requests fail. Systems crash. Services get overloaded. The most impactful thing Temporal does is simplify error handling and related tasks like retries and timeouts.

When developing Temporal applications, you separate your deterministic code – your core procedural logic that will always behave the same no matter how often it is called – from your non-deterministic code – database requests, calls to external services, anything that uses the file system, etc. Deterministic code belongs in a Workflow, and non-deterministic code goes into Activities.

If an Activity fails—for any reason—Temporal will automatically retry that Activity for you. You can configure exactly how Temporal should perform retries – how often and how many times. Temporal also provides options for when Activities should time out and then be retried. Each Activity can be configured independently, giving you maximum control without requiring custom code.

From the Temporal UI, you can see how many times an activity has been attempted, the cause of any failures, when the activity will be retried next, and how many more times it will be attempted:

The Temporal UI shows information about each activity.

Simplifying Complex Architecture

Temporal’s out-of-the-box handling of retries and timeouts greatly simplifies your application architecture. Traditional solutions to these problems rely on queues and serverless functions, which can get complex very quickly. Just look at this “Simple” Queue Service workflow as an example:

Event "simple" AWS queues are complex. Temporal can replace all of this architecture.

(Photo Credit: https://aws.amazon.com/what-is/dead-letter-queue/)

With Temporal, all of the above infrastructure goes away. What’s simpler is how Temporal gives you a lot less to maintain. You run the Worker that runs your code and Temporal handles the rest. Infrastructure operations are also simplified; even in the case of your Worker crashing, Temporal will resume your workflows right where they left off, and Activities that already ran will not run again.

This truly simplified architecture also means that all of your code exists in a single codebase instead of being spread out across multiple serverless functions.

Batch Processing

Temporal can also greatly simplify architecture requirements for performing a single operation repetitively. Each batch can have a single parent workflow, and each operation in the batch can have its own child workflow, allowing increased visibility of the individual and aggregate operations in a batch.

This example shows how straightforward it is to send an email to a list of addresses by starting a child workflow for each:

export async function parentWorkflow(): Promise<void> {
  const childHandles: ChildWorkflowHandle<typeof childWorkflow>[] = []

  for (const emailAddress of emailAddresses) {
    const handle = await startChild(childWorkflow, {})
    childHandles.push(handle)
  }

  return Promise.all(childHandles.map((childHandle) => childHandle.result()))
}

For more information about using Temporal for batch operations, check out our deep dive: How to Use Node.js Temporal Workflows for Batch Processing.

Time-Based and Recurring Tasks

Temporal also allows you to handle time-based tasks within the same codebase. You can create a timer or sleep a workflow to trigger at a specific time. This makes it trivial to handle use cases like “cancel an order if it isn’t picked up before the store closes” right alongside the rest of your logic.

You can sleep a workflow for as long as you need without any performance or cost implications. This means that tasks like recurring billing become as simple as sleeping a workflow within a loop and triggering the billing Activity each time the workflow wakes up.

Having these timers right alongside the rest of your logic means the Temporal UI for the workflow shows you the full history of that workflow – you can easily see every time the billing Activity has been triggered and exactly when it will be triggered next:

Temporal shows you the full history of a workflow, including retries and payloads.

Tasks That Require Manual Intervention

There are times when your workflow doesn’t depend on something that takes a specific amount of time, but rather something that takes an indefinite amount of time. Tasks like calling out to a third-party API and waiting for a callback on a Webhook, waiting for an order to be delivered, or waiting for a data manager to manually review some data changes – all of these can take unpredictable amounts of time. It can be very complex to handle situations like these with traditional API architectures.

With Temporal, handling tasks of unpredictable duration is trivial. Temporal workflows can accept asynchronous messages called Signals that can be used to update state variables. Temporal also provides a condition API that can be used to sleep your workflow until a state variable has a specific value.

This example shows how to make an external request—in this case, requesting a data manager perform some manual review—and then wait for a signal back to indicate that the review is complete:

let dataManagerReviewComplete = false
setHandler(dataManagerReviewCompleteSignal, () => {
  dataManagerReviewComplete = true
})

await submitForDataManagerReviewActivity()

await condition(() => dataManagerReviewComplete === true)

Conclusion

Temporal is a programming model and set of SDKs that help to build reliable systems much more easily than traditional alternatives. Temporal can improve your systems by automatically handling errors from calls to external services, simplifying complex architectures, making it easy to handle batch operations, making time-based and recurring tasks trivial, and allowing you to handle tasks that require manual intervention just like the rest of your logic. If your application has a need for any of these things, you should give Temporal a try.

Want to Talk Temporal?

Our Temporal Consulting experts would be happy to assist you! Schedule a free consultation call to learn more. 

Schedule a Temporal Consultation