Sổ tay Kiến trúc Phần mềm
04 Phần I · Nền tảng Chương 8

Tư duy theo Component

Component-Based Thinking

Module là khái niệm gom code; component là hình hài vật lý của nó — viên gạch cơ bản nhất mà KTS thật sự cầm nắm. Quyết định lớn đầu tiên: chia component theo kỹ thuật hay theo nghiệp vụ?

01 Component là gì?

Component = sự bao gói vật lý của các module (physical packaging of modules) — đơn vị đơn giản & cơ bản nhất của kiến trúc. Mỗi ngôn ngữ có cơ chế riêng: jar (Java), dll (.NET), gem (Ruby), package/namespace/library/service.

Phạm vi component — từ "cùng bộ nhớ" đến "qua mạng"

Library cùng bộ nhớ function call Subsystem tầng / phân hệ deployable unit Service không gian riêng TCP/REST/queue Mức mô-đun cao dần · khoảng cách triển khai xa dần →
KTS không bắt buộc phải dùng component — chỉ là thường hữu ích khi cần mức mô-đun cao hơn mức thấp nhất ngôn ngữ cho. Một microservice nhỏ có thể chẳng cần chia component.

02 KTS vs Lập trình viên

Component là tầng thấp nhất mà KTS trực tiếp đụng tới. Bên trong nó là lớp/hàm — việc của tech lead & dev.

Kiến trúc sư

  • Xác định, tinh chỉnh, quản trị các component.
  • Chịu trách nhiệm phân vùng cấp cao (top-level partitioning).
  • Một trong các việc đầu tiên trên dự án mới.

Lập trình viên

  • Tạo lớp & hàm bên trong component.
  • Thiết kế class là việc của tech lead/dev.
  • KTS không nên micromanage từng quyết định.

Nếu KTS không bao giờ để vai khác ra quyết định hệ trọng, tổ chức sẽ cạn nguồn KTS kế cận — trao quyền cũng là một phần của thiết kế tốt.

03 Phân vùng: kỹ thuật vs domain

Quyết định nền tảng nhất khi định danh component: tổ chức theo khả năng kỹ thuật hay theo domain/luồng nghiệp vụ? Hai cách, hai bộ đánh đổi — không cách nào "đúng" tuyệt đối.

Khía cạnhPhân vùng kỹ thuật (technical)Phân vùng domain
Tổ chức quanhVai trò kỹ thuật: presentation, business, persistence…Luồng/domain: CatalogCheckout, Payment…
Khớp vớiLayered architecture, mẫu MVC (mặc định nhiều nơi)Domain-Driven Design, Bounded Context, microservices
ƯuDễ tìm code theo loại kỹ thuật; decoupling giữa các tầng.Domain gọn trong một chỗ, khớp kiểu thay đổi hay xảy ra.
NhượcMột domain (vd CatalogCheckout) bị trải mỏng khắp các tầng.Code kỹ thuật cùng loại nằm rải theo nhiều domain.

Định luật Conway. "Tổ chức thiết kế hệ thống bị ràng buộc tạo ra bản sao của cấu trúc giao tiếp của chính tổ chức." Chia đội theo kỹ thuật (Backend / DBA / UI) → kiến trúc nghiêng về phân tầng kỹ thuật. Inverse Conway Maneuver: đổi cấu trúc đội trước để thúc kiến trúc mong muốn.

RAG  Hệ "Hỏi–đáp tài liệu" nên phân vùng theo domain/luồng: pipeline Python tách thành các component có mục đích rõ, PHP là một component web ở ranh giới:

flowchart LR
  subgraph PHP["PHP · Web (component ranh giới)"]
    W["Web/API"]
  end
  subgraph PY["Python · pipeline (domain components)"]
    direction TB
    ING["Ingestion"]:::m
    RET["Retrieval"]:::m
    ANS["AnswerService"]:::m
    VS[("Vector Store")]:::db
    ING --> VS
    RET --> VS
    RET --> ANS
  end
  W -->|"POST /ingest"| ING
  W -->|"POST /query"| RET
  classDef m fill:#dbeee8,stroke:#0f7d72,color:#1c1a14;
  classDef db fill:#e2edf3,stroke:#2f6d93,color:#1c1a14;
            
Mỗi component (Ingestion, Retrieval, AnswerService) ôm một luồng — kết dính chức năng cao. Ranh giới PHP↔Python chính là chỗ quyết định monolith hay phân tán.

04 Chu kỳ định danh component

Tìm đúng component là một trong các việc khó nhất của KTS — nên nó là vòng lặp, không phải làm một lần. Năm bước:

Xác định component ban đầu

Dựa trên kiểu phân vùng đã chọn, phác các khối cấp cao. Bộ đầu tiên hiếm khi tốt — nên mới phải lặp.

Gán yêu cầu / user story

Ánh xạ story vào component để xem khớp tới đâu: tạo mới, gộp lại, hay tách ra nếu một component ôm quá nhiều.

Phân tích vai trò & trách nhiệm

Soi roles + hành vi để tính hạt (granularity) của component khớp hành vi ứng dụng.

Phân tích đặc tính kiến trúc

Đặc tính (vd scalability) có thể buộc chia nhỏ: hai phần đều xử lý input nhưng một phần phải đỡ hàng trăm user đồng thời → tách.

Tái cấu trúc component

Phản hồi là sống còn — liên tục lặp với dev khi edge case & hiểu biết mới lộ ra.

Granularity là bài toán đánh đổi. Quá mịn (fine-grained) → giao tiếp giữa component quá nhiều. Quá thô (coarse-grained) → coupling nội bộ cao, khó test & triển khai.

05 Cách phân rã & cái bẫy

Không có cách "đúng" duy nhất — chỉ là các kỹ thuật với đánh đổi khác nhau:

Actor / Actions

actor/actions approach

Xác định tác nhân (user/system) & hành động họ làm. Tổng quát, hợp cả monolith lẫn phân tán; tốt cho quy trình nhiều thiết kế trước.

Event Storming

từ DDD

Giả định hệ dùng message/event; tìm các sự kiện xảy ra rồi dựng component quanh handler. Hợp microservices.

Workflow

workflow approach

Như event storming nhưng không bắt buộc message — mô hình hoá quanh luồng công việc & vai trò then chốt.

Anti-pattern · Bẫy Thực thể (Entity Trap). Ánh xạ component một-một với bảng cơ sở dữ liệu (CustomerManager, OrderManager…). Đó không phải kiến trúc — chỉ là một ORM trá hình. Dấu hiệu thiếu suy nghĩ về luồng thật; component sinh ra quá thô, chẳng hướng dẫn được đội code.

RAG  Đừng rơi vào Entity Trap khi dựng RAG — gom theo luồng, đừng theo bảng:

Python · Entity Trap ✗ vs Workflow ✓
# ✗ Entity Trap — mỗi bảng DB thành một "Manager" (ORM trá hình)
class DocumentManager: ...      # bảng documents
class ChunkManager: ...         # bảng chunks
class VectorManager: ...        # bảng vectors

# ✓ Theo luồng — component ôm một workflow có mục đích
class IngestionService:  ...    # load → chunk → embed → lưu
class RetrievalService:  ...    # truy hồi theo câu hỏi
class AnswerService:     ...    # ghép ngữ cảnh → gọi LLM
Component theo luồng có kết dính chức năng cao; "Manager theo bảng" thì thô & vô định hướng.

Tách theo đặc tính — bài học GGG

Trong case study đấu giá Going, Going, Gone, KTS tách Bid Capture thành Bid Capture (người đấu giá) & Auctioneer Capture (đấu giá viên) vì hai bên cần scalability/reliability khác hẳn nhau.

RAG  Cùng logic: tách Ingestion (chạy nền, theo lô, chịu tải) khỏi Retrieval (đồng bộ, độ trễ thấp) — vì đặc tính kiến trúc của chúng đối nghịch, ép chung một component là gượng.

Architecture quantum (đơn vị triển khai độc lập + kết dính chức năng cao + connascence đồng bộ) giúp chốt sớm: nếu các component cần đặc tính khác nhau → nghiêng về phân tán; giống nhau → có thể giữ monolith. Đừng ám ảnh "thiết kế đúng duy nhất" — chọn bộ đánh đổi ít tệ nhất.

06 Ghi nhớ nhanh

Component = module đóng gói vật lý — viên gạch cơ bản nhất KTS cầm nắm, từ library → service.

KTS định component, dev định class — phân vùng cấp cao là việc của KTS; đừng micromanage bên trong.

Kỹ thuật hay domain? — quyết định nền tảng; nhớ Conway's Law: kiến trúc soi gương cấu trúc đội.

Định danh component là vòng lặp — gán story, soi vai trò & đặc tính, rồi tái cấu trúc liên tục.

Tránh Entity Trap — đừng map component 1-1 với bảng DB; gom theo luồng để kết dính cao.

NguồnChương 8 (Component-Based Thinking), Fundamentals of Software Architecture — Mark Richards & Neal Ford, O'Reilly 2020.