The Workflow Pattern
blog.bittacklr.be
blog.bittacklr.be
We use languages best suited to writing Windows GUIs or Linux kernels to implement business rules — and then we act surprised when we have to invent an entire language and runtime to solve simple business problems.
One missing feature in typical languages is the native ability to have a computation frozen in the middle of a function call and then defrosted and continue as if nothing had happened. The low-level mechanisms are often available already — such as object serialisation — but these primitives never support the serialisation of a call stack or an in-flight computation.
We even have compilers that can split up and restructure an async function into a heap object and an associated state machine!
What’s the difference between an async function that is awaiting a slow HTTP call and an async function awaiting a long-running workflow step? Only that the state machine of the latter is persisted to storage instead of the heap!
I always thought it was a bit silly that the async mechanism in modern languages is so myopic and single-purpose. These kinds of high level language transformations ought to be extensible and pluggable so that we could write workflows in a proper programming language and have it look like normal code except for the occasional “await” keyword.
PS: The same philosophy could be applied to Java Loom style async programming where threads could be marked as eligible for hibernation, in which case they would be restricted to using data types that can be safely round-tripped by the serialiser.
This is how Temporal works. For example, in Python the async event loop is replaced by a durable event loop [0], the JS promises become durable promises, .NET tasks become durable via a custom scheduler, etc. Granted it doesn't serialize the stack, it uses event sourcing almost exactly like the article describes, and therefore requires deterministic code for replaying. From the dev POV, it looks like any code can just be frozen in the middle of a function and magically resumed elsewhere.
0 - https://temporal.io/blog/durable-distributed-asyncio-event-l...
(disclaimer, I work at Temporal and have written some of these distributed coroutine impls)
They are a generic way to run some background process that doesn't need too much human interaction and can be coded in any language. If you need to orchestrate tens of thousands of tasks, you will beg for a DSL and some kind of interface to monitor things.
honestly, when working with some new archaic system, the ability to insert manual tasks is invaluable for an early deployment
Continuation-based web frameworks are really nifty, but have potential scaling bottlenecks. IIRC HN originally used some type of continuation web framework with Arc, but ran into scaling and caching headaches. Dunno how it works these days.
The above is succinctly implemented in wat: https://github.com/manuel/wat-js (also see forks for more documented versions).
For example, in some businesses, payment is like another communication. Not that sending a single message across is very simple thing, but definitely a lot less complicated than a payment process.
Maybe the split, should it be internaly-language-handled - and forgotten if power goes off - or externalized and persisted, should depend on where there is certain boundary being crossed? Defining "boundary" per project..
I have similar doubts about how important the difference is between programming as devs do it and these tools. But to me, it's less a language concern and more a question of how tasks/jobs/processes/whatever-you-call-them are managed, if they're managed at all. In programming languages today, devs spawn countless async sub-processes (promises, async tasks, whatever you call them) with abandon. And they're implicit and invisible in almost all systems. An operator or user isn't there introspecting what jobs are outstanding. Or managing the workflow of these subprocesses.
These workflow tools are just a tiny leap over what programmers do: they make computing real. They reify computing into a managed element, make work being done a kind of data that is tracked through the system. Where-as in most programming, we have lots of tools for authoring and finagling subprocesses, but we are so very lacking in tools to manage & expose & let the user control what's happening in the process. The processs is mostly-closed, except what is afforded, and what is afforded is largely custom-craft APIs (with folks like OTel starting to define some more common ways for processes to at least explain themselves, if not manage & interconnect with other processes).
I don't think the languages have to change. I think the languages can stay right where they are, for the most part. But we can and should be building better runtimes, that let us interact with the objects & subprocesses, that let the app be more commanded and turned into an task runner rather than also the command-pallet system, kind of has to happen. It's not super related, but there was a post on Audacity in the browser, where @solardev suggested "headless" WASM apps and then separate UIs, and I can easily see that idea applying here: a workflow pattern tool being the UI, that orchestrates/drives WASM apps (which are practically just libraries).
Found a link to it from telefork: https://thume.ca/2020/04/18/telefork-forking-a-process-onto-...
Its capabilities comes to light when you model really complex workflows and one real value is how its all very visual not just during modeling but when running it. The history remains visible and you can even see how the whole flow evolved.
That said, Conductor looks really cool, thanks for sharing.
In addition to everything the author mentioned, the constraints of state machines allow a workflow platform to provide a ton of additional guarantees and capabilities around consistency, state propagation, reliable timers, inter-instance messaging, etc.
We built our workflow execution platform [1] around state machines and we've seen great results. We find our workflow code is incredibly simple and easy to understand.
the other benefit of a statemachine is the ability to accurately determine what parts can be collapsed into subworkflows which allows for reuse, replacement, or general modification
It was hard to read and understand
CEP is not trendy, which is probably because of history and traditional implementations but I think it deserves a good look.
[0] https://github.com/Picolab/pico-engine is an implementation of the KRL
This was a few years ago but NiFi only supported editing the DAG from a GUI; you could store the resulting XML in git, but no human could read or edit it. Python steps were stuck on Python 2.6 or 2.7. Hard to debug pileups. Is it better now?