Bạn còn nhớ lần gần nhất mở file architecture.png trong thư mục docs/ không? Nếu team bạn giống phần lớn team mình từng làm thì file đó được vẽ bằng draw.io từ hai năm trước. Người vẽ đã nghỉ việc, file .drawio gốc cũng không ai tìm thấy, còn trong hình vẫn có một service đã bị xoá từ ba sprint trước. Tuần này trên Hacker News có hai dự án về vẽ diagram lên top là Reladraw (một ngôn ngữ diagram cho phép bạn tự quyết định vị trí các node) và Drawgent (coding agent vẽ trực tiếp trên canvas Excalidraw). Điều đó cho thấy dev vẫn rất cần diagram, chỉ là cách chúng ta đang làm thì chưa ổn. Bài này chia sẻ cách mình áp dụng diagrams as code để diagram được sống cùng code, được review qua PR và không bị lỗi thời.
Vì sao diagram dạng ảnh luôn chết sớm
Vấn đề không nằm ở công cụ vẽ mà nằm ở workflow. Một diagram ở dạng ảnh PNG có mấy điểm yếu chết người:
- Không diff được: PR đổi kiến trúc nhưng reviewer không thấy diagram thay đổi gì.
- Không có source of truth: file gốc nằm trên máy ai đó hoặc một link Google Drive đã hết quyền truy cập.
- Tốn công sửa: chỉ thêm một service thôi cũng phải mở tool, kéo thả, export, commit. Làm vậy không ai muốn sửa.
Diagrams as code giải quyết vấn đề bằng cách coi diagram là text. Text thì Git hiểu, reviewer đọc được, CI kiểm tra được, và bây giờ thì LLM cũng sinh ra được. Một điểm quan trọng khác là GitHub, GitLab, Notion, Obsidian đều render Mermaid native, bạn không cần build gì thêm.
Các lựa chọn phổ biến hiện nay:
| Tool | Điểm mạnh | Khi nào dùng |
|---|---|---|
| Mermaid | Render native trên GitHub/GitLab | Flowchart, sequence, ERD trong README |
| PlantUML | UML đầy đủ, rất mature | Team enterprise, cần UML chuẩn |
| D2 | Layout đẹp, hỗ trợ nhiều engine | Diagram kiến trúc lớn |
| Structurizr DSL | Theo mô hình C4 | Document kiến trúc nhiều level |
Mình mặc định chọn Mermaid vì chi phí áp dụng gần như bằng 0.
Workflow: diagram đi cùng PR
Đây là luồng mình đang dùng cho một dự án khoảng 12 service:
flowchart LR
A[Dev sửa code] --> B[Sửa file .mmd]
B --> C[git commit]
C --> D{CI: mmdc validate}
D -->|Lỗi syntax| E[PR fail]
D -->|OK| F[Render SVG]
F --> G[Reviewer xem diff]
G --> H[Merge vào main]
Quy tắc chỉ có một: PR nào đổi kiến trúc thì phải đổi diagram trong cùng PR đó. Mình ghi rõ quy tắc này vào PR template bằng một checkbox, reviewer thấy thiếu là request changes.
Để render ở local, bạn cài mermaid-cli (bản 11.x yêu cầu Node 18 trở lên):
# Cài mermaid-cli
npm install -g @mermaid-js/mermaid-cli@11
# Render một file sang SVG
mmdc -i docs/diagrams/architecture.mmd -o docs/diagrams/architecture.svg
# Render toàn bộ thư mục, theme dark cho docs site
for f in docs/diagrams/*.mmd; do
mmdc -i "$f" -o "${f%.mmd}.svg" -t dark -b transparent
done
# Render các block mermaid bên trong file markdown
mmdc -i README.md -o README.rendered.md
Lệnh cuối rất hữu ích khi bạn cần xuất docs sang nơi không render được Mermaid, ví dụ Confluence bản cũ hoặc file PDF. Lúc đó mmdc sẽ thay từng block mermaid bằng link tới ảnh SVG mà nó vừa sinh ra.
Sinh diagram tự động từ code thật
Cách để diagram không bao giờ lỗi thời là đừng viết tay nó. Có rất nhiều thông tin kiến trúc vốn đã nằm sẵn trong repo, chẳng hạn docker-compose.yml, Terraform, migration của database. Đoạn Python dưới đây đọc depends_on trong compose file rồi sinh ra Mermaid graph:
# scripts/compose_to_mermaid.py
# pip install pyyaml==6.0.2
import sys
import yaml
def compose_to_mermaid(path: str) -> str:
with open(path) as f:
compose = yaml.safe_load(f)
lines = ["graph TD"]
for name, svc in compose.get("services", {}).items():
image = svc.get("image", "build")
lines.append(f' {name}["{name}<br/><small>{image}</small>"]')
deps = svc.get("depends_on", [])
# depends_on có thể là list hoặc dict (dạng có condition)
if isinstance(deps, dict):
deps = list(deps.keys())
for dep in deps:
lines.append(f" {name} --> {dep}")
return "\n".join(lines)
if __name__ == "__main__":
src = sys.argv[1] if len(sys.argv) > 1 else "docker-compose.yml"
print(compose_to_mermaid(src))
Chạy thử:
python scripts/compose_to_mermaid.py > docs/diagrams/services.mmd
Với một compose file bình thường, kết quả sẽ trông giống thế này:
graph TD
nginx[nginx] --> api[api]
api --> postgres[(postgres:16)]
api --> redis[(redis:7)]
worker[worker] --> redis
worker --> postgres
Bạn có thể áp dụng cùng ý tưởng này cho ERD: đọc schema từ SQLAlchemy hoặc Prisma rồi sinh ra erDiagram. Với Terraform thì đã có sẵn terraform graph, nó xuất ra định dạng DOT và bạn chỉ cần convert sang.
Chặn diagram hỏng ngay trong CI
Có sinh tự động mà không kiểm tra thì sớm muộn cũng lệch. Mình dùng GitHub Actions cho hai việc: validate syntax của mọi file .mmd, và kiểm tra diagram sinh từ compose có khớp với bản đã commit hay không.
# .github/workflows/diagrams.yml
name: diagrams
on:
pull_request:
paths: ["docs/diagrams/**", "docker-compose.yml"]
jobs:
check:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install tools
run: |
npm install -g @mermaid-js/mermaid-cli@11
pip install pyyaml==6.0.2
- name: Validate syntax
run: |
for f in docs/diagrams/*.mmd; do
mmdc -i "$f" -o /tmp/out.svg || exit 1
done
- name: Detect drift
run: |
python scripts/compose_to_mermaid.py > /tmp/services.mmd
diff -u docs/diagrams/services.mmd /tmp/services.mmd \
|| (echo "Diagram lệch với docker-compose.yml, hãy chạy lại script" && exit 1)
Bước Detect drift là quan trọng nhất. Nếu ai đó thêm service vào compose mà quên sinh lại diagram thì PR sẽ fail. Lúc đó diagram trở thành một phần của contract chứ không còn là thứ "có thì tốt".
Một lưu ý nhỏ: trên runner Ubuntu 24.04, Puppeteer bên trong mmdc có thể báo lỗi sandbox. Cách xử lý là tạo file puppeteer.json với nội dung {"args": ["--no-sandbox"]} rồi truyền vào bằng mmdc -p puppeteer.json.
Khi nào nên dùng LLM để vẽ, và khi nào không
Những dự án như Drawgent cho thấy xu hướng để AI vẽ diagram. Kinh nghiệm của mình là LLM viết Mermaid khá tốt khi bạn đưa cho nó context cụ thể, ví dụ paste một đoạn code rồi yêu cầu vẽ sequence diagram cho luồng login. Tuy vậy có ba điểm cần nhớ:
- Luôn review output như review code. LLM rất hay bịa ra một mũi tên giữa hai service thực tế không gọi nhau.
- Ưu tiên sinh từ source thật (như script ở trên) hơn là để LLM đoán. Thứ gì deterministic được thì nên làm deterministic.
- Dùng LLM cho diagram giải thích, chẳng hạn sequence của một flow phức tạp hoặc state machine. Đây là những diagram khó sinh tự động và cũng ít thay đổi.
Điều này khớp với chủ đề đang được bàn nhiều trên Dev.to: khi AI viết cả code lẫn review thì developer thực sự verify cái gì? Với diagram thì câu trả lời rõ ràng. Bạn verify rằng hình vẽ phản ánh đúng hệ thống đang chạy.
Kết luận
Diagram lỗi thời còn tệ hơn không có diagram, vì nó khiến người mới hiểu sai hệ thống ngay từ ngày đầu. Muốn diagram sống được thì nó phải nằm trong cùng workflow với code. Những việc bạn có thể làm ngay trong tuần này:
-
Chuyển diagram quan trọng nhất sang Mermaid và đặt nó trong
docs/diagrams/*.mmdhoặc viết thẳng vào README. GitHub sẽ tự render. - Thêm checkbox "Đã cập nhật diagram (nếu cần)" vào PR template. Việc này tốn 5 phút nhưng thay đổi được thói quen của cả team.
- Viết một script sinh diagram từ source có sẵn như docker-compose, ORM model hoặc Terraform. Chỉ cần làm một cái là đã thấy giá trị.
- Thêm bước drift check vào CI để diagram lệch thì PR fail.
- Dùng LLM để viết nháp các diagram giải thích flow, nhưng phải review kỹ từng mũi tên.
Khi diagram trở thành text, bạn diff được, review được và tự động hoá được nó như mọi thứ khác trong repo. Nhờ vậy lần sau có người mới vào team, bạn chỉ cần gửi link README là đủ.
Top comments (1)
Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support