PlantUML project case study: a microservice from 0 to 1

puml.online

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

![refund flow](docs/sequence/refund-after.puml)

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
# .github/workflows/plantuml-render.yml
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.

  • Title: PlantUML project case study: a microservice from 0 to 1
  • Author: puml.online
  • Created at : 2026-07-29 16:25:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-real-project-case-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.