Documenting User Flows in Your Software Architecture - Archyl Blog

Our architecture diagrams looked perfect. Then a bug report came in, and we realized nobody actually understood how a user moved through the system. Here's how we fixed that.

Documenting User Flows in Your Software Architecture

The bug report seemed simple: "Checkout fails intermittently."

We had beautiful architecture diagrams. C4 diagrams showing every service, every database, every integration. But when we tried to trace a user's journey from "add to cart" to "order confirmed," nobody could agree on the exact sequence. Which services were involved? In what order? Where did the inventory check happen — before or after payment?

Three engineers had three different mental models. Our static architecture diagrams showed the structure, but not the behavior. We could see the boxes and arrows, but we couldn't see how a user actually moved through the system.

That's when I realized we needed user flows.

What User Flows Actually Are

A user flow documents the journey a user takes through your system to accomplish a goal. Not the static structure (that's what C4 diagrams are for), but the dynamic sequence:

  1. User does X
  2. System responds with Y
  3. User chooses Z
  4. And so on...

It sounds obvious, but it's surprisingly rare to have these documented well. Most teams have architecture diagrams (system structure) and API documentation (endpoint details), but nothing in between that shows how these pieces work together from the user's perspective.

The Gap Between Structure and Behavior

Here's a simplified version of our C4 container diagram at the time:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Web App   │────▶│     API     │────▶│  Database   │
└─────────────┘     └──────┬──────┘     └─────────────┘
                           │
                    ┌──────▼──────┐
                    │   Payment   │
                    │   Service   │
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │   Stripe    │
                    └─────────────┘

This tells you the components exist and how they connect. But it doesn't answer: When a user checks out, what happens first? Does the API validate inventory before calling payment? What happens if payment succeeds but inventory is now unavailable?

These questions require understanding the flow, not just the structure.

Anatomy of a Useful User Flow

Over time, I've developed a template that works well for documenting flows:

1. Goal Statement

Start with what the user is trying to accomplish:

Goal: Complete a purchase of items in shopping cart

This keeps the flow focused. If you find yourself documenting multiple goals, split into multiple flows.

2. Preconditions

What must be true before this flow begins?

  • User is logged in
  • Cart contains at least one item
  • Items in cart are in stock (as of page load)

This prevents confusion about the starting state and clarifies assumptions.

3. The Happy Path

Document the expected successful path first:

1. User clicks "Checkout" button
   → Web App displays shipping address form

2. User enters shipping address, clicks "Continue"
   → API validates address format
   → API calculates shipping options
   → Web App displays shipping method selection

3. User selects shipping method, clicks "Continue"
   → API validates shipping method
   → Web App displays payment form

4. User enters payment details, clicks "Place Order"
   → API creates pending order in database
   → API calls Payment Service with order details
   → Payment Service calls Stripe to charge card
   → Payment Service returns success to API
   → API marks order as confirmed
   → API triggers confirmation email
   → Web App displays order confirmation page

5. User sees confirmation with order number

Notice a few things:

  • Each step includes what the user does AND how the system responds
  • We specify which components handle each action
  • The level of detail is enough to understand the flow without reading code

4. Error Paths and Edge Cases

The happy path is just the beginning. Real value comes from documenting what happens when things go wrong:

Payment Declined:

At step 4, if Stripe returns payment_declined:
→ Payment Service returns error to API
→ API does NOT mark order as confirmed
→ API deletes pending order
→ Web App displays "Payment declined" message
→ User can retry with different payment method

Inventory Changed:

At step 4, before creating pending order:
→ API rechecks inventory availability
→ If item now unavailable:
  → API returns inventory_error
  → Web App displays "Item no longer available" with link to cart
  → User must remove item and restart checkout

Network Timeout:

At step 4, if Payment Service doesn't respond in 30 seconds:
→ API does NOT retry (to prevent double charges)
→ API returns timeout_error
→ Web App displays "Please check your email to confirm order status"
→ Background job checks payment status after 5 minutes
→ If payment succeeded: mark order confirmed, send email
→ If payment failed: clean up pending order

These error paths are where bugs hide. They're also where most documentation stops, which is why bugs hide there.

5. Technical Notes

Add any technical context that's relevant:

Technical Notes:
- Inventory check and payment are NOT atomic. Race condition possible
  but acceptable given low probability and manual resolution process.
- Stripe webhook provides backup confirmation if API response is lost.
- Pending orders older than 1 hour are cleaned up by scheduled job.

Connecting Flows to Architecture

Here's where it gets powerful: linking user flows to your C4 diagrams.

In Archyl, we can annotate which components are involved in each step of a flow. This creates bidirectional traceability:

  • Looking at a flow? See which components it touches.
  • Looking at a component? See which flows pass through it.

When that checkout bug came in, we could have:

  1. Pulled up the checkout flow
  2. Seen exactly which services were involved
  3. Traced to the specific step that was failing
  4. Understood the error handling at that point

Instead, we spent two days rediscovering how our own system worked.

Different Types of Flows

Not all flows are the same. We categorize them:

User Flows

User-initiated sequences from the UI:

  • Checkout flow
  • Registration flow
  • Password reset flow
  • Profile update flow

These start with a user action and end with a visible result.

System Flows

Machine-initiated sequences:

  • Nightly reconciliation job
  • Webhook processing
  • Event-driven cascades
  • Scheduled notifications

These are equally important but often less documented because there's no UI to see.

Integration Flows

Cross-system sequences:

  • Third-party order import
  • Payment settlement batches
  • Data synchronization with partners

These typically involve systems outside your control and have more potential failure modes.

How We Document Flows Now

After that checkout debugging fiasco, we established a process:

Every Major Feature Gets a Flow

When we build a new feature, we document the flow before writing code. This serves as a design document and persists as documentation.

The format is simple markdown:

# Feature: User Reviews

## Goal

User submits a review for a purchased product

## Preconditions

- User is logged in
- User has purchased the product
- Product allows reviews

## Flow

1. User clicks "Write Review" on product page
   ...

## Error Handling

...

## Components Involved

- Web App
- API Gateway
- Review Service
- Product Service
- Notification Service

## Related ADRs

- ADR-0023: Review moderation strategy

Flow Review in PRs

If a PR changes a documented flow, we require the flow documentation to be updated. PR templates include: "Does this change any user flows? If yes, update the flow docs."

Quarterly Flow Audit

Every quarter, we walk through critical flows as a team:

  1. Pull up the flow documentation
  2. Trace through the actual code/system
  3. Identify discrepancies
  4. Update documentation or fix bugs

This sounds like overhead, but it's caught real issues. Twice we discovered that code changes had broken documented behavior without anyone realizing.

Common Mistakes

Mistake 1: Too Much Detail

A flow document shouldn't read like code. If you're documenting individual database queries, you're at the wrong level. Focus on user-visible steps and service-level interactions.

Mistake 2: Only Documenting Happy Paths

The happy path is easy. The error paths are where bugs live. Force yourself to answer: "What happens if this step fails?"

Mistake 3: Not Updating After Changes

A documented flow that doesn't match reality is worse than no documentation. It creates false confidence and leads to wrong assumptions during debugging.

Mistake 4: Treating Flows as Static

User flows evolve. New features add steps. Optimizations remove steps. A/B tests change behavior. Treat flows as living documents, not one-time artifacts.

Tools and Formats

You don't need special tools for flow documentation. Markdown in your repository works fine. What matters is consistency and discoverability.

That said, visual flow diagrams can help for complex sequences. Tools like Mermaid let you write diagrams as code:

sequenceDiagram
    participant U as User
    participant W as Web App
    participant A as API
    participant P as Payment Service
    participant S as Stripe

    U->>W: Click "Place Order"
    W->>A: POST /orders
    A->>A: Create pending order
    A->>P: Process payment
    P->>S: Charge card
    S-->>P: Success
    P-->>A: Payment confirmed
    A->>A: Mark order confirmed
    A-->>W: Order details
    W-->>U: Confirmation page

The key is version control. Flow diagrams in Figma or Lucidchart tend to drift from reality because they're disconnected from the development process.

Conclusion

Static architecture diagrams tell you what your system is made of. User flows tell you how it behaves. You need both.

The next time you're debugging a complex issue across multiple services, ask yourself: "Do we have a documented flow for this?" If the answer is no, you'll spend hours reconstructing how the pieces fit together — hours that could have been saved with a simple document.

Start with your most critical flow. For most systems, that's probably authentication or the primary conversion path (checkout, signup, core action). Document the happy path, then force yourself to document at least three error cases.

Your future self — frantically debugging at 2 AM — will thank you.


More on architecture documentation: Introduction to the C4 Model | Architecture Decision Records | Why Documentation Matters