Sequence diagrams for microservices are usually overcomplicated
Most people draw them wrong because they treat every service call like it needs its own lifeline, which makes the diagram unreadable by the time you've mapped out a simple checkout flow. I spent three weeks reverse-engineering someone's 40-service architecture diagram last year, and the only useful thing on it was a post-it note someone stuck in the corner that said "this doesn't even work anymore." Here's how to actually do it.Microservices Sequence Diagram Example
Start with a concrete scenario instead of trying to diagram everything at once. A customer places an order, which triggers inventory reservation, payment processing, and notification sending. That's four services and three interactions. Anything bigger than that in a single diagram is already too much. I keep my sequence diagrams under 15 interactions and split them up if they grow past that point. The tooling matters less than you'd think. PlantUML in a text file is what I use, and I'd recommend it to anyone who wants version control on their diagrams. You can run it through CI and regenerate on every merge. Lucidchart and Draw.io work fine but they turn into black boxes after a while when you need to batch-update thirty diagrams after a refactoring. Mermaid has gotten decent recently, though, and if your team is already on GitHub, Mermaid in markdown files is probably the path of least resistance. Here's what a real one looks like in PlantUML syntax:
participant Customer
participant "Order Service" as Order
participant "Inventory Service" as Inv
participant "Payment Service" as Pay
participant "Notification Service" as Notif
Customer -> Order: placeOrder(orderDetails)
Order -> Inv: reserveStock(productId, qty)
Inv --> Order: stockReserved
Order -> Pay: charge(customerId, amount)
Pay --> Order: paymentConfirmed
Order -> Notif: sendConfirmation(orderId)
Order --> Customer: orderPlaced The arrow types matter more than people realize. Solid arrows for synchronous calls, dotted for returns, and dashed for asynchronous messages. If you're using event-driven architecture, which most microservices are, you'll want to show those pub/sub interactions explicitly with a separate lifeline for the message broker. I once missed that distinction in a diagram and the team spent two days debugging a race condition that the diagram would have made obvious in five seconds. One thing nobody tells you about these diagrams: the timing axis gets misleading fast. Sequence diagrams imply strict ordering, but in practice your services might be calling each other concurrently through async events, or the calls might be idempotent retries hitting the same endpoint twice. I started adding alt and opt fragments for conditional paths and loop fragments for repeated operations, which keeps the diagram accurate without turning into a decision tree.
Here's a more complete version with error handling: participant Customer
participant "Order Service" as Order
participant "Inventory Service" as Inv
participant "Payment Service" as Pay
participant "Notification Service" as Notif
participant "Message Broker" as Broker
Customer -> Order: placeOrder(orderDetails)
Order -> Inv: reserveStock(productId, qty)
alt stockAvailable
Inv --> Order: stockReserved
else insufficientStock
Inv --> Order: stockUnavailable
Order --> Customer: error(stockUnavailable)
end
Order -> Pay: charge(customerId, amount)
opt paymentSuccess
Pay --> Order: paymentConfirmed
Order -> Broker: publish("order.completed", orderId)
Broker -->> Notif: consume("order.completed")
Notif --> Order: confirmationSent
else paymentFailed
Pay --> Order: paymentDeclined
Order --> Customer: error(paymentDeclined)
end
Order --> Customer: orderPlaced The alt/opt/loop fragments are where the diagram earns its keep. Without them you're just drawing a linear happy path that doesn't match reality. Every production system I've worked on had at least three branches per major flow, and a sequence diagram that omits them is actively misleading.
Get the Full Details

I also stopped trying to show every timeout and retry in the main diagram. That belongs in an appendix or a separate failure-mode diagram. The main sequence should answer "what normally happens" in under two minutes of reading. If someone needs the failure details, they ask. A practical workflow that saved me a lot of headaches: keep the diagram in the same repo as the service code it describes, preferably in a docs/diagrams folder. When someone changes an API contract, the diagram should break the build if it's out of sync. I use a simple Node script that runs plantuml via a Docker container and exits non-zero if the generated SVG doesn't match the checked-in version. Takes about 30 seconds in CI and catches stale diagrams before they become the source of truth. The biggest limitation of sequence diagrams in microservices is that they don't scale horizontally well. A single diagram covering ten services interconnecting becomes illegible, and splitting it across multiple diagrams creates consistency problems where two diagrams disagree on the same interaction. The workaround is to layer them: one high-level diagram showing the top-level flow, then one focused diagram per service showing its internal behavior and external dependencies. Don't try to do both at once.
Also worth noting: sequence diagrams silently encode assumptions about data formats and authentication that aren't visible in the diagram itself. I learned this the hard way when a frontend team integrated against a service based purely on the sequence diagram and assumed REST JSON when the actual implementation was GraphQL with JWT auth. Adding a small text box in the diagram header with protocol, payload format, and auth method took thirty seconds and prevented a week of integration debugging. Download the PlantUML templates I use — they're just a .puml file with default styles set (cyan for external services, light blue for internal, gray for actors) so you don't spend time formatting every new diagram. The file lives at github.com/your-org/arch-diagrams/tree/main/templates if your team sets one up. If not, just copy the syntax above and adjust the participant colors in the @startuml header.