← Lập trình với Claude Code

Bài 17 · Nâng cao · 24 phút· Cập nhật 11/06/2026

Chuẩn nghề: Kiến trúc & mẫu senior

Biên soạn bởi Nguyễn Anh Tuấn

Mã hoá ranh giới kiến trúc (layered/hexagonal/DDD-lite, CQRS) và mẫu chịu lỗi (retry/timeout/circuit breaker, idempotency) vào CLAUDE.md + subagent kiến trúc.

Claude sửa nhanh và đặt code ở chỗ tiện nhất nếu bạn không ràng buộc. Sau vài chục thay đổi, nghiệp vụ dính chặt vào controller, DB, framework - hệ thống cứng & khó đổi. Senior phòng điều đó bằng cách mã hoá ranh giới kiến trúc để mỗi thay đổi của Claude nằm đúng chỗ.

  • Không ràng buộc → nghiệp vụ rò rỉ khắp nơi → mỗi thay đổi sau càng đắt.
  • Mã hoá: luật kiến trúc trong CLAUDE.md + subagent kiểm khi plan/review.
  • Mục tiêu: lõi nghiệp vụ ĐỘC LẬP với DB/web/framework - dễ đổi, dễ test.

Lý thuyết sâu ở khoá riêng

Bài này mã hoá kiến trúc cho Claude Code. Nền tảng đầy đủ (Clean/Onion/Hexagonal, Entities & Use Cases, Dependency Injection) ở khoá Clean & Onion ArchitectureSOLID - học xong, đưa quy ước của bạn vào CLAUDE.md & subagent dưới đây.

Trái tim của mọi kiến trúc sạch là quy tắc phụ thuộc: phụ thuộc hướng VÀO TRONG, lõi nghiệp vụ không biết gì về tầng ngoài. Ghi rõ vào CLAUDE.md:

CLAUDE.md - mục kiến trúc

## Kiến trúc (bắt buộc)
- Tầng: domain (thực thể + quy tắc nghiệp vụ) ← application (use case)
  ← adapters (controller, repository impl) ← infrastructure (DB, web, lib).
- Quy tắc phụ thuộc: phụ thuộc luôn hướng VÀO TRONG.
  domain/ và application/ KHÔNG được import từ infrastructure/ hay framework.
- Controller chỉ điều phối: nhận request → gọi use case → trả kết quả.
  KHÔNG đặt nghiệp vụ trong controller.
- Truy cập DB/dịch vụ ngoài qua INTERFACE (port); cài đặt cụ thể ở adapter.
  • Lõi nghiệp vụ (domain/use case) thuần, không dính DB/web → đổi hạ tầng không phải viết lại.
  • Controller mỏng; nghiệp vụ ở use case; truy cập ngoài qua interface (port).
  • Nâng cao: thêm linter/hook chặn import sai tầng để biến luật thành ràng buộc cứng.

Vài mẫu xuất hiện trong hầu hết dự án chuyên nghiệp - ghi thành quy ước để Claude dùng đúng chỗ:

MẫuGiải quyết
Repository (port-adapter)Giấu chi tiết DB sau interface → đổi DB / test không cần DB thật.
Dependency InjectionTiêm phụ thuộc từ ngoài → lõi nhận abstraction, dễ thay & test.
DTO & mapperTách dữ liệu vào/ra API khỏi thực thể nội bộ → đổi API không lộ nội bộ.
CQRSTách đường ĐỌC khỏi đường GHI khi chúng rất khác nhau (dùng khi cần, không mặc định).

Biết KHÔNG dùng cũng là kỹ năng

Mẫu thiết kế là công cụ, không phải huy chương. Lạm dụng (vd CQRS cho app nhỏ) làm hệ thống phức tạp vô ích. Ghi vào CLAUDE.md cả khi nào KHÔNG cần, để Claude không “vẽ rắn thêm chân”.

Hệ thống thật gọi mạng, DB, dịch vụ ngoài - những thứ sẽ lỗi. Senior viết code chịu lỗi sẵn:

CLAUDE.md - mục chịu lỗi

## Chịu lỗi khi gọi dịch vụ ngoài (bắt buộc)
- TIMEOUT mọi lời gọi mạng/DB - không chờ vô hạn.
- RETRY chỉ cho lỗi TẠM THỜI (timeout, 5xx, mất mạng), KHÔNG retry lỗi 4xx;
  dùng exponential backoff + jitter; giới hạn số lần.
- Thao tác được retry phải IDEMPOTENT (xem bài Bảo mật & dữ liệu).
- CIRCUIT BREAKER cho phụ thuộc hay hỏng: ngừng gọi tạm thời khi lỗi liên tục,
  tránh lỗi dây chuyền; có fallback/ thông báo nhẹ nhàng.
- Việc nền/hàng đợi: idempotent consumer + dead-letter cho bản tin hỏng.

Thử circuit breaker thực tế - bấm các nút và quan sát trạng thái máy:

Nhấn các nút để gửi sự kiện vào circuit breaker. Quan sát trạng thái thay đổi từ CLOSEDOPENHALF-OPEN → CLOSED.

CLOSED - Bình thường

Mạch đóng: mọi lời gọi đi qua bình thường.

Bấm "Gọi lỗi" 3 lần liên tiếp để xem mạch mở...

Ngưỡng mặc định: mở sau 3 lỗi liên tiếp; đóng lại sau 2 lần OK trong half-open.

  • Timeout + retry-có-backoff cho lỗi tạm thời (và phải idempotent).
  • Circuit breaker: như cầu dao - mở khi phụ thuộc liên tục lỗi, tránh sập dây chuyền.
  • Lỗi là chuyện thường: thiết kế để hỏng tới đâu giảm tính năng tới đó (graceful degradation), không sập toàn bộ.

Mạnh nhất là một subagent kiến trúc kiểm ranh giới ngay khi plan mode - chặn vi phạm TRƯỚC khi code, rẻ hơn sửa sau:

.claude/agents/architecture-reviewer.md

---
name: architecture-reviewer
description: Kiểm thiết kế & ranh giới kiến trúc theo CLAUDE.md. Dùng khi lập kế hoạch hoặc review tính năng đụng nhiều tầng.
tools: Read, Grep, Glob
model: sonnet
---

Bạn là kiến trúc sư phần mềm. CHỈ đọc. Kiểm theo luật kiến trúc trong CLAUDE.md:
- Nghiệp vụ có nằm đúng tầng (domain/use case), không rò vào controller/DB?
- Quy tắc phụ thuộc: có import nào từ trong ra ngoài (domain → infra) không?
- Truy cập ngoài có qua interface (port) không?
- Mẫu dùng có HỢP LÝ không, hay phức tạp thừa (vd CQRS cho app nhỏ)?
Đề xuất cách đặt lại cho đúng tầng; nêu file:dòng.
claude - ~/cua-hang-x

Thêm tính năng “huỷ đơn hàng”. Vào plan mode trước.

⤷ giao cho subagent: architecture-reviewer (kiểm ranh giới)

Kế hoạch theo kiến trúc tầng trong CLAUDE.md:

1) Use case CancelOrder ở tầng application (không gọi thẳng DB) 2) Truy cập DB qua OrderRepository (port) - đổi DB không ảnh hưởng nghiệp vụ 3) Controller chỉ nhận request → gọi use case → trả kết quả

Không đặt logic huỷ trong controller. Duyệt kế hoạch để tôi code chứ?

Hết mini-track “Chuẩn nghề” 🎓

Bạn đã mã hoá trọn bộ chuẩn senior thành công cụ cho Claude Code: bảo mật & dữ liệuchất lượng & truy cậpkiến trúc & chịu lỗi. Gói chúng thành một plugin “gác cổng chất lượng” (bài Plugins) để mang sang mọi dự án. Giờ tới capstone: áp tất cả vào 5 dự án thực chiến.

Câu hỏi thường gặp

Vì AI sửa nhanh và đặt logic ở “chỗ tiện nhất” nếu bạn không ràng buộc - sau vài chục thay đổi, nghiệp vụ dính chặt vào controller/DB/framework, hệ thống thành mớ bòng bong. Mã hoá ranh giới kiến trúc vào CLAUDE.md (+ subagent kiểm) giúp mỗi thay đổi của Claude vẫn nằm đúng tầng, giữ hệ thống dễ đổi lâu dài.

Phụ thuộc luôn hướng VÀO TRONG, về phía lõi nghiệp vụ: tầng ngoài (web, DB, UI) biết tầng trong (domain/use case), nhưng lõi nghiệp vụ KHÔNG biết gì về web/DB/framework. Nhờ đó bạn đổi DB hay framework mà không phải viết lại nghiệp vụ. (Sâu hơn ở khoá Clean & Onion Architecture.)

CQRS = tách đường GHI (command, thay đổi trạng thái) khỏi đường ĐỌC (query, chỉ trả dữ liệu) thành hai mô hình riêng. Hữu ích khi đọc & ghi rất khác nhau về quy mô/hình dạng. Nhưng nó thêm phức tạp - đừng dùng mặc định; chỉ khi có lý do rõ ràng. Senior biết khi nào KHÔNG dùng cũng quan trọng như biết dùng.

Như cầu dao điện. Khi một dịch vụ phụ thuộc liên tục lỗi, “cầu dao” MỞ: tạm ngừng gọi nó một thời gian (trả lỗi/nhanh fallback ngay) để khỏi dồn request vào chỗ đang hỏng và sập theo. Sau một lúc nó “half-open” thử lại vài request; ổn thì ĐÓNG lại (bình thường). Tránh “lỗi dây chuyền” (cascading failure).

Chỉ retry lỗi TẠM THỜI (timeout, 5xx, mất mạng) - không retry lỗi 4xx do dữ liệu sai. Dùng exponential backoff + jitter (giãn dần + ngẫu nhiên) để khỏi “bão retry” cùng lúc. Và thao tác được retry phải IDEMPOTENT (bài bảo mật & dữ liệu) - nếu không, retry sẽ nhân đôi tác dụng.

CLAUDE.md là hướng dẫn mạnh nhưng vẫn là “lời khuyên”. Với ràng buộc CỨNG, kết hợp: luật trong CLAUDE.md + một subagent kiến trúc kiểm khi plan/review + (nâng cao) hook/linter chặn import sai tầng. Nhiều lớp thì khó “lọt” hơn.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

“Quy tắc phụ thuộc” (dependency rule) nói gì?

  1. 1

    Ranh giới vào CLAUDE.md

    Thêm vào CLAUDE.md quy tắc phụ thuộc & vị trí từng tầng (domain/use case/adapter/infra) cho dự án bạn. Nhờ Claude thêm một tính năng và xem nó đặt logic đúng tầng chưa.

    Hoàn thành khi: Claude đặt nghiệp vụ ở tầng domain/use case (không nhét vào controller/DB); import hướng vào trong.

  2. 2

    Chọn mẫu đúng

    Cho 3 nhu cầu: (a) đổi DB không sửa nghiệp vụ; (b) test nghiệp vụ không cần DB thật; (c) đọc & ghi khác nhau rất nhiều. Mẫu nào hợp mỗi cái?

    Hoàn thành khi: (a) repository/port-adapter; (b) dependency injection (tiêm bản giả); (c) cân nhắc CQRS. Giải thích được vì sao.

  3. 3

    Mẫu chịu lỗi

    Thêm luật resilience vào CLAUDE.md (timeout, retry chỉ cho lỗi tạm thời + backoff, idempotent). Nhờ Claude viết một lời gọi API ngoài.

    Hoàn thành khi: Lời gọi có timeout, retry có backoff cho lỗi tạm thời, và an toàn khi bị gọi lại (idempotent).

  4. 4

    Subagent kiến trúc

    Tạo .claude/agents/architecture-reviewer.md (chỉ-đọc) kiểm vi phạm ranh giới tầng. Dùng trong plan mode trước khi code một tính năng.

    Hoàn thành khi: Subagent phát hiện được một vi phạm (vd domain import từ infra) và đề xuất cách đặt lại cho đúng.

  5. 5

    Gói thành plugin (nâng cao)

    Gom các skill/subagent “chuẩn nghề” (bảo mật, a11y, clean code, kiến trúc) thành một plugin để tái dùng cho mọi dự án.

    Hoàn thành khi: Một plugin nạp được bằng claude --plugin-dir, mang theo bộ “gác cổng chất lượng” của bạn sang dự án khác.