Alternative To Temporal
Didact as an alternative to the Temporal workflow system.
Introduction
With an impressive history starting out as an open source Uber product previously called Cadence, Temporal has appeared in recent years as a new workflow execution system for software teams to use. Temporal is a massive platform and offers some interesting perspectives on the philosophy of workflow execution.
In this article, we explore some of the primary differences between Didact and Temporal with a heavy focus on intended use cases, mechanical differences, and differences in business models.
Use Cases
Although Temporal and Didact share some commonalities with each other, I would argue that they are more different than alike. Perhaps the best place to start with their differences is understanding their difference in use cases and problem spaces.
Massive Distributed Systems
If two words primarily come to my mind when I think of Temporal's use cases, those two words would be distributed and microservices. More specifically, I would say that Temporal is designed for massive distributed systems that compromise of many microservice applications.
These sorts of systems would be almost always cloud based - think Azure, or AWS, or Google Cloud Platform - and would likely be maintained by large developer teams from large enterprise organizations. After all, one of the main benefits of microservices is supposed to be that they allow extremely large developer teams to concurrently build and maintain a massive platform with the platform broken into independent "services" for the various teams to manage. These distributed systems are likely quite large, quite complex, and quite expensive.
Microservices
And in terms of microservices, I think one of Temporal's goals is to allow developers to coordinate complex workflows that span large assortments of said microservices. Coordination of this type typically involves some sort of distributed transaction - more on that below - and requires robust workflow mechanics like automatic retries, durability, replayability, and so on.
For example, let's go with the classic "customer places an order on your eCommerce website" scenario. Let's say that when a customer places an order on your website, several different actions need to occur to fully process the order business transaction:
- Check item's available inventory.
- Insert record into Orders database table.
- Process customer payment.
- Send receipt to customer's email.
In a microservice architecture, each of these actions might need to take place in an entirely separate application. In our example, assume you have a dedicated inventory app, order processor app, payment processor app, and email-sending app, and processing an order in its entirety requires you to interact with all four of these microservices.
Temporal is designed to assist you with making a large, sweeping transaction that invokes the necessary actions within each of these microservices. You can almost think of Temporal as an overall platform coordinator: it takes a birds eye view from above of your microservice architecture and ensures that your business transaction touches each required component.
This goes hand in hand with the concept of distributed transactions and the various flavors that they come in.
Individual Applications
Continuing with our order processing example, I would say that Didact is not necessarily concerned with coordinating a distributed workflow across your microservices.
Instead, I would say Didact is concerned with background job processing and asynchronous processing for an individual microservice itself.
Now, in truth, Didact Flows
can contain any C# code that you want. The ExecuteAsync
method can contain any business logic and do any sort of work that you need, so could you run a distributed transaction within a Flow? Absolutely, your distributed transaction could be making a few asynchronous calls to your microservices, and there's certainly nothing about a Flow that prevents you from doing that.
Zoom In vs. Zoom Out
In simplest terms, think of the target use cases for Didact and Temporal like this:
Temporal is responsible for coordinating distributed, complex transactions that span multiple microservices, or individual applications, that form a larger overall distributed system. Temporal is zoomed out.
Didact, on the other hand, is responsible for the background job processing, asynchronous processing, and job automation of an individual microservice, an individual application. Didact is zoomed in.
So going back to our eCommerce customer order example, if each event that must take place is a separate microservice, then:
- Temporal is intended to help you durably and intelligently execute a distributed transaction across those microservices.
- Didact is intended to help you durably and intelligently execute a Flow, or job, for each individual microservice itself.
Temporal is zoomed out, Didact is zoomed in.
Does this mean that you can't zoom in with Temporal and that you can't zoom out with Didact? Not at all, both Temporal and Didact can handle the other's primary use case. But again, we are talking about primary use cases here, and I think each technology is out to solve a different level of the problem.
Monoliths and Small Distributed Systems
While massive distributed systems and hyperscaling are very fun topics to discuss, I often remind myself that a large number of - perhaps even most - organizations simply do not require massive distributed systems or hyperscaling. I think there's an argument to be made for the small to medium sized businesses (SMBs) that run off of either:
- Small microservice architectures, or
- Monolithic architectures.
In these cases, I really think a larger workflow tool like Temporal is not quite necessary; you are not having to coordinate distributed workflows across swaths of microservices.
I would instead argue that, in these cases, Didact is likely more compatible with the simpler, straightforward technological choices that you and your team have made, especially for monoliths.
Polygot vs. First Class
One major advantage that Temporal has over Didact is the various language SDKs that it offers.
Didact is specifically and exclusively dedicated to dotnet - specifically C# - alone, whereas Temporal offers an SDK in multiple languages like Typescript, Go, and Java. This is especially useful for companies with polygot architectures. If you aren't familiar with that term, it essentially means that a company's various software teams use different languages.
For example, the inventory team could use Java, the orders team could use Typescript with NodeJS, and the payment processor team could use Go. If you're a small or medium sized business, that might sound silly, but it can happen in large enterprises.
Again, notice that complex environments like polygot architectures usually only occur in very large organizations.
In this case, Temporal offers a nice solution that could satisfy the language requirements of multiple software teams within the same polygot organization.
Didact does not offer SDKs in other languages because, again, it exclusively targets dotnet, but I tend to look at that as an advantage in most cases. While it is true that Didact can't satisfy the needs of a massive polygot organization, what it can do is maximize the potential and power that dotnet offers. Dotnet is a first-class citizen with Didact which means that dotnet gets all of my attention to itself.
It never fails that each language and each framework has its own pecularities, and to have the best solution for a given language or framework, a developer needs to be willing to "get their hands dirty" and deep dive into the nitty-gritty specifics of said language or framework. The longer that I'm a software developer, the more I realize that the small things matter; that users tend to get the most benefit from the smallest opinionated choices; that going the extra mile and offering an extra tight integration, or an extra robust API, really speaks to the users of a devtool.
I cannot promise Didact to be the best polygot workflow solution, but I can promise Didact to be the best dotnet workflow solution.
Job Mechanics
Flows and Workflows
Temporal has two important constructs for you to learn about: Workflows and Activities.
Starting with Workflows, I look at them as the sort of "parent" execution objects. Workflows contain all of your business logic in one scope and act as a parent reference for the more detailed metadata. Workflows have various configurations that you can play with, but really the bulk of your work is supposed to take place in Activities.
In Didact, you could say that a Flow
is similar to a Temporal Workflow. A Flow is the parent construct, as well, that encapsulates all execution metadata configurations. And within the Flow's ExecuteAsync
method, you write all of your actual code to execute. Here within the ExecuteAsync method, you have the option to use Blocks
or not to use them, so the Flow itself might be your only execution object depending on what tools from Didact you choose to use.
Blocks and Activities
Temporal's designers really intend for you to write most of your configurations, such as the actual code to execute, retries, and so on, within individual Activities within your Workflow. The Activities are essentially wrappers for the actual atomic chunks of work to be done, and Temporal strongly encourages you to write your Workflows using them.
I would say that Blocks
in Didact are very similar to Temporal Activities. Blocks encapsulate delegates for you to execute. And remember, a delegate can be just about anything, such as a chunk of code, a lambda function, a method from a class library, and so on. The Blocks do not exist in isolation, but rather, you utilize them within the Flows
ExecuteAsync
method. Blocks are optional, but you are encouraged to use them to reap many of the same benefits that Temporal offers through its Activities.
Long-lived Transactions
One feature that Temporal prides itself on are long-lived transactions. However, don't mistaken these for long-running - sometimes called cpu-intensive - transactions, as these are two very different concepts.
Traditionally, what I would consider a long-running transaction is a transaction that is processed in one fell swoop, likely requiring a cpu core for the duration of the transaction's lifespan. Something that is long-running is actively consuming compute resources, whether it be a thread, a cpu core, whatever.
However, Temporal has the concept of a long-lived transaction. What's the difference? Think of long-lived transactions as code that executes over long periods of time but that spends considerable chunks of time not actively consuming compute resources. This is actually something I expound upon in more detail below, as Temporal speaks a lot about this feature.
Within Didact, I would say the main focus is to make your Flows run "in process". For example, if a Flow contains three Blocks that it's using, that Flow would not execute a Block, then pause execution and requeue itself to the database. Rather, since Blocks are defined in a Flow's ExecuteAsync
method, Didact would simply run the method in its entirety from top to bottom. Think of Didact Flow's as being focused on "in process" work, executing code in complete passthroughs and bounded timeframes, not really intended to be run endlessly for weeks or months long. Didact is also extremely optimized for asynchronous work like database calls or API calls, and it's a smaller, more lightweight platform which makes execution throughput a breeze.
Durable Timers
In Temporal, each Activity in a Workflow can be executed with delays between them. However, these delays are not "in process" delays like you would typically think. In other words, they are not delays which actively consume compute resources. For example, don't think of something like Task.Delay(1000)
or Thread.Sleep(1000)
. Both of these C# methods are actively awaiting Tasks or putting threads to sleep, but they are doing so in an active, ongoing process.
In Temporal, Workflows and Activities can durably sleep which, in Temporal's platform, means that:
- Code execution is halted.
- Current progress is recorded.
- Metadata is written to the database with data about when to reawaken the Workflow or Activity. Durable sleep has now begun.
- Then internal mechanisms within Temporal check the database periodically and schedule the Workflow or Activity for rewawaking when the durable sleep time expires.
Think of durable timers in Temporal as server-side, "out of process" sleeps. You execute, then stop and go back into the database, sleep for a bit, then await an alarm clock to rewaken.
Does Didact support durable timers?
At the moment, for Didact's initial versions, no, it does not currently offer durable timers. However, with the usage of Blocks
, I do not believe this would be difficult to add. For example, if I were to add functionality to track Block execution within a Flow Run's execution, then Didact could reawaken and replay Flows while skipping already-executed Blocks. This would be something much, much more similar to what Temporal offers.
If this feature interests you, please check out the feedback board or contact me.
Retries
Temporal offers retry policies at both the Workflow and Activity levels. According to their docs, however, by default they do not assign retry policies to Workflows, but they do assign retry policies to Activities. Their reasoning behind this is that, again, a Workflow is intended to be broken into manageable, atomic execution components - the Activities - and the Activities themselves should be durable, idempotent, and handle any necessary retry behavior.
The retry policies are also configurable to emulate various resiliency patterns, such as:
- Constant-delay retries.
- Linear backoff retries.
- Exponential backoff retries.
Likewise, Didact also offers retries at both the Block and Flow levels, and Didact's retry policies are also configurable to emulate different resiliency patterns.
Replayability
Temporal's Workflows offer native replayability via Activities and its event sourcing data model. This is essentially a reexecution of a Workflow.
As alluded to above, Didact is not specifically targeting replayability at the moment, but it would not be difficult to add.
If this feature interests you, please check out the feedback board or contact me.
Eventual Consistency
With Temporal's Workflows, Activities, retry policies, and so on, you can implement a variety of different types of distributed transactions. Workflows are "unopinionated" in a sense and offer common building blocks for you to implement more advanced distributed transaction patterns such as saga transactions.
Specifically, though, Temporal Workflows are better suited for eventually-consistent transactions. I don't really think that distributed transactions like two-phase commits would work with Temporal.
Similarly to what I stated above, Flows and Blocks can be further molded to offer transactional templates for you if you are interested in that feature. For example, I could add some functionality in the data model and APIs to allow you to create SagaFlows (Flows built for saga transactions). Let me know if this might interest you.
Monetization
As we come to a close on this comparison, I want to move to one final umbrella topic: business models.
While I readily admit that business models, profitability, and other such business-related topics might not be very interesting to developers, I do think it's something important for you and your team to consider when you are choosing a new technology for adoption.
First, let's discuss monetization.
Saas Offering
Temporal's primary monetization strategy is pretty common with many commercial open source companies today: they offer cloud hosting for their open source platform.
The thought behind this is that it's cumbersome to setup and maintain the infrastructure required for Temporal's various applications, not to mention keeping up with the applications themselves. So, to simplify your life and save yourself some infrastructure headaches, you can choose a cloud plan with them and run your workflows in their cloud infrastructure.
Ideally, since their team are the maintainers of the Temporal platform, they will naturally know the best optimizations and scaling strategies to maximize your usage, and they will likely setup failover and redundancy strategies for you to ensure platform uptime (though probably at a higher cost).
Self-hosted
Now, to be clear, Temporal absolutely does allow you to self-host their platform. They have at least one guide that I've seen which walks you through the steps of setting up the various components of their platform on your own infrastructure. However, to my knowledge, they do not offer anything related to support for self-hosted deployments. Their monetary incentivization is definitely geared towards their hosted SaaS offering, so if you run into issues or are looking for guidance on self-hosted deployments, then, outside of their documentation, you are on your own.
Didact, on the other hand, is - at least for the moment - focused exclusively on self-hosting. I do not currently offer a SaaS hosted option for you to use Didact, and that's because all of Didact's early adopters (literally every single one) indicated that they were not interested in a SaaS solution. They told me that job orchestration is too sensitive, too precarious, and that they want their business logic running on their infrastructure only; I understand why and empathize with them.
Consequently, my entire business model for Didact is centered around you self-hosting my product: that is the one and only way I am trying to monetize Didact. A few corollaries that follow from this are that:
- I expect to have to help you with on premise setups. That's part of the self-hosting game.
- I am incentivized to provide you with extremely clean, actionable documentation so that you can manage Didact on your own. In particular, one of my primary goals with Didact Docs is to provide you with useful deployment guides on how to get Didact setup and optimization guides to help you scale job throughput.
- And to be honest, I kind of like the idea of you self-hosting Didact. If something goes wrong, ideally you and your internal teams will get a call first, and then you'll come to me second if you cannot resolve an issue on your own. That's a nice middle ground for a solopreneur like me.
While it is technically true that I have a small SaaS component for Didact - namely the licensing server for paid users - this is something I have mitigations and preparations for. You can read more about the licensing server on Didact Docs or email me with any concerns you might have about it.
Bootstrapping vs. Venture Capital
Alongside monetization, I also want to draw attention to growth strategies. In particular, I want to explore two very different startup philosophies: bootstrapping vs. raising venture capital.
There is a plethora of material that has already been spoken on this matter over and over again, just go watch some MicroConf or Y Combinator videos on YouTube for starters. However, I would be remiss if I told you that I don't have some very strong beliefs about these two philosophies.
In simple terms, I think that most businessses, including tech companies, should be bootstrapped. When I say bootstrapped, I mean that I think a company should not raise money from investors like venture capitalists; instead, they should focus on profitability from day one, ensure that founders retain equity, and try to keep as much outside influence as possible away from the startup-to-customer relationship.
The Dangers of Venture Capital
I think one of the greatest dangers to venture capital funded businesses is the distractions from outside influences. I'm always so surprised at how consistently I hear various founders from Silicon Valley talk about how - in often morbid reflections - they regret raisng large sums of money for their startups. After all, raising rounds of venture capital can be quite glamorous for a startup; they receive media attention, perhaps have a TechCrunch article written about them, probably go viral on Twitter for a day or two, and so on. What's not to like about that?
But over and over, I've heard various founders talk about the pressures from investor partners, how many of them try to push a founder and their startup into directions and markets that they don't really belong. One important thing to remember is this: if you as a founder raise venture capital, you've made promises to investors that you are going to "unicorn scale" your startup. We are talking massive growth, humongous markets, tens (hundreds?) of millions of dollars in annual revenue. Think "the next Facebook", "the next Netflix", and so on.
While this all sounds great on paper, the truth is that limitless growth is riddled with challenges. Most startups never achieve the growth requirements of venture capital which is why they often quietly shutdown, disband, or get sold for pennies. And for the companies that do achieve venture-level growth, maintaining that growth and the company itself is very, very difficult.
The staff numbers grow, more middle management is introduced, founders become further and further removed from the frontlines of talking with customers directly and transition to executive roles, the products often become bloated with unnecesary features, and so on.
And if revenue targets are not eventually hit, and if some sort of exit or IPO or whatever is not eventually reached, then that company will ultimately not survive.
It's a massively-rewarding but extremely risky game to play, one that 90% of startups fail.
The Durability of Bootstrapping
On the complete opposite side of the spectrum, you have bootstrappers and solopreneurs (or crazy founders that are both, like myself). We make no over-arching promises to investors, we don't feel the need to hypergrow into limitless amounts of annual revenue, we don't have to target massive markets, we like to stay on the frontlines and talk to our customers directly, and we are interested in a sort of business that is profitable and sustainable from day one.
In other words, we just want to build a boring, normal, good-ole-fashioned business.
These sorts of businesses, once they achieve profitability, are extremely durable. They survive good and bad economies, good and bad interest rates, they are much less risky, and in general can win, if nothing else, through gritty war of attrition. In other words, as long as the founder sticks around, the business isn't going anywhere.
And since I don't have hypergrowth, venture-scale requirements, I don't have to live in constant fear of being sold off for pennies, or being voted off of my own board of directors, or being pushed into directions that Didact has no business going towards for the sake of boosting revenue.
This is the type of business that I aspire Didact to be. I want a profitable, sustainable business that allows me to work on Didact fulltime, a business that I can easily automate, a business where I can speak directly with users and customers (remain on the frontlines), and, ultimately, a business that I deeply enjoy running.
If it's something that I love doing, I'm all the more incentivized to stick around for the long haul and provide you with an excellent job orchestrator. And Didact is a labor of love for me, data engineering, job orchestrators, asynchronous processing, cpu cores, and all the likes are topics I am deeply passionate about as a founder and developer.
That, I think, makes both me and Didact durable. We aren't going anywhere.
- Introduction
- Use Cases
- Massive Distributed Systems
- Microservices
- Individual Applications
- Zoom In vs. Zoom Out
- Monoliths and Small Distributed Systems
- Polygot vs. First Class
- Job Mechanics
- Flows and Workflows
- Blocks and Activities
- Long-lived Transactions
- Durable Timers
- Retries
- Replayability
- Eventual Consistency
- Monetization
- Saas Offering
- Self-hosted
- Bootstrapping vs. Venture Capital
- The Dangers of Venture Capital
- The Durability of Bootstrapping