🏗️🧠 Requirement sai từ đầu, kiến trúc sai đến cuối - System Design P3
Functional vs Non-functional Requirements: Khi "Chạy Đúng" Là Chưa Đủ
1. Mở đầu: Câu chuyện từ "chiến trường" Production
Trong sự nghiệp của mình, tôi đã chứng kiến không ít đội ngũ kỹ sư rơi vào "thảm kịch" vào đúng ngày ra mắt. Hãy quay lại đợt Flash Sale lớn nhất của năm 2023: Một team kỹ sư vừa release tính năng thanh toán (checkout) cực kỳ quan trọng. Ở môi trường staging, mọi thứ hoàn hảo. Code pass toàn bộ unit test, logic nghiệp vụ không sai một li: trừ tồn kho chuẩn, tính tiền chính xác, lưu database mượt mà.
Nhưng khi vừa bước ra "chiến trường" production, hệ thống bắt đầu vỡ vụn. Không có lỗi logic (Logic Bug) nào được báo về, nhưng latency (độ trễ) tăng vọt từ milliseconds lên hàng chục giây. Database bị treo cứng vì quá tải, các service gọi nhau bị timeout dây chuyền. Hệ thống sập không phải vì code "sai", mà vì nó không thể "sống sót" dưới áp lực thực tế.
Bài học xương máu mà bất kỳ Senior Architect nào cũng phải nằm lòng: Thiết kế sai thường bắt đầu từ việc hiểu sai hoặc thiếu requirement. Một hệ thống chạy đúng tính năng nhưng không chịu tải được thì vẫn là một thất bại thảm hại về mặt kiến trúc.
2. Niềm tin phổ biến vs. Thực tế khắc nghiệt
Có một rào cản tư duy rất lớn phân tách giữa một Junior và một Senior Engineer khi tiếp cận yêu cầu hệ thống.
- Niềm tin sai lầm (Junior Perspective): Requirement chỉ là danh sách các feature từ Product Manager. Non-functional (phi chức năng) là "phần phụ", là những thứ có thể tối ưu sau khi hệ thống đã chạy được. Họ thường lạm dụng câu nói "Premature optimization is the root of all evil" (Tối ưu sớm là nguồn gốc của mọi tội lỗi) để trì hoãn việc suy nghĩ về kiến trúc.
- Thực tế TechCraft (Senior Perspective):Architecture is not Optimization. Thiết kế một hệ thống chịu được 100k RPM (Requests Per Minute) ngay từ đầu không phải là "tối ưu sớm", đó là "thiết kế đúng yêu cầu". Nếu Functional Requirements là "lời hứa" về tính năng, thì Non-functional Requirements chính là "nền móng" để lời hứa đó được thực hiện bền bỉ.
Tư duy "tối ưu sau" thường dẫn đến cái bẫy đắt giá nhất: Phải đập đi xây lại toàn bộ kiến trúc (Re-architecture) vì cấu trúc hạ tầng đã chọn ban đầu không có khả năng thích ứng với thực tế vận hành.
[!TIP] Senior PerspectiveProduct Managers thường chỉ quan tâm đến "Cái gì" (What). Nhiệm vụ của một Architect là chuyển hóa những câu nói mơ hồ như "Tôi muốn hệ thống chạy nhanh" thành các thông số kỹ thuật cụ thể như "p99 latency < 200ms tại 10k RPS".
3. Functional Requirements: "Hệ thống phải làm gì?" (The What)
Functional Requirements (FR) xác định các hành vi cụ thể của hệ thống—những nghiệp vụ mà code của bạn phải xử lý chính xác để đáp ứng nhu cầu kinh doanh.
Ví dụ thực tế với API create_order trong hệ thống E-commerce:
| Functional Requirement | Kết quả mong đợi |
|---|---|
| Validate Input | Kiểm tra thông tin user, sản phẩm, địa chỉ có hợp lệ không. |
| Kiểm tra tồn kho | Đảm bảo sản phẩm vẫn còn hàng trước khi cho phép đặt. |
| Tính toán thanh toán | Áp dụng mã giảm giá, phí ship và tổng tiền chính xác. |
| Lưu trữ dữ liệu | Ghi nhận đơn hàng vào Database với trạng thái "Pending". |
| Phản hồi | Trả về Order ID cho người dùng ngay lập tức. |
Về cơ bản, FR xác định các "hộp" (components) cần có trong sơ đồ kiến trúc của bạn.
4. Non-functional Requirements: "Hệ thống vận hành như thế nào?" (The How)
Nếu FR là cái khung, thì Non-functional Requirements (NFR) chính là chất liệu và sự liên kết bền bỉ giữa các bộ phận. Dưới lăng kính của một Senior Engineer, NFR là những "Constraint" (giới hạn) bắt buộc phải thỏa mãn:
- Scalability (Khả năng mở rộng): Không chỉ là thêm server, mà là khả năng hệ thống giữ được hiệu năng ổn định khi traffic tăng gấp 10. Một hệ thống scale tốt là một hệ thống có chi phí mở rộng tỷ lệ tuyến tính với traffic.
- Availability (Độ sẵn sàng): Lời hứa về việc hệ thống phải "sống". Nếu một Data Center sập, hệ thống có tự phục hồi không? Availability thường tỷ lệ nghịch với Consistency (theo CAP Theorem) – một sự đánh đổi mà bạn phải quyết định từ ngày đầu.
- Reliability & Consistency: Tại sao dữ liệu phải đúng? Trong hệ thống phân tán, Scalability mà không có Reliability thì chỉ là một cách nhanh hơn để làm sai dữ liệu ở quy mô lớn.
- Performance (Latency vs. Throughput): Cần phân biệt rõ: Latency là tốc độ của một request đơn lẻ, còn Throughput là năng suất tổng thể. Đôi khi bạn phải chấp nhận tăng Latency (như dùng Message Queue) để đạt được Throughput cực lớn.
5. Tại sao sự phân tách này quyết định kiến trúc?
Áp dụng tư duy "System Thinking", chúng ta thấy FR và NFR quyết định hai tầng khác nhau của thiết kế:
"Nếu Functional Requirements là bản vẽ phân chia các phòng trong một ngôi nhà, thì Non-functional Requirements chính là độ chịu lực của khung thép và công suất của lưới điện."
Hãy xem cách NFR "ép" kỹ sư phải ra quyết định kiến trúc:Nếu yêu cầu chỉ là "Lưu đơn hàng" (FR), một SQL Database đơn giản là đủ. Nhưng nếu NFR yêu cầu Latency < 100ms cho 1 triệu người dùng đồng thời, lúc này các SQL JOIN truyền thống không còn là một lựa chọn—chúng trở thành một "Architectural Liability" (gánh nặng kiến trúc).
Yêu cầu này buộc bạn phải:
- Đổi từ Relational Flexibility (SQL) sang Key-Value Speed (NoSQL/Redis).
- Sử dụng Caching để giảm tải cho Primary DB.
- Tách Microservices để scale độc lập những phần hot-path.
6. Case Study: Thất bại khi bỏ qua Non-functional Requirements
Tình huống: Một team xây dựng tính năng "Like" cho một mạng xã hội mới.
- Functional: User nhấn like -> Số like tăng 1.
- Sai lầm: Team áp dụng tư duy của nghiệp vụ kế toán (Strong Consistency) cho một tính năng cần High Availability. Họ dùng giải pháp khóa dữ liệu (Row-level Locking) trong SQL Database để đảm bảo số like chính xác tuyệt đối.
Hậu quả: Khi một Influencer đăng bài, hàng nghìn request đổ vào để "khóa" đúng một dòng dữ liệu duy nhất trong Database. Hệ thống bị treo cứng (Deadlock), Latency nổ tung, và toàn bộ service bị sập.
Senior's Root Cause Analysis: Team đã tranh luận về việc chọn Database (MySQL vs PostgreSQL) quá sớm khi chưa thống nhất mục tiêu về Throughput. Trong trường hợp này, việc số like lệch vài đơn vị trong vài giây (Eventual Consistency) là cái giá quá nhỏ để đổi lấy việc hệ thống luôn phản hồi (Availability).
7. Tư duy thiết kế: Cách bóc tách Requirement như một Senior
Để không rơi vào bẫy "chạy đúng nhưng sập", hãy thực hành quy trình 3 bước:
- Liệt kê Feature (FR): Hệ thống cần làm những gì?
- Áp đặt giới hạn (Constraints/NFR): Đặt câu hỏi về con số. Traffic bao nhiêu? Latency mục tiêu? Ngân sách hạ tầng?
- Xác định đánh đổi (Trade-offs): Với các con số đó, ta phải hy sinh điều gì?
Một Senior luôn bắt đầu bằng câu hỏi: "Điều gì xảy ra nếu...?" Nếu traffic tăng đột biến? Nếu mạng giữa các service bị chậm? Nếu Database chính bị sập? Câu trả lời cho những câu hỏi này chính là kiến trúc của bạn.
8. Đánh đổi (Trade-offs): Không có bữa trưa nào miễn phí
System Design là nghệ thuật quản lý các giới hạn. Bạn không thể có mọi thứ cùng lúc.
| Cặp thuộc tính xung đột | Bản chất đánh đổi | Business Impact |
|---|---|---|
| Consistency vs Availability | Muốn dữ liệu khớp 100% (Strong Consistency) thì phải chấp nhận hệ thống dừng hoạt động khi có sự cố mạng. | Thanh toán: Cần Consistency. Mạng xã hội: Cần Availability. |
| Latency vs Throughput | Xử lý hàng loạt (Batching) tăng năng suất tổng thể nhưng làm chậm từng request cá nhân. | Data Analytics: Ưu tiên Throughput. Hệ thống Trading: Ưu tiên Latency. |
| Cost vs Performance | Hệ thống cực nhanh và chịu lỗi tốt yêu cầu hạ tầng phức tạp và đắt đỏ. | Startup: Cần tối ưu chi phí. Enterprise: Cần tối ưu sự ổn định. |
[!TIP] Senior PerspectiveKiến trúc sư giỏi không phải là người chọn công nghệ "xịn" nhất, mà là người chọn sự đánh đổi phù hợp nhất với mô hình kinh doanh hiện tại.
9. Tổng kết và Bài học kinh nghiệm
- Kiến trúc tốt bắt đầu từ Requirement đúng: Đừng vẽ sơ đồ khi chưa biết hệ thống phải chịu tải bao nhiêu.
- Thông số là "luật": Đừng bàn về Database hay Kafka cho đến khi biết p99 Latency mục tiêu.
- NFR là tài sản sản phẩm: Khả năng mở rộng và độ sẵn sàng là tính năng của sản phẩm, không phải là việc riêng của DevOps.
10. Open Loop & Lời kết
Sau khi đã bóc tách được các yêu cầu Functional và Non-functional, câu hỏi tiếp theo sẽ là: Làm sao chúng ta biến những con số trừu tượng thành các con số kỹ thuật thực tế? Chúng ta cần bao nhiêu server? Bao nhiêu RAM? Database dung lượng bao nhiêu là đủ?
Chào mừng bạn đến với hành trình tiếp theo: Episode 04 - "Estimation 101: Ước Lượng “Đủ Xài” Trong System Design".
Bạn muốn đi sâu hơn vào tư duy hệ thống?
Nếu bạn muốn tìm hiểu cách những Senior Engineer đưa ra quyết định kiến trúc trong các môi trường production khắc nghiệt, hãy tham gia cộng đồng TechCraft. Chúng tôi chia sẻ những bài học thực chiến để giúp bạn thoát khỏi tư duy "build feature" và tiến tới tư duy "design system".
Khám phá Dev Insider - Đi nhanh hơn số đông
🚀 Tiếp tục hành trình cùng TechCraft
Bài viết này là một phần trong hành trình khám phá Backend Engineering, System Design và Production Systems tại TechCraft.
Nếu bạn muốn học theo một lộ trình rõ ràng hơn, TechCraft đang xây dựng Dev Insider như nơi tập trung các series chuyên sâu hơn về:
- Backend Internals
- Database Internals
- Transaction & Consistency
- Distributed Systems
- Production System Design
- AI-Proof Engineer
🚀 Dev Insider
https://www.patreon.com/techcraft_official/posts/vi-sao-dev-ra-161163881?collection=2220113
📘 Facebook
https://www.facebook.com/techcraft.official
🎥 YouTube
https://www.youtube.com/@techcraft.official
🎵 TikTok
https://www.tiktok.com/@techcraft.official
Hiểu hệ thống. Không chỉ framework.
All rights reserved