A Transaction Is a Graph Before It Is a Transaction

Kai Nguyen

Hatched by Kai Nguyen

Aug 27, 2026

11 min read

92%

0

What if the hardest part of a distributed transaction is not making every service agree, but discovering the order in which agreement is even possible?

A customer clicks “Place order” and expects a single event. Behind that click, however, a system may need to reserve inventory, authorize payment, create a shipment, issue a confirmation, and update a loyalty balance. To the customer, this is one action. To the architecture, it is a network of dependencies spread across independent databases, processes, and failure domains.

This creates a useful connection between two ideas that are usually taught separately: topological sorting in graph algorithms and the Saga pattern in distributed systems. One determines how dependent work can proceed. The other determines how a system can recover when that work cannot complete as planned.

Together, they suggest a broader principle:

Reliable distributed workflows are not merely sequences of actions. They are dependency graphs equipped with a theory of recovery.

Once you see transactions this way, ordering, failure handling, orchestration, and event design become parts of the same problem.

Every Business Process Hides a Dependency Graph

A linear checklist is often a misleading representation of a business process. Consider this simplified order flow:

  1. Validate the order.
  2. Reserve inventory.
  3. Authorize payment.
  4. Create shipment.
  5. Send confirmation.

The list implies that each step must happen exactly after the previous one. That may be convenient for documentation, but it is not necessarily true. Payment authorization and inventory reservation may be independent after validation. Sending an email may depend on the order being accepted, but it may not need to block shipment creation. A loyalty update might happen later without affecting the customer’s ability to receive the package.

The more accurate model is a directed graph. Each operation is a node. An arrow from one node to another means that the first must happen before the second. A node with no incoming edges is a source: it has no unmet prerequisites. A node with no outgoing edges is a sink: nothing else depends on it.

For example:

Validate order
   /          \
Reserve stock   Authorize payment
   \          /
    Confirm order
          |
    Create shipment

A topological sort converts this partial ordering into a valid linear sequence. Crucially, it does not invent dependencies that do not exist. If stock reservation and payment authorization are independent, the system can choose either order, or perform them concurrently.

That distinction matters operationally. A workflow designed as a rigid chain tends to be slower, more fragile, and harder to evolve. A workflow designed as a graph exposes genuine constraints while leaving independent work free to proceed.

The first insight, then, is simple but powerful: before deciding how to coordinate a transaction, identify what actually depends on what.

Many transaction failures begin with a false dependency. A team makes one service wait for another because the process was originally described as a list. Later, that wait becomes a timeout, the timeout becomes a retry, and the retry becomes a duplicate side effect. A performance problem quietly becomes a correctness problem.

Topological Order Explains Progress, Not Recovery

A valid ordering answers one question: what may happen next?

It does not answer another question that distributed systems must confront constantly: what should happen if the next step fails after several earlier steps have already succeeded?

Suppose an order service reserves a product, the payment service authorizes a charge, and the shipping service then rejects the request because the address is invalid. There is no single database transaction spanning all three services. The inventory database cannot simply roll back the payment database. The system has crossed several boundaries, and each boundary has committed independently.

This is where a Saga becomes more than a transaction implementation technique. It is a way of attaching compensation behavior to a dependency graph.

The forward path might be:

Reserve inventory → Authorize payment → Create shipment

The recovery path might be:

Cancel shipment request
Refund payment
Release inventory

These are not traditional rollbacks. A database rollback erases an uncommitted change as though it never occurred. A compensation is a new business operation that attempts to restore an acceptable state. A refund does not make a payment authorization never have happened. It creates a later financial event that offsets it.

This difference changes how engineers should reason about failure. The question is not merely, “Can this operation be undone?” It is:

If this operation succeeds, what obligations does the system acquire, and how can those obligations be discharged if a later dependency fails?

That question turns every node in the graph into more than an action. Each node has at least four properties:

  • Its prerequisites.
  • Its local effect.
  • The events or state changes it exposes to other services.
  • Its compensation or failure policy.

A topological ordering gives the system a legal forward path. Compensation policies give it a legal retreat.

Forward execution asks whether progress is permitted. Recovery asks what progress has already made the system responsible for.

This is why the two concepts fit together so well. Topological sorting describes the geometry of progress. Saga design describes the geometry of responsibility.

The Real Shape of a Saga Is Not a Line

The word “Saga” can tempt teams into imagining a simple chain of steps with a reverse chain of undo actions. Real workflows are rarely so neat.

Suppose an online retailer wants to process an order. After validation, it can reserve inventory and authorize payment in parallel. Once both succeed, it can create a shipment. If shipment creation fails, the system must release inventory and refund payment. But what if the refund itself fails? What if inventory release succeeds for one item but fails for another? What if a notification is sent before the failure is discovered?

The forward process may look like this:

                 Reserve item A
                /
Validate order  
                \
                 Authorize payment
                         |
                  Create shipment
                         |
                 Send confirmation

The recovery process is not simply the exact reverse of the drawing. It depends on which branches completed, which effects are reversible, and which outcomes are permanent. If confirmation was sent, the system may need to send a correction rather than pretend the message never existed. If a payment refund is delayed, the order may enter a state such as “refund pending,” not return cleanly to “new.”

This suggests a practical mental model: a Saga is a graph with state transitions, not a stack with an undo button.

For each operation, define a small state machine. An inventory reservation might move through states such as:

Available → Reserved → Released

A payment might follow:

Unrequested → Authorized → Captured → Refunded

These states reveal a critical fact: compensation is constrained by timing. An authorization may be voided, while a captured payment may require a refund. A shipment request may be canceled before dispatch, but a package already handed to a carrier may require a return process. The appropriate recovery action depends on the state reached, not merely on the name of the original action.

This is also why idempotency is essential. Messages can be delivered more than once, workers can crash after completing an operation but before recording its result, and retries can arrive after a timeout. A compensation such as “release inventory” must be safe to repeat. Otherwise, the recovery mechanism becomes a new source of corruption.

A robust Saga therefore needs more than a sequence of messages. It needs explicit answers to questions such as:

  • What state proves that a local transaction succeeded?
  • What happens if the response is lost after success?
  • Can the operation be retried safely?
  • Is the compensation exact, approximate, or impossible?
  • What state represents a failed compensation?
  • Who is responsible for deciding whether to retry, compensate, or escalate?

These questions are the operational equivalent of checking whether a graph is well formed before attempting a topological sort. If the dependencies are ambiguous, the order is unreliable. If the recovery semantics are ambiguous, failure is unreliable.

Orchestration and Choreography Are Different Ways to Read the Graph

There are two common ways to coordinate a Saga, and they can be understood as two different ways of representing the same dependency structure.

In orchestration, a central coordinator explicitly directs each service. It knows that inventory must be reserved, payment must be authorized, and shipment may be created only after both succeed. When something fails, the coordinator issues the necessary compensating commands.

This resembles an algorithm performing a topological traversal. The coordinator maintains knowledge of which prerequisites are complete, selects the next available nodes, records outcomes, and determines the recovery path. The main advantage is visibility. The workflow is easier to inspect, test, and reason about as a whole.

The danger is that the orchestrator can become a hidden monolith. If every business rule is placed inside one coordinator, services may lose autonomy and the coordinator may become a bottleneck for change. It can also create a single conceptual authority that is difficult to scale across many workflows.

In choreography, services react to events and publish new events after completing local transactions. Inventory publishes “stock reserved.” Payment reacts, authorizes the charge, and publishes “payment authorized.” Shipment reacts when its prerequisites are satisfied.

This resembles a distributed graph in which nodes discover progress through events rather than receiving instructions from one central controller. It can preserve service autonomy and reduce direct coupling. But the graph becomes harder to see. A dependency that is obvious in a coordinator may be scattered across event handlers, queues, and implicit assumptions.

Choreography has a particularly subtle risk: the architecture can become topologically valid but cognitively invisible. The system may execute correctly while no single person can explain the complete workflow, identify all compensation paths, or predict the effect of adding one new event listener.

A useful design test is therefore not “Which pattern is more modern?” but:

Where should the dependency graph be made explicit so that humans can operate it under failure?

Use orchestration when the workflow has strict ordering, many compensation paths, or high business visibility. Use choreography when services need independence and the event relationships remain understandable. In either case, document the graph. Distributed does not have to mean mysterious.

Designing for Partial Progress

The most important shift is to stop treating partial completion as an exceptional accident. In a distributed system, partial progress is the normal condition. One service may have committed while another is unavailable. One event may have been processed while its acknowledgment was lost. A compensation may be waiting in a queue while the customer is already asking for an explanation.

This means the system should expose meaningful intermediate states rather than forcing every request into a simplistic success or failure result. An order might be “awaiting payment,” “payment authorized,” “fulfillment pending,” “canceling,” or “refund pending.” These states are not implementation noise. They are honest descriptions of a process that cannot become atomic across all boundaries.

A mature workflow also separates three different concerns:

  1. Eligibility: Are the prerequisites satisfied?
  2. Execution: Did the local transaction complete?
  3. Convergence: After success or failure, is the overall business state moving toward an acceptable outcome?

Topological ordering primarily addresses eligibility. Local transactions address execution. Saga coordination and compensation address convergence.

This three layer model helps explain why a technically successful service call may still produce a failed business process. Payment authorization can succeed, yet the order can remain unacceptable if inventory disappears later. Conversely, an operation can fail temporarily while the overall Saga remains healthy because a retry or compensation is progressing.

Teams should measure the workflow accordingly. Useful operational signals include:

  • The number of Sagas stalled at each state.
  • The age of pending compensations.
  • The rate of duplicate messages and idempotent replays.
  • The time between a forward failure and a stable business outcome.
  • The number of workflows requiring manual intervention.

These metrics reveal something ordinary request latency cannot: whether the system can finish what it started.

Key Takeaways

  • Draw dependencies before drawing sequences. Identify which operations genuinely require others and which can proceed independently.
  • Attach recovery semantics to every side effect. For each step, define what happens if a later step fails, including the possibility that compensation also fails.
  • Model states, not just commands. “Reserve inventory” is an instruction. “Reserved,” “released,” and “release pending” are states that support reliable recovery.
  • Make retries safe. Local transactions and compensations should be idempotent, because timeouts and duplicate messages are normal conditions.
  • Choose coordination based on graph visibility. Central orchestration improves explicit control. Choreography improves autonomy. The better choice is the one whose dependencies and failure paths people can still understand.

A distributed transaction is often described as a problem of consistency. That is true, but incomplete. It is also a problem of ordering under uncertainty and responsibility after partial success.

The graph gives you the legal paths forward. The Saga gives you a way to live with the paths that break. Together, they offer a more realistic definition of reliability: not that every operation succeeds, but that every partial outcome has a known place in the system’s state space.

The deepest design question is therefore not, “How do we make this transaction atomic?” Across independent services, that may be impossible. Ask instead: Can we make the dependencies visible, the intermediate states honest, and the recovery paths deliberate?

When the answer is yes, failure stops being a hole in the architecture. It becomes another path through the graph, one the system was designed to take.

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣