< Back to Blogs

Write the recipe, not the code.

blog-image

Workflows are everywhere in software delivery. A document moves from draft to review to published. A task moves from open to in progress to closed. A payment moves from initiated to authorized to settled. Underneath, every one of those flows is the same shape: a set of states, the conditions that move you between them, and an action that fires on the way.

For most teams, managing that logic means writing it again for every project, every flow, every variation. It works, but it doesn’t scale cleanly, and it’s slow to adapt when requirements change. That’s the problem our codeless workflow engine was built to solve.

Think of a kitchen

A recipe is written once. Every cook who works the kitchen follows the same recipe with the same ingredients, the same sequence, and the same result. The recipe isn’t rewritten each time a new cook arrives; the cook reads the rules and follows them.

Our workflow engine works the same way. The rules are written once, in JSON. The engine reads them and guides what happens next for every state transition, condition, and action, all in line with what the JSON defines. The logic lives in configuration, not in code, so changing a flow doesn’t require a developer to rewrite the application. It means updating the recipe.

What a workflow actually is

A workflow is a set of states and the transitions between them. Every workflow starts blank, with nothing having happened and no action taken. From there, the engine moves through the defined states based on the conditions it meets.

In our implementation, workflows are defined entirely in JSON, which describes three things: the states that exist, the conditions that trigger transitions from one state to the next, and the actions taken when those transitions occur. The engine reads the definition and manages every transition automatically, without any hardcoding or manual intervention, unless the workflow explicitly asks for it.

What that looks like

Here’s a simplified content-publishing flow, written the way the engine reads it:

{ "workflow": "content_publishing", "initialState": "draft", "states": { "draft": { "transitions": [ { "on": "submit", "to": "review", "action": "notifyReviewer" } ] }, "review": { "transitions": [ { "on": "approve", "to": "ready", "action": "lockContent" }, { "on": "reject", "to": "draft", "action": "notifyAuthor" } ] }, "ready": { "transitions": [ { "on": "publish", "to": "published", "action": "pushLive" } ] }, "published": { "final": true } } }

A draft can be submitted for review. A reviewer can approve it (moving it to ready) or reject it (sending it back to draft). Once ready, it can be published. That’s the entire flow. Business logic that's common and predictable lives in configuration. Domain-specific computation lives in pluggable functions that the engine calls by name, just a file describing states, the events that move between them, and the action each move triggers. A product manager or QA engineer can read that and know exactly how content behaves, without ever opening the codebase.

Isolation by design

A workflow is deliberately locked to the project it was created for; it can’t be dropped into another project without being reconfigured. That’s a feature, not a limitation. It keeps each project’s flows clean and prevents one project’s logic from quietly interfering with another’s, the kind of cross-contamination that’s painful to debug later.

Why JSON

Three reasons it earns its place:

  • It’s readable. A product manager or QA engineer can look at a definition and understand what it does without reading application code.
  • It’s versionable. Every change to a workflow is a change to a file, so it flows through the same version control as everything else in the pipeline. The history of every workflow change is traceable.
  • It’s portable. The engine reads any valid JSON definition, so it doesn’t need to change when a new workflow is added. You write the recipe; the engine runs it.

What do these changes mean for DevOps

Not all changes are equal, and that distinction is what matters.

No deployment needed: Adding states, updating permissions, adjusting SLA timers, swapping notification templates- these are all JSON updates. Reviewed, versioned, and shipped through the pipeline without a build.

Deployment, like adding new action functions with custom domain logic, is still required.

These are written once in code, registered by name, and from that point, the JSON can invoke them freely, with no further code changes needed.

Where it sits today

The engine is already live in client projects. We’re now progressively rolling it into our own product development by moving internal flows that used to be hardcoded onto the same engine that powers those client implementations. The direction is one we’re committed to: one engine, any workflow, defined in configuration rather than code.

When it’s the right tool

A configuration-driven engine isn’t the answer to every problem, and it isn’t meant to be. Its sweet spot is flows whose shapes are stable but whose details change often, such as approval chains, status lifecycles, and multi-step processes that vary from one client to the next. When a flow needs arbitrary computation rather than well-defined states and transitions, code is still the right home for it. Knowing where that line sits is part of using the engine well.

The takeaway

When a new flow is needed, the team writes a recipe. The engine reads it and guides what happens next. For the people building on top of it, that turns a development task into a configuration change, making it faster to ship, easier to review, and safer to roll back.

That’s the whole idea.

Author:
Karthik Gowrishankar

Related Posts