Using UML Sequence Diagrams to Verify Inter‑Service Calls in a Distributed Microservice System
Discover how to model and validate OrderService → PaymentService interactions with UML sequence diagrams, check real call order with logs, and understand when async messaging can break the model.
19 Mar 2026, 08:21 UTC

Problem: How can we be sure that the OrderService actually talks to the PaymentService the way we think it does?
In a microservice architecture, one service often needs to invoke another to complete a business transaction. Even if both teams write unit tests, the overall call order, guard conditions, and error paths are hard to see at a glance. A common solution is to create a UML sequence diagram that depicts the expected interaction. But will the diagram stay accurate? What if the services communicate asynchronously or the diagram becomes too large to read?
Thesis: Sequence diagrams are a lightweight, visual contract for inter‑service communication that can be verified against runtime traces, yet they need careful scoping and an awareness of asynchronous patterns.
1. Build the Diagram
We’ll model a typical scenario: OrderService receives a new order, writes it to OrderDB, then calls PaymentService to charge the customer. The payment call is synchronous; the response includes a paymentId that OrderService stores.
Using PlantUML, the diagram looks like this:
@startuml
actor User
participant "OrderService" as OS
participant "OrderDB" as DB
participant "PaymentService" as PS
participant "PaymentGateway" as PG
User -> OS: POST /orders
OS -> DB: INSERT order
DB --> OS: orderId
OS -> PS: POST /payments
PS -> PG: HTTP POST /charge
PG --> PS: 200 OK
PS --> OS: paymentId
OS -> DB: UPDATE order with paymentId
@enduml
Notice the lifelines (OS, DB, PS, PG) and the synchronous messages (solid arrows). Guard conditions (e.g., if paymentFailed) can be added with alt fragments, but for clarity we keep the base flow first.
2. Verify Against Runtime Traces
Once the diagram is drafted, we need to confirm that the real system follows it. One practical method is to use Spring Boot Actuator’s trace endpoint (or any distributed tracing system like OpenTelemetry). The steps are:
- Enable tracing in both services: add
spring-boot-starter-actuatorandspring-cloud-starter-sleuthto the Maven/Gradle build. - Run the services locally or in a test environment.
- Trigger the order flow via a POST request:
curl -X POST http://localhost:8080/orders -d '{"productId":123,"quantity":2}'. - Retrieve the trace from the actuator endpoint:
curl http://localhost:8080/actuator/trace. The JSON will include a list of spans with parent/child relationships. - **Map the trace to the diagram**: each span should correspond to a message arrow in the diagram. Verify that the order of spans matches the sequence of arrows.
- **Check guard conditions**: simulate a payment failure by mocking the PaymentGateway to return 400, then confirm the trace includes an error span and that OrderService handles it as per the diagram’s
altfragment.
Example check: if the trace shows OS -> PS occurring after OS -> DB but before PS -> PG, the diagram is accurate. If the order diverges, update the diagram or investigate why the implementation deviates.
3. When Sequence Diagrams Break
Sequence diagrams are built on a synchronous, request/response model. In real distributed systems, you often use asynchronous messaging (Kafka, RabbitMQ) or event‑driven patterns. The diagram then requires alt fragments with asynchronous arrows (dashed lines) and may need additional lifelines for the message broker. Failure to represent this accurately can mislead stakeholders.
Large interactions can also cause diagram bloat. A single order flow might involve dozens of microservices; showing all in one diagram makes it unreadable. A common mitigation is to split the diagram into sub‑diagrams and use UML packages to group related services.
4. Trade‑off: Readability vs. Completeness
Adding every guard, loop, and asynchronous arrow increases fidelity but reduces clarity. A pragmatic approach is:
- Show the main flow in the primary diagram.
- Add a secondary diagram or a note for complex conditional paths.
- Use stereotypes (
<<send>>,<<create>>) to link diagram elements to code artifacts, aiding traceability.
Remember that the diagram is a contract, not a source of truth. Keep it in sync with code by integrating a review step: every time a new service call is added, the diagram must be updated and a trace check run.
Actionable Closing
1. Draft a sequence diagram for the core inter‑service call you need to verify. 2. Add guard fragments for error handling and loops if they exist. 3. Enable distributed tracing in your services and capture a trace after a test run. 4. Map the trace spans back to the diagram; adjust either the diagram or code if mismatches appear. 5. Store the diagram in a version‑controlled repository and enforce a review process whenever new calls are added.
By treating the sequence diagram as a living artifact that is continuously validated against real traces, you gain a clear, shared understanding of your distributed system’s behavior while avoiding the pitfalls of stale or overly complex diagrams.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.