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.
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.
- 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.
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.
In-memory tracking
- Nothing to configure
- Fine for local work and demos
- Forgets everything if the process restarts
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
| Platform | Where the core process lives | Who 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'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.