PlantUML project case study: a microservice from 0 to 1
The ultimate exercise of PlantUML — a complete e-commerce order microservice, end to end, from requirements to production, with the full diagram suite.
Project background We’re designing an e-commerce order microservice :
Order lifecycle: pending → paid → shipped → delivered → completed / refunded
Microservices: order / payment / inventory / user / recommendation
Data: PostgreSQL + Redis + Kafka
Deployment: Kubernetes
The 8 diagrams below cover each stage.
Diagram 1: Use case — business scope 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @startuml left to right direction skinparam actorStyle awesome title Order system - business use cases actor "Visitor" as Guest actor "Member" as Member actor "Admin" as Admin Member <<Human>> rectangle "Order system" { usecase "Browse items" as UC1 usecase "Search" as UC2 usecase "Add to cart" as UC3 usecase "Place order" as UC4 usecase "Pay" as UC5 usecase "View orders" as UC6 usecase "Refund" as UC7 usecase "Manage products" as UC8 usecase "Reports" as UC9 } Guest --> UC1 Guest --> UC2 Member --> UC1 Member --> UC3 Member --> UC4 Member --> UC5 Member --> UC6 Member --> UC7 Admin --> UC8 Admin --> UC9 UC4 ..> UC5 : <<include>> UC4 ..> UC3 : <<include>> UC7 ..> UC5 : <<extend>> UC5 ..> "Auth" : <<include>> @enduml
Reviewer : PM checks business coverage.
Diagram 2: C4 Context — system & external deps 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @startuml !include /plantuml/c4/C4_Context.puml LAYOUT_TOP_DOWN() Person(buyer, "Buyer", "Web / Mobile") Person(admin, "Operations", "admin portal") System(orderSys, "Order system", "Order lifecycle") System_Ext(paySvc, "Payment gateway", "Alipay / WeChat") System_Ext(logistics, "Logistics system", "warehouse / delivery") System_Ext(cdm, "CDN", "static assets") System_Ext(sso, "SSO", "single sign-on") Rel(buyer, orderSys, "orders", "HTTPS") Rel(admin, orderSys, "manages", "HTTPS") Rel(buyer, cdm, "loads assets") Rel(orderSys, paySvc, "charges") Rel(orderSys, logistics, "fulfills") Rel(orderSys, sso, "token check") @enduml
Reviewer : architects confirm boundary + external deps.
Diagram 3: Component — internal modules 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @startuml title Order service - component view package "frontend" { [Web SPA] [Mobile App] } package "edge" { [API Gateway] } package "order-svc" { [OrderController] [OrderDomain] [OrderEventPublisher] [OrderRepo] } package "data" { database "Order DB" as ODB database "Cache" as Cache queue "Kafka" as K } [Web SPA] --> [API Gateway] [Mobile App] --> [API Gateway] [API Gateway] --> [OrderController] [OrderController] --> [OrderDomain] [OrderDomain] --> [OrderRepo] [OrderRepo] --> ODB [OrderRepo] --> Cache [OrderDomain] --> [OrderEventPublisher] [OrderEventPublisher] --> K @enduml
Reviewer : tech lead checks service boundaries.
Diagram 4: Class — domain model 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @startuml skinparam classAttributeIconSize 0 class Order { -id: String <<UK>> -customerId: Long -status: OrderStatus -items: List<OrderItem> -total: Money +place() +pay() +ship() +complete() +refund() } class OrderItem { -productId: Long -quantity: int -unitPrice: Money } class Money { -amount: BigDecimal -currency: String } class Customer { -id: Long -name: String -email: String } class Product { -id: Long -sku: String -name: String -price: Money } enum OrderStatus { PENDING PAID SHIPPED DELIVERED CANCELLED REFUNDED } abstract class AggregateRoot { +id: Long {abstract} +validate() } interface Auditable { +createdAt: Instant +updatedAt: Instant } AggregateRoot <|-- Order Auditable ..|> Order Auditable ..|> Customer Order "1" *-- "1..*" OrderItem Order "1" --> "1" Customer OrderItem "1" --> "1" Product Order "1" --> "1" OrderStatus OrderItem "1" --> "1" Money @enduml
Reviewer : architects confirm aggregate roots, associations, enums.
Diagram 5: State — order lifecycle 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @startuml title Order state machine [*] --> Pending : create Pending --> Paid : payment success / reserve stock Pending --> Cancelled : user cancel Pending --> TimedOut : 24h [cron] Paid --> AwaitingShipment : system confirm Paid --> Refunded : refund [approved] AwaitingShipment --> Shipped : warehouse pick / queue AwaitingShipment --> Refunded : refund [approved] Shipped --> Delivered : user receives / award points Shipped --> AfterSale : open after-sale Delivered --> Completed : 7 days [auto] Delivered --> AfterSale : open after-sale AfterSale --> AfterSaleDone : refund-only / exchange-only AfterSale --> Refunded : approve refund Refunded --> [*] Cancelled --> [*] TimedOut --> [*] Completed --> [*] AfterSaleDone --> [*] note right of Delivered : 7-day auto-close timer note left of AfterSale : enters refund flow here @enduml
Reviewer : business verifies each state.
Diagram 6: Sequence — full place-order flow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @startuml title Place order - full sequence actor Buyer participant "Web" as FE participant "API gateway" as GW participant "Order svc" as Os participant "Inventory" as Is participant "Payment" as Ps participant "Kafka" as K participant "Notification" as Ns Buyer -> FE: click 'Place order' FE -> GW: POST /orders GW -> Os: createOrder(payload) Os -> Is: lockStock(items) alt in stock Is --> Os: ok Os -> Os: compute total Os -> GW: 200 + orderId GW --> FE: 200 + orderId FE --> Buyer: redirect to pay Os -> Ps: pay(orderId) Ps -> Ps: call payment gateway alt paid Ps --> Os: ok Os --> K: publish OrderPaid K --> Ns: consume OrderPaid Ns --> Buyer: email notify else failed Ps --> Os: fail Os -> Is: unlockStock Os --> K: publish OrderFailed end else out of stock Is --> Os: error Os --> GW: 409 + 'Out of stock' GW --> FE: 409 + 'Out of stock' FE --> Buyer: prompt end @enduml
Reviewer : tech lead reads cross-service calls.
Diagram 7: Deployment — K8s topology 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @startuml title Order system - production deployment node "Client" { [Web SPA] [Mobile App] } cloud "AWS Cloud" { node "Region us-east-1" { node "ALB" { [API Gateway] } node "EKS Cluster" { node "namespace: order" { node "Deployment" { node "Pod 1" { [Order Service] } node "Pod 2" { [Order Service] } node "Pod 3" { [Order Service] } } node "Deployment" { node "Pod 1" { [Payment Service] } } node "Deployment" { node "Pod 1" { [Inventory Service] } } } node "MSK" { queue "Kafka" as K } } node "RDS" { database "PostgreSQL Multi-AZ" as PDB } node "ElastiCache" { database "Redis Cluster" as Cache } } } [Web SPA] -[#2F4858]-> [API Gateway] : HTTPS [Mobile App] -[#A31F34]-> [API Gateway] : HTTPS [API Gateway] -[#2F4858]-> [Order Service] : HTTP [Order Service] -[#2F4858]-> [Payment Service] : HTTP [Order Service] -[#2F4858]-> [Inventory Service] : HTTP [Order Service] -[#2F4858]-> PDB : SQL [Order Service] -[#2F4858]-> Cache : Cache [Order Service] -[#20A464]-> K : publish @enduml
Reviewer : SRE verifies HA / replica / boundary.
Diagram 8: PR description with sequence 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ## PR: order refund flow ### What changed Adds explicit refund state. After refund approval, order transitions from AfterSale to Refunded. ### Before Order refunds were silently set to Cancelled, losing the explicit after-sale flow. ### After  Author: explicit state machine for order refund. Reviewer: read the state diagram + sequence to confirm against business req.
Diagram 9: CI auto-render 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 name: PlantUML render on: push: paths: ['docs/**/*.puml' ] branches: [main ] pull_request: paths: ['docs/**/*.puml' ] jobs: render: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: { java-version: '21' , distribution: temurin } - run: | sudo apt-get update && sudo apt-get install -y fonts-noto-cjk curl -L -o plantuml.jar \ https://github.com/plantuml/plantuml/releases/latest/download/plantuml.jar find docs -name '*.puml' | while read f; do java -jar plantuml.jar -tsvg -failfast2 -nometadata "$f" done - name: Verify SVGs up-to-date run: | git diff --quiet docs/ || ( echo "::warning::Some SVGs out of sync with their .puml sources" git diff --name-only docs/ | head exit 1 ) - name: Auto-commit regenerated SVGs if: failure() run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com git add docs/ git commit -m "render: regenerate SVGs [skip ci]" git push
8 diagrams + 1 CI = complete project artifact:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 docs/ architecture/ c4-context.puml → public/architecture/c4-context.svg components/ order-svc.puml → public/components/order-svc.svg sequence/ place-order.puml → public/sequence/place-order.svg refund-after.puml → public/sequence/refund-after.svg state/ order-lifecycle.puml → public/state/order-lifecycle.svg deployment/ production.puml → public/deployment/production.svg class/ domain.puml → public/class/domain.svg usecase/ business.puml → public/usecase/business.svg
Review checklist Each diagram has an owner
Diagram
Reviewer
Looks at
Use case
PM / business
Coverage
C4 Context
Architect
Boundaries
Component
Tech lead
Module splits
Class
Architect
Domain model
State
Business + dev
Business rules
Sequence
Devs
Call relations
Deployment
SRE
HA / replicas
PR descriptions
Reviewer
Change reasoning
When to use which in a PR
New feature → use case + sequence + class update
Bug fix → state (where the bug is) + sequence (the fix path)
Infra change → deployment update
Architectural refactor → redo C4 + components
Why this case is useful Drop this eight-diagram set on a new hire and they grok the entire project in 30 minutes. Beats a 100-page wiki.
Anti-patterns 1. One monolithic diagram 1 2 3 @startuml note: from business to deployment, everything in one SVG @enduml
A single diagram supporting 8 viewpoints renders slow, reviews slow.
2. Sequence with only the happy path 1 2 3 4 @startuml Buyer -> Service: call Service --> Buyer: OK @enduml
Without failure paths debugging lives in prose.
3. Deployment diagram never updated Deployment changes should update the diagram. CI auto-commit keeps it fresh.
TL;DR PlantUML’s full practice isn’t “one diagram” — it’s “multiple diagrams segmented by stage, reader, and review point.” This article’s 8 diagrams are a complete template, drop-in for any microservice project.