Mục lục

Project Structure: The Senior Architect Mindset

Phân tích sâu về Feature-based Architecture. Tại sao Layer-based lại giết chết khả năng scale? Những cạm bẫy 'Circular Dependency' và 'Cross-feature logic' mà Junior thường mắc phải.

Bạn code React 3 năm, project phình to, và bạn bắt đầu sợ mở cái folder src/components. Đó là lúc "Kiến trúc" (Architecture) lên tiếng.

Tôi đã từng maintain những dự án mà file utils.ts có 5000 dòng code, và components/Button.tsx được import ở 200 nơi với logic if-else rối rắm. Dưới đây là đúc kết xương máu về cách tổ chức file.

1. Cái chết của Layer-based (Mô hình phân tầng)

Mô hình truyền thống (Rails style):

Code:
src/
  controllers/
  models/
  views/

Áp dụng vào React -> components/, hooks/, context/.

Tại sao nó tệ hại?

  1. Context Switching: Để sửa 1 tính năng "Comment", bạn phải mở 1 file hooks, 1 file component, 1 file types nằm ở 3 góc trời khác nhau. Não bạn phải 'nhảy số' liên tục.
  2. Coupling ngầm (The Hidden Coupling):
    • Bạn thấy src/hooks/useUser.tssrc/hooks/useProduct.ts nằm cạnh nhau. Bạn tiện tay import useProduct vào useUser để check quyền mua hàng.
    • BÙM! Feature User giờ đây phụ thuộc chặt chẽ vào Feature Product. Bạn không thể tách User ra microservice riêng được nữa.

2. Feature-based (Screaming Architecture)

Hãy nhóm theo DOMAIN (Nghiệp vụ), chứ không phải theo ROLE (Kỹ thuật).

Code:
src/
  features/
    auth/         <- Mọi thứ về Auth nằm ở đây
    ecommerce/    <- Cart, Product, Checkout
    social/       <- Profile, Feed, Comment

Quy tắc "Vòng tròn đồng tâm"

Feature auth KHÔNG BAO GIỜ được biết về sự tồn tại của ecommerce. Nhưng ecommerce CÓ THỂ chọc vào auth (để lấy user ID).

Cách giải quyết Dependency Hell:

  1. Core Feature: Độc lập (Auth, UI Kit).
  2. Composite Feature: Phụ thuộc vào Core (Checkout cần Auth + UI Kit).

3. Những cạm bẫy cần tránh (Senior Note)

❌ Trap 1: Barrel File Explosion (index.ts)

Junior thường lười: export * from './components'. Kết quả: Import 1 cái nút, dính luôn cả cái feature. Webpack tree-shaking khóc thét. => Lời khuyên: Chỉ export đúng cái gì cần thiết ở features/auth/index.ts. Private component thì giấu kỹ đi.

❌ Trap 2: Cross-feature Logic (Vùng xám)

Vấn đề: Khi user thanh toán (Checkout Feature), cần gửi thông báo (Notification Feature). Code ở đâu?

  • Cách 1 (Tệ): Import sendNotification vào file Checkout. -> Coupling.
  • Cách 2 (Tốt): Dùng Event Bus hoặc Global Store Actions. Checkout chỉ dispatch PAYMENT_SUCCESS. Notification lắng nghe event đó. Không ai biết ai.

❌ Trap 3: Component chung chung "vô chủ"

Bạn tạo src/components/List.tsx. Sau đó Auth cần List user. Product cần List hàng. Dần dần List.tsx thành nồi lẩu thập cẩm if (type === 'user') ... else if (type === 'product'). => Lời khuyên: Thà duplicate code một chút (AuthList, ProductList) còn hơn tạo ra một component "God Object" quá trừu tượng. DRY (Don't Repeat Yourself) đôi khi là cái bẫy. (AHA - Avoid Hasty Abstractions).

4. Folder Structure chuẩn (Ý kiến cá nhân)

Code:
src/
├── app/               # Next.js App Router (Chỉ làm nhiệm vụ Routing)
├── components/        # Base UI (Button, Input, Modal - Dumb 100%)
├── lib/               # 3rd party setup (axios, queryClient, cn)
├── features/          # NGHIỆP VỤ CHÍNH
│   ├── auth/
│   │   ├── components/  # LoginForm, RegisterModal (Smart)
│   │   ├── api/         # login(), logout()
│   │   └── index.ts     # Public API
│   └── order/
├── stores/            # Global State quản lý cross-feature
└── utils/             # Helper thuần túy (date, string)

Kết luận

  • Đừng tối ưu hóa sớm. Nếu project < 10 files, để file nào cũng được.
  • Khi project > 50 files, hãy chuyển sang Feature-based.
  • Luôn tự hỏi: "Nếu tôi xóa Feature Order, tôi có cần sửa code trong Feature Auth không?". Nếu câu trả lời là CÓ -> Kiến trúc của bạn đang sai.
Quảng cáo
mdhorizontal