← All posts

Engineering — Headless Commerce

Why Business Logic Stops Scaling — and How Medusa Workflows Fix It

Checkout isn't a single action, it's a sequence of business decisions. Medusa Workflows make those decisions explicit, recoverable, and easier to change as a commerce platform grows.

Topic: Medusa v2 Workflows Read time: ~8 min Level: Architecture / business logic Published:
Overview

Most commerce backends start the same way: placing an order means validate the cart, create the order, reserve inventory, capture payment, send a confirmation email — five steps, one function. It stays that simple for a while. Then a loyalty program needs hooking in, orders need to sync with an ERP, and an enterprise customer needs manual approval before payment. Two years in, that once-simple function is usually the file every engineer on the team is most reluctant to touch. The problem was never that too little code was written — it's that the process kept changing while its representation in the codebase stayed flat. This piece looks at why that happens, and at how Medusa v2 addresses it by turning business processes into something the platform itself tracks, recovers, and can keep running for as long as the real world takes.

Why business logic doesn't stay small

Ask a support engineer where a stuck order actually failed in a typical Node/Express commerce backend, and the honest answer is usually: "let me find whoever wrote the checkout service and ask them."

That answer isn't a knock on any particular team — it's what happens to any process that only exists as imperative code. When business logic lives inside a single service, every new requirement competes for the same piece of code. The checkout flow that once handled five operations quietly grows to fifteen, because there's nowhere else for a new rule to go. Developers become reluctant to touch it, since any change risks breaking an unrelated feature three steps away. Eventually the service stops representing one responsibility — it becomes the place where every business rule ends up living, whether it belongs there or not.

One interesting thing about commerce systems specifically is that failures are rarely catastrophic. They're usually partial. Inventory was reserved. The payment failed. The customer received a confirmation email anyway. The warehouse never got the signal to hold the item. None of these are impossible to recover from — the trouble is that every recovery path has to be designed, written, and kept up to date by hand, and it's the kind of code nobody tests until the day it actually runs.

In practice, that produces a predictable set of symptoms as a product grows:

  • Adding a second payment provider means editing the same function everyone else's changes also live in
  • Nobody can answer "what happens if the warehouse webhook times out" without reading the whole chain
  • Undo logic — releasing a stock hold, refunding a charge — is written once, by hand, and quietly rots as the happy path changes around it
  • A crash halfway through leaves the business in a state nobody explicitly designed for

None of this is unique to Node.js, or to commerce. It's what tends to happen to any application whose business processes keep evolving while the architecture representing them stays flat.

Modeling the process, not the function

Medusa's response to this is to stop treating a business process as one function and start treating it as a first-class thing the platform itself understands. Its documentation describes the workflow engine as "a built-in durable execution engine to help complete tasks that span multiple systems" — and the operative word there is durable. A workflow is a named sequence of discrete steps, and each step is independently defined, independently recoverable, and tracked by the platform, rather than living only inside one function call that disappears the moment it returns.

Practically, that means "place an order" stops being one opaque block and becomes a visible pipeline: each step has a name and a defined answer to "what happens if this specific step fails" — instead of one shared, hand-written answer buried at the bottom of a 200-line function that everyone is afraid to edit.

One opaque process versus a visible pipeline Diagram comparing a single "Place an order" function with five hidden stages, against a Medusa workflow broken into named, connected steps: check stock, create, hold, charge card, notify buyer — each with a defined failure answer. TRADITIONAL "Place an order" one function, five hidden stages WORKFLOW check stock create hold charge card notify buyer each stage: named, owned, with a defined failure answer
One hidden process vs. a pipeline anyone on the team can point to and reason about

Recovery belongs to the step that needs it

The typical answer to "what if payment fails after we've already reserved stock" is a try/catch block someone wrote once, that has to be manually kept in sync with the happy path forever, and that nobody exercises until it fails for real. Medusa takes a different position: rather than asking developers to write rollback logic separately, it associates recovery with each individual step, as part of that step's own definition.

Stage: Hold Inventory
Does
Reserves stock for this order
If a later stage fails
Releases the hold automatically — no separate cleanup step to remember

Per Medusa's documentation, when a later step fails, the platform automatically undoes every step that already succeeded, in reverse order, on its own — no shared cleanup function, no ordering to get wrong under pressure. The recovery logic lives with the action it undoes, instead of in a separate place someone has to remember to keep in sync.

Automatic reverse-order recovery after a failed step Diagram showing stock held and order created succeeding, then card declined causing the process to halt; recovery then runs automatically in reverse order, cancelling the order and releasing the stock hold. ✓ stock held ✓ order created ✗ card declined process halts — recovery runs automatically, newest first: cancel order release stock
Payment fails → the two completed stages unwind themselves, newest first, with nobody paged

When a process takes real time

Not every business process resolves in the same second it starts. Waiting for a wire transfer to clear. Waiting for a shipment to be scanned as delivered. These usually get handled with duct tape — a cron job that polls a status field, a queue worker nobody fully trusts, and eventually a support ticket when the whole thing silently stalls and nobody notices for three days.

Medusa treats this as a native case rather than a workaround. A step can be told to keep retrying on an interval — Medusa's own example is exactly this scenario, "waiting for a payment to be captured or a shipment to be delivered" — and once it does, per the docs, the whole process becomes what Medusa calls a long-running workflow: it keeps running in the background for as long as it takes, without blocking anything or depending on a separate script someone has to remember exists. Ops can also nudge a specific stuck step by hand rather than restarting the entire process from scratch.

Where the reliability actually comes from

None of the recovery behavior above means much if the platform forgets a process's progress the moment the server restarts. That bookkeeping is a distinct, swappable piece of Medusa's architecture — the Workflow Engine, responsible for recording exactly which step of which process has run and what state it left things in.

Development default

In-memory tracking

  • Nothing to configure
  • Fine for local work and demos
  • Forgets everything if the process restarts
Production

Persistent tracking (Redis)

  • Remembers progress outside the running process
  • Survives a deploy, a crash, a restart mid-process
  • What makes a days-long process actually trustworthy

This is the difference between rollback as a claim and rollback as an operational guarantee. Without the persistent option running in production, a crash in the middle of capturing a payment loses the process's memory of what it had already done — precisely the failure mode this whole system exists to remove.

Where this shows up in practice

These aren't hypothetical — they're the shape of processes commerce teams actually run into once a store gets past basic checkout:

Checkout & order orchestration

Stock check, order creation, payment, fulfillment kickoff — one process, one place to see it end to end.

B2B approval flows

A buyer's order sits pending until a manager approves it — a process that can legitimately take days, not milliseconds.

Marketplace fulfillment

One order splitting across several vendors, each with its own shipment and its own failure mode to recover from independently.

Subscription billing retries

A declined card doesn't cancel a subscription outright — it retries on a schedule, exactly the "real time" case above.

Returns & refunds

Restock, refund, and notify have to happen together or not at all — a textbook case for defined recovery per stage.

ERP / warehouse sync

Keeping an external system in step with Medusa without a fragile custom polling script holding it together.

What a team gains

Representing a process as named steps instead of one function changes the day-to-day work in a few concrete ways:

  • Faster, safer iteration — changing how refunds work means editing one named step, not re-reading and re-testing an entire monolithic function
  • Visibility for people who don't read code — support and ops can see which named step an order actually stalled at, instead of filing a ticket back to engineering
  • Less manual firefighting — partial failures resolve themselves instead of becoming 2am incidents
  • Faster onboarding — a new developer can read a process as a sequence of named steps instead of reverse-engineering a 300-line function someone left the company having half-explained

What it costs

Representing business logic as workflows introduces additional structure, and structure is never free. Developers now have to think in terms of steps and compensations instead of just writing a function that does the thing. For a small application, that can feel like unnecessary ceremony — a process that touches one system and can't meaningfully fail halfway through doesn't need any of this; a plain function is still the right call there. But once a process spans multiple services, external APIs, and asynchronous waits, that same structure is what keeps the complexity from turning into the tangled service function this article started with.

  • A real learning curve — a team coming from traditional MVC-style backends has to unlearn "just write the function" as the default
  • An added production dependency — the durability that matters in practice requires running the persistent (Redis-backed) tracking layer, not the zero-config default
  • Still developer-authored — this isn't a no-code business-rules engine; a store manager still can't reshape a process without an engineer, they can just finally see it

How this compares to other platforms

Comparison of where business process logic lives and who can change it across Shopify, a traditional custom Node backend, and Medusa
PlatformWhere the core process livesWho can change it
Shopify Hidden inside Shopify's own core; merchants only get webhook/app extension points to react after the fact Only Shopify itself — apps observe, they don't rewrite the process
Traditional custom Node backend Imperative code inside services/controllers, undocumented outside the code itself Whichever developer is brave enough to touch that function
Medusa (workflows) An explicit, named, platform-tracked sequence of stages Any developer, one stage at a time, without re-deriving the whole process

The distinction isn't that Medusa has more features. It's that Shopify's extension model lets you observe and react to a process you don't control, while a fully custom backend gives you full control but no structure. Medusa's approach is aimed at giving teams both at once — full control over the process, with the platform still tracking and recovering it.

Medusa runs its own infrastructure on this

🚚 Medusa Cloud runs on the same engine

Medusa's own team didn't just ship this for merchants — they used it to build Medusa Cloud's own infrastructure provisioning: validating a GitHub repo, choosing a Dockerfile, provisioning a database, wiring up a build pipeline — all defined as steps in a workflow, with the same automatic recovery described above. In their own words: "the same principles that drive commerce operations in Medusa's core now also power the complex operations of Medusa Cloud." That's a different kind of evidence than a curated demo — the team that built the pattern is running its own production infrastructure on it.


Closing thoughts

Business logic tends to get more complex over time, regardless of the framework a team chooses. What changes is how that complexity gets represented. Traditional backends concentrate it inside services, where it accumulates until nobody wants to touch the file anymore. Medusa Workflows distribute it across explicit, recoverable steps that describe the business process itself, rather than hiding it behind one.

The result isn't just cleaner code. It's a system that's easier to understand, easier to modify, and considerably more resilient when the real-world failures that commerce systems inevitably hit — a declined card, a stalled webhook, a warehouse that never confirms — actually happen. For teams building commerce platforms meant to last years rather than months, that's often a more meaningful improvement than any single new feature.

  • Business processes tend to outgrow whatever function they started in — that's a symptom of shared code, not a skill gap.
  • Medusa Workflows turn a process into named, tracked steps, each with its own defined recovery.
  • Failures unwind automatically, in reverse order, without hand-written cleanup code.
  • Processes that take real time — approvals, shipments, billing retries — are handled natively as long-running workflows, not cron-job workarounds.
  • That reliability depends on running the persistent (Redis) tracking layer in production, not the zero-config default.
  • It's not a no-code tool — engineers still author every step — but the process itself becomes something the whole team can finally see.

Sources

  1. Workflows — Medusa Documentation
  2. Compensation Function — Medusa Documentation
  3. Retry Failed Steps — Medusa Documentation
  4. Workflow Engine Module — Medusa Documentation
  5. Building Medusa with Medusa: Workflows — Medusa Blog