🗺️ MOC - Kiến trúc ứng dụng web

28 dot notes tách từ ebook của Nguyễn Thế Huy. Sách đi từ tư tưởng gốc (tách core code khỏi infrastructure code) → Domain Model & Repository → Read/View Model & Application Service → Kiến trúc Phân lớp → và đỉnh cao là Hexagonal Architecture (Ports & Adapters). Đọc tuần tự lần đầu, sau đó dùng từng note như tài liệu tra cứu.

Tư tưởng xuyên suốt của cả cuốn sách: Code tốt = code dễ bảo trì & mở rộng. Đạt được bằng cách tách logic nghiệp vụ (core) khỏi chi tiết kỹ thuật (infrastructure) - nguyên tắc nền là Separation of Concerns, chìa khoá kỹ thuật là phụ thuộc vào trừu tượng (Dependency Inversion Principle, Dependency Injection).

🔗 Liên thông với bộ OOP cho người đi làm cùng tác giả: các note ở đây link thẳng tới Single Responsibility Principle, Open Closed Principle, Program to Interface - Tập trung vào trừu tượng, Dependency Injection, Observer Pattern… trong vault OOP.


🧭 Lộ trình học đề xuất (6 level)

Level 0 - Tư tưởng nền: vì sao phải tách Core & Infrastructure

Hiểu động cơ của cả cuốn sách trước khi đi vào kỹ thuật.

  1. Separation of Concerns - sợi chỉ đỏ xuyên suốt: mỗi phần code lo một mối quan tâm
  2. Vì sao phải tách biệt Core và Infrastructure - mục tiêu & tầm quan trọng. Bắt đầu từ đây!
  3. Vấn đề khi Core và Infrastructure trộn lẫn - 5 hậu quả của “mixed code”
  4. Lợi ích của việc tách biệt Core và Infrastructure - 6 phần thưởng khi tách đúng
  5. Giải pháp tổng quan để tách biệt Core và Infrastructure - bản đồ các kỹ thuật ⭐

Level 1 - Định nghĩa: phân biệt Core vs Infrastructure

Nền tảng khái niệm. Không chắc level này thì mọi kiến trúc phía sau chỉ là học vẹt.

  1. Core Code là gì - 2 quy tắc + bài test “chạy trong memory?”
  2. Infrastructure Code là gì - code kết nối bên ngoài; abstraction KHÔNG tự biến thành core
  3. Ranh giới giữa Core và Infrastructure Code - vùng xám thực tế & câu hỏi vàng để định danh

Level 2 - Domain Model & Repository 🔥 (trái tim)

Hiện thực hóa core code một cách chặt chẽ, có cấu trúc.

  1. Domain Model - Mô hình miền nghiệp vụ - Transaction Script vs Domain Model
  2. Domain Entity - invariant: đủ dữ liệu & hợp lệ ngay khi tạo
  3. Entity vs Value Object - có danh tính vs có giá trị
  4. Domain Service - logic không thuộc riêng entity nào
  5. Domain Event - OrderPlaced, ProductOutOfStock; pub/sub cấp domain
  6. Repository Pattern - interface ở domain, implementation ở hạ tầng ⭐⭐

Level 3 - Đọc/Ghi & Tầng ứng dụng

Tách mô hình đọc khỏi ghi, tách use case khỏi controller.

  1. Read Model - mô hình chuyên để đọc; đừng dùng entity cho hiển thị
  2. View Model - DTO sẵn sàng cho UI/API (JSON)
  3. CQRS - Command Query Responsibility Segregation - tách hẳn đường đọc và đường ghi
  4. Application Service - điều phối use case, gọi được từ web/CLI/test
  5. Command Object và DTO - gom input thành 1 object (khác Command Pattern GoF)

Level 4 - Kiến trúc Phân lớp (Layered Architecture)

Tổ chức toàn bộ thành các tầng có trách nhiệm rõ ràng.

  1. Layered Architecture - Kiến trúc Phân lớp - 3-tier cổ điển → biến thể DDD
  2. Tầng Domain - trái tim, thuần PHP, không dính framework
  3. Tầng Application - lớp keo điều phối, gọi domain
  4. Tầng Infrastructure - kết nối bên ngoài, triển khai mọi interface
  5. Dependency Rule - Quy tắc phụ thuộc một chiều - luật xương sống: phụ thuộc chỉ đi vào trong ⭐

Level 5 - Ports and Adapters (Hexagonal Architecture) 🏆 (đỉnh cao)

Đưa việc tách biệt lên mức linh hoạt cao nhất.

  1. Hexagonal Architecture - Ports and Adapters - lõi ở giữa, các cổng quanh rìa
  2. Port - Cổng giao tiếp - input port (UI/API/CLI) & output port (DB/mail/API)
  3. Adapter - Bộ chuyển đổi - code cụ thể cắm vào port (khác Adapter Pattern GoF)
  4. Bounded Context - cô lập giữa các phân hệ; tiền đề tách microservices

🚀 Lối vào nhanh theo vấn đề (tra cứu khi gặp chuyện)

Bạn đang gặp gì?Đọc note
Đọc code mà không hiểu “câu chuyện nghiệp vụ” đang diễn raVấn đề khi Core và Infrastructure trộn lẫnDomain Model - Mô hình miền nghiệp vụ
Logic nghiệp vụ nằm rải rác trong controllerApplication ServiceCommand Object và DTO
Dữ liệu sai (email lỗi, số lượng âm) lọt vào DBDomain Entity (invariant trong constructor)
Đổi MySQL sang DB khác phải sửa khắp nơiRepository PatternDependency Inversion Principle
Không viết được unit test vì phụ thuộc DB/networkCore Code là gìHexagonal Architecture - Ports and Adapters
Model vừa đọc vừa ghi, phình to khó bảo trìRead ModelView ModelCQRS - Command Query Responsibility Segregation
Không biết nên đặt code ở tầng nàoLayered Architecture - Kiến trúc Phân lớpDependency Rule - Quy tắc phụ thuộc một chiều
Muốn thêm kênh mới (API, CLI, mobile) mà không sửa logicHexagonal Architecture - Ports and AdaptersPort - Cổng giao tiếp
Đổi email sang SMS / đổi frameworkAdapter - Bộ chuyển đổi
Muốn phản ứng thêm khi có sự kiện (gửi SMS khi đặt hàng)Domain EventObserver Pattern
Context này gọi thẳng DB của context kiaBounded Context
Không biết đoạn code là core hay hạ tầngRanh giới giữa Core và Infrastructure Code

🧠 5 câu thần chú rút gọn cả cuốn sách

  1. “Tách logic nghiệp vụ (core) khỏi chi tiết kỹ thuật (infrastructure)” - Vì sao phải tách biệt Core và Infrastructure
  2. “Đoạn code này có chạy độc lập, trong memory, không side effect ngoài không?” - Core Code là gì
  3. “Phụ thuộc chỉ đi một chiều: từ ngoài vào trong” - Dependency Rule - Quy tắc phụ thuộc một chiều
  4. “Core định nghĩa port (interface), infrastructure cung cấp adapter” - Hexagonal Architecture - Ports and Adapters
  5. “Domain là trái tim - thuần, sạch, không biết gì về framework” - Tầng Domain

🗺️ Kiến trúc ứng dụng web - Reading Map

Sơ đồ cấu trúc & thứ tự đọc ebook của Nguyễn Thế Huy. Nguyên tắc: đọc tuần tự lần đầu (phần sau xây trên phần trước), sau đó dùng từng phần như tài liệu tra cứu.

Tổng quan: thứ tự đọc 6 phần

flowchart TD
    Start(["📖 Bắt đầu"]) --> P0

    subgraph P0["0️⃣ TƯ TƯỞNG NỀN - vì sao tách Core & Infrastructure"]
        direction TB
        A0["Separation of Concerns<br/>(mỗi phần lo một mối quan tâm)"]
        A1["Vì sao tách biệt<br/>(mục tiêu cả cuốn sách)"]
        A2["Vấn đề khi trộn lẫn<br/>(5 hậu quả)"]
        A3["Lợi ích khi tách biệt<br/>(6 phần thưởng)"]
        A4["Giải pháp tổng quan<br/>(bản đồ kỹ thuật)"]
        A0 --> A1 --> A2 --> A3 --> A4
    end

    P0 --> P1

    subgraph P1["1️⃣ ĐỊNH NGHĨA - Core vs Infrastructure Code"]
        direction TB
        B1["Core Code là gì<br/>(2 quy tắc, chạy trong memory)"]
        B2["Infrastructure Code là gì<br/>(kết nối bên ngoài)"]
        B3["Ranh giới thực tế<br/>(vùng xám, câu hỏi vàng)"]
        B1 --> B2 --> B3
    end

    P1 --> P2

    subgraph P2["2️⃣ DOMAIN MODEL & REPOSITORY 🔥 (trái tim)"]
        direction TB
        C0["Domain Model<br/>(mô hình miền nghiệp vụ)"]
        C1["Domain Entity + Value Object<br/>(invariant, danh tính vs giá trị)"]
        C2["Domain Service + Domain Event"]
        C3["Repository Pattern<br/>(interface ở domain, impl ở hạ tầng)"]
        C0 --> C1 --> C2 --> C3
    end

    P2 --> P3

    subgraph P3["3️⃣ ĐỌC/GHI & TẦNG ỨNG DỤNG"]
        direction TB
        D1["Read Model + View Model<br/>(mô hình chuyên đọc)"]
        D2["CQRS<br/>(tách đường đọc / đường ghi)"]
        D3["Application Service + Command/DTO<br/>(tách use case khỏi controller)"]
        D1 --> D2 --> D3
    end

    P3 --> P4

    subgraph P4["4️⃣ KIẾN TRÚC PHÂN LỚP (Layered)"]
        direction TB
        E1["Domain - Application - Infrastructure<br/>(3 tầng)"]
        E2["Dependency Rule<br/>(phụ thuộc chỉ đi VÀO TRONG)"]
        E1 --> E2
    end

    P4 --> P5

    subgraph P5["5️⃣ PORTS & ADAPTERS 🏆 (Hexagonal - đỉnh cao)"]
        direction TB
        F1["Hexagonal Architecture<br/>(lõi giữa, cổng quanh rìa)"]
        F2["Port (cổng) + Adapter (đầu nối)<br/>(input/output, quan hệ 1-n)"]
        F3["Bounded Context<br/>(cô lập giữa các phân hệ)"]
        F1 --> F2 --> F3
    end

    P5 --> Done(["✅ Dùng từng phần làm tài liệu tra cứu"])

    style P2 fill:#f8d7da,stroke:#c82333,stroke-width:2px
    style P5 fill:#fff3cd,stroke:#e0a800,stroke-width:3px
    style Start fill:#d4edda,stroke:#28a745
    style Done fill:#d4edda,stroke:#28a745

Tư tưởng chi phối tất cả

flowchart LR
    T1["🧠 Core Code<br/>(logic nghiệp vụ thuần)"] --> X["Mọi mẫu thiết kế<br/>& kiến trúc trong sách"]
    T2["🔌 Infrastructure Code<br/>(kết nối bên ngoài)"] --> X
    X --> R1["Domain Model + Repository"]
    X --> R2["Kiến trúc Phân lớp"]
    X --> R3["Ports & Adapters (Hexagonal)"]
    K["🔑 Chìa khoá chung:<br/>phụ thuộc vào TRỪU TƯỢNG (port),<br/>không phụ thuộc CHI TIẾT (adapter)"] --> X

Chọn cách xử lý nào? (tra nhanh theo vấn đề)

flowchart TD
    Q(["🤔 Vấn đề của bạn?"]) --> G{"Thuộc nhóm nào?"}

    G -- "Logic nghiệp vụ lộn xộn" --> C1{"Tình huống?"}
    C1 -- "Rải rác trong controller" --> F1["Application Service + Command"]
    C1 -- "Dữ liệu sai lọt vào DB" --> F2["Domain Entity (invariant)"]
    C1 -- "Logic tản mát, thủ tục" --> F3["Domain Model"]

    G -- "Phụ thuộc công nghệ" --> C2{"Tình huống?"}
    C2 -- "Đổi DB phải sửa khắp nơi" --> F4["Repository Pattern"]
    C2 -- "Đổi email→SMS / đổi framework" --> F5["Adapter (Hexagonal)"]
    C2 -- "Không test được vì dính DB" --> F6["Ports & Adapters + Core Code"]

    G -- "Đọc / hiển thị dữ liệu" --> C3{"Tình huống?"}
    C3 -- "Model vừa đọc vừa ghi, phình to" --> F7["Read Model / View Model"]
    C3 -- "Cần tối ưu đọc & ghi riêng" --> F8["CQRS"]

    G -- "Tổ chức & tích hợp" --> C4{"Tình huống?"}
    C4 -- "Không biết đặt code ở tầng nào" --> F9["Layered + Dependency Rule"]
    C4 -- "Thêm kênh mới (API/CLI/mobile)" --> F10["Port (Hexagonal)"]
    C4 -- "Context này gọi thẳng DB context kia" --> F11["Bounded Context"]

    style Q fill:#f8d7da,stroke:#c82333
    style F1 fill:#d4edda,stroke:#28a745
    style F2 fill:#d4edda,stroke:#28a745
    style F3 fill:#d4edda,stroke:#28a745
    style F4 fill:#d4edda,stroke:#28a745
    style F5 fill:#d4edda,stroke:#28a745
    style F6 fill:#d4edda,stroke:#28a745
    style F7 fill:#d4edda,stroke:#28a745
    style F8 fill:#d4edda,stroke:#28a745
    style F9 fill:#d4edda,stroke:#28a745
    style F10 fill:#d4edda,stroke:#28a745
    style F11 fill:#d4edda,stroke:#28a745