Mỗi phương pháp thiết kế phần mềm đều sinh ra để giải quyết một vấn đề cụ thể của thời đại nó. Đọc theo trình tự thời gian, có thể thấy một chuỗi liên tục: phương pháp này dọn xong bài toán phương pháp trước để lại, và đặt ra bài toán mới cho phương pháp kế tiếp. Bài này kể câu chuyện đó — từ 1968 đến nay.
Table of contents
Open Table of contents
- Dòng thời gian tổng quan
- 1. Structured Programming (1968–1970s)
- 2. Object-Oriented Programming (1970s–1980s)
- 3. Design by Contract (1986)
- 4. Component-Based Software Engineering (1990s)
- 5. Aspect-Oriented Programming (cuối 1990s)
- 6. Domain-Driven Design (2003)
- 7. Hexagonal Architecture / Ports and Adapters (2005)
- 8. Behavior-Driven Development (2006)
- 9. CQRS & Event Sourcing (2010s)
- 10. Clean Architecture (2012)
- 11. Microservices (2014)
- Khi nhìn lại, chúng xếp chồng lên nhau
- Cách chọn cho dự án của bạn
- Tóm lại
Dòng thời gian tổng quan
timeline
title Lịch sử các phương pháp thiết kế phần mềm
1968 : Structured Programming
: (Dijkstra)
1972 : Object-Oriented Programming
: (Alan Kay - Smalltalk)
1986 : Design by Contract
: (Bertrand Meyer)
1991 : Component-Based SE
: (CORBA, COM)
1997 : Aspect-Oriented Programming
: (Gregor Kiczales)
2003 : Domain-Driven Design
: (Eric Evans)
2005 : Hexagonal Architecture
: (Alistair Cockburn)
2006 : Behavior-Driven Development
: (Dan North)
2010 : CQRS & Event Sourcing
: (Greg Young)
2012 : Clean Architecture
: (Robert C. Martin)
2014 : Microservices
: (Fowler & Lewis)
1. Structured Programming (1968–1970s)
Câu chuyện bắt đầu năm 1968. Code thời đó dùng goto nhảy tự do — chương trình viết xong vài tháng quay lại đọc còn không dò được luồng chạy. Bài Go To Statement Considered Harmful của Dijkstra ra mắt năm đó là cột mốc. Cùng Constantine và Yourdon, ông đề xuất một bộ kỷ luật đơn giản: bỏ goto, viết theo hàm/thủ tục, gò luồng vào ba cấu trúc cơ bản (sequence, selection, iteration), và bẻ bài toán lớn xuống dần.
Nghe bây giờ thấy hiển nhiên — vì mọi paradigm sau này đều giả định bạn đã có những thứ này.
flowchart TD
A[Bài toán lớn] --> B[Module 1]
A --> C[Module 2]
A --> D[Module 3]
B --> B1[Function 1.1]
B --> B2[Function 1.2]
C --> C1[Function 2.1]
C --> C2[Function 2.2]
D --> D1[Function 3.1]
classDef root fill:#dbeafe,stroke:#1e40af,color:#1e3a8a,stroke-width:2px
classDef module fill:#fef3c7,stroke:#92400e,color:#78350f
class A root
class B,C,D module
| Ưu điểm | Nhược điểm |
|---|---|
| Dễ học, dễ đọc | Khó quản lý dữ liệu và hành vi cùng nhau |
| Phù hợp thuật toán tuyến tính | Dữ liệu toàn cục khó kiểm soát |
| Nền tảng cho mọi paradigm sau này | Khó tái sử dụng code |
2. Object-Oriented Programming (1970s–1980s)
Tới giữa thập niên 70, lập trình cấu trúc đã giải quyết được vấn đề luồng. Nhưng khi codebase phình to lên hàng trăm nghìn dòng, một vấn đề mới lộ ra: dữ liệu một nơi, hàm thao tác trên dữ liệu một nơi khác, sửa một chỗ phá ba chỗ. Alan Kay với Smalltalk (1972), rồi Stroustrup với C++ (1983), rồi Java (1995) cùng đẩy một ý: gom dữ liệu cùng hành vi vào object, mô hình theo cách thế giới thực vận hành.
Bốn ý chính của OOP:
mindmap
root((OOP))
Encapsulation
Đóng gói dữ liệu
Ẩn implementation
Inheritance
Tái sử dụng code
Phân cấp class
Polymorphism
Đa hình
Override method
Abstraction
Interface
Abstract class
Ví dụ quan hệ class:
classDiagram
class Animal {
+String name
+int age
+eat()
+sleep()
}
class Dog {
+bark()
+fetch()
}
class Cat {
+meow()
+scratch()
}
Animal <|-- Dog
Animal <|-- Cat
| Ưu điểm | Nhược điểm |
|---|---|
| Mô hình hóa thế giới thực tự nhiên | Lạm dụng kế thừa gây cứng nhắc |
| Tái sử dụng qua kế thừa, composition | Dễ rơi vào “anemic model” |
| Encapsulation giảm coupling | Tư duy “mọi thứ là object” không luôn phù hợp |
3. Design by Contract (1986)
OOP giải xong bài toán tổ chức — nhưng để lại bài toán tin cậy. Khi object A gọi object B, ai đảm bảo B làm đúng việc B nói nó làm? Bertrand Meyer trả lời câu này trong ngôn ngữ Eiffel năm 1986: coi mỗi method như một hợp đồng. Caller phải đảm bảo precondition, method đảm bảo postcondition, class luôn duy trì invariant. Vi phạm hợp đồng thì biết ngay ai sai — không còn chuyện đổ lỗi qua lại.
Mô hình hợp đồng:
flowchart LR
A[Caller] -- Đảm bảo Precondition --> B[Method]
B -- Đảm bảo Postcondition --> C[Caller nhận kết quả]
B -. Luôn duy trì Invariant .- B
classDef caller fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef method fill:#fef3c7,stroke:#92400e,color:#78350f
class A,C caller
class B method
| Ưu điểm | Nhược điểm |
|---|---|
| Tăng độ tin cậy phần mềm | Ít ngôn ngữ hỗ trợ native |
| Tài liệu hóa rõ ràng kỳ vọng | Tăng overhead khi viết code |
| Phát hiện lỗi sớm | Khó áp dụng vào codebase có sẵn |
4. Component-Based Software Engineering (1990s)
Class giúp tái sử dụng trong cùng codebase. Nhưng đầu thập niên 90, khi các hệ thống enterprise lớn lên và các công ty bắt đầu cần dùng phần mềm của nhau, class không đủ. CORBA (1991), COM của Microsoft (1993), rồi JavaBeans (1996), .NET, OSGi — tất cả cùng chạy theo một ý: đóng gói thành component có interface rõ, có thể versioning, deploy độc lập, lắp ghép như Lego — kể cả lắp ghép giữa các ngôn ngữ và hệ thống khác nhau.
flowchart TB
subgraph App [Ứng dụng]
direction LR
C1[Component A<br/>Authentication]
C2[Component B<br/>Payment]
C3[Component C<br/>Notification]
C4[Component D<br/>Reporting]
end
C1 -. Interface .- C2
C2 -. Interface .- C3
C1 -. Interface .- C4
classDef comp fill:#dcfce7,stroke:#166534,color:#14532d
class C1,C2,C3,C4 comp
| Ưu điểm | Nhược điểm |
|---|---|
| Tái sử dụng cao | Phức tạp về mặt kỹ thuật |
| Phát triển song song | Vấn đề versioning component |
| Tách biệt interface và implementation | Dependency hell, overhead |
5. Aspect-Oriented Programming (cuối 1990s)
Component giải bài toán tái sử dụng ở mức vĩ mô. Nhưng bên trong từng component, một loại vấn đề khác vẫn còn nguyên: logging, security check, transaction management — những thứ này len vào mọi method nghiệp vụ và không có chỗ nào thực sự là “của” chúng. Gregor Kiczales tại Xerox PARC (AspectJ, 1997–2001) đề xuất gom chúng lại thành aspect — viết một nơi, weave vào nhiều nơi qua khai báo. Business logic không còn phải gánh các concern xuyên ngang.
flowchart TD
subgraph Business [Business Logic]
M1[createOrder]
M2[processPayment]
M3[shipProduct]
end
subgraph Aspects [Cross-cutting Aspects]
A1[Logging]
A2[Security]
A3[Transaction]
end
A1 -. weave .-> M1
A1 -. weave .-> M2
A1 -. weave .-> M3
A2 -. weave .-> M1
A2 -. weave .-> M2
A3 -. weave .-> M2
classDef biz fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef asp fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
class M1,M2,M3 biz
class A1,A2,A3 asp
| Ưu điểm | Nhược điểm |
|---|---|
| Giảm code trùng lặp đáng kể | Khó debug, luồng không hiển nhiên |
| Tách biệt business và infrastructure | Đường cong học tập dốc |
| Code chính sạch sẽ | Dễ bị lạm dụng |
6. Domain-Driven Design (2003)
Đến đầu thế kỷ 21, các vấn đề kỹ thuật đã có lời giải tương đối. Nhưng vấn đề lớn nhất vẫn còn — và nó không phải vấn đề kỹ thuật. Phần lớn dự án enterprise không chết vì code dở. Chúng chết vì code và nghiệp vụ nói hai thứ tiếng khác nhau, không ai chịu trách nhiệm dịch giữa hai bên. Eric Evans, trong cuốn Domain-Driven Design: Tackling Complexity in the Heart of Software (2003), đề xuất một cách tiếp cận: đặt domain (nghiệp vụ thực) vào trung tâm thiết kế, dùng cùng vốn từ trong code lẫn trong cuộc họp với business — không còn chỗ nào phải dịch nữa.
DDD chia làm hai tầng:
flowchart TB
DDD[Domain-Driven Design]
DDD --> Strategic
DDD --> Tactical
subgraph Strategic [Strategic Design]
S1[Ubiquitous Language]
S2[Bounded Context]
S3[Context Map]
end
subgraph Tactical [Tactical Design]
T1[Entity]
T2[Value Object]
T3[Aggregate]
T4[Domain Event]
T5[Repository]
T6[Domain Service]
end
classDef center fill:#dcfce7,stroke:#166534,color:#14532d,stroke-width:3px
classDef strategic fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef tactical fill:#fef3c7,stroke:#92400e,color:#78350f
class DDD center
class S1,S2,S3 strategic
class T1,T2,T3,T4,T5,T6 tactical
Ví dụ Bounded Context trong E-commerce:
flowchart LR
subgraph Sales [Sales Context]
S_Customer[Customer = Buyer]
S_Order[Order]
end
subgraph Support [Support Context]
SP_Customer[Customer = Ticket Owner]
SP_Ticket[Ticket]
end
subgraph Shipping [Shipping Context]
SH_Customer[Customer = Recipient]
SH_Address[Address]
end
Sales <-. Context Map .-> Support
Sales <-. Context Map .-> Shipping
classDef ctx fill:#dcfce7,stroke:#166534,color:#14532d
class S_Customer,S_Order,SP_Customer,SP_Ticket,SH_Customer,SH_Address ctx
| Ưu điểm | Nhược điểm |
|---|---|
| Thu hẹp khoảng cách business–tech | Đường cong học tập dốc |
| Quản lý phức tạp hiệu quả | Over-engineering với hệ thống đơn giản |
| Code dễ bảo trì lâu dài | Cần domain expert thực sự |
| Nền tảng cho microservices | Tốn chi phí thiết kế ban đầu |
7. Hexagonal Architecture / Ports and Adapters (2005)
DDD đặt domain vào trung tâm — về mặt khái niệm. Nhưng trong code thực tế, lõi domain vẫn dễ dính chặt vào Spring, vào Postgres, vào REST controller. Đổi một thứ ở rìa kéo theo phải sửa cả lõi. Alistair Cockburn (2005) đề xuất một bố cục dứt khoát: lõi public các port (interface), thế giới ngoài (UI, DB, external API) cài adapter để nói chuyện với lõi qua các port đó. Đổi DB là đổi adapter, không động vào một dòng nào của logic.
flowchart TB
subgraph External [Thế giới bên ngoài]
UI[Web UI]
CLI[CLI]
DB[(Database)]
API[External API]
end
subgraph Adapters [Adapters]
UA[UI Adapter]
CA[CLI Adapter]
DA[DB Adapter]
AA[API Adapter]
end
subgraph Core [Core Domain]
Ports[Ports / Interfaces]
Logic[Business Logic]
end
UI --> UA --> Ports
CLI --> CA --> Ports
Ports --> DA --> DB
Ports --> AA --> API
Ports <--> Logic
classDef core fill:#dcfce7,stroke:#166534,color:#14532d,stroke-width:3px
classDef adapter fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef ext fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
class Ports,Logic core
class UA,CA,DA,AA adapter
class UI,CLI,DB,API ext
| Ưu điểm | Nhược điểm |
|---|---|
| Business logic độc lập với công nghệ | Tăng số lượng abstraction |
| Dễ test (mock adapter) | Over-engineering với app nhỏ |
| Dễ đổi DB, UI framework | Cần kỷ luật cao trong team |
8. Behavior-Driven Development (2006)
DDD và Hexagonal giúp tách lõi và rìa, nhưng làm sao chứng minh lõi đúng? TDD đã trả lời phần nào — viết test trước, code sau. Nhưng cú pháp test toàn ngôn ngữ kỹ thuật, business stakeholder mở ra đọc không hiểu gì, lại quay về cảnh “code và nghiệp vụ nói hai thứ tiếng” mà DDD vừa cố giải. Dan North (2006) đẩy thêm một bước: viết test bằng ngôn ngữ ai cũng đọc được, dạng Given — When — Then. Test trở thành đặc tả, đặc tả trở thành test, business tham gia được vào cả hai.
Quy trình BDD:
flowchart LR
A[Business Requirement] --> B[Viết Scenario<br/>Given-When-Then]
B --> C[Step Definitions]
C --> D[Viết Code<br/>để test pass]
D --> E[Refactor]
E --> F{Đủ scenario?}
F -- Chưa --> B
F -- Đủ --> G[Done]
classDef start fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef step fill:#fef3c7,stroke:#92400e,color:#78350f
classDef test fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
classDef done fill:#dcfce7,stroke:#166534,color:#14532d,stroke-width:2px
class A start
class B,C,E step
class D test
class G done
Ví dụ scenario:
Feature: Đăng nhập hệ thống
Scenario: Đăng nhập thành công
Given người dùng đã đăng ký với email "user@example.com"
When người dùng nhập đúng mật khẩu
Then hệ thống hiển thị trang chủ
| Ưu điểm | Nhược điểm |
|---|---|
| Stakeholder phi kỹ thuật đọc được | Tốn công viết và bảo trì scenario |
| Tài liệu sống, đồng bộ với code | Dễ bị lạm dụng cho mọi loại test |
| Tập trung vào hành vi nghiệp vụ | Cần sự tham gia thực sự của business |
9. CQRS & Event Sourcing (2010s)
Đến đầu thập niên 2010, hệ thống bắt đầu phình to ở quy mô internet — Twitter, Facebook, Amazon. CRUD chuẩn (đọc và ghi cùng model, cùng database) tới một quy mô nào đó là gãy: ghi cần consistency cao, đọc cần throughput cao, một schema không phục vụ tốt cả hai. Greg Young hệ thống hóa CQRS quanh năm 2010, dựa trên CQS của Bertrand Meyer thập niên 80: tách hẳn write side và read side. Đi kèm là Event Sourcing (Fowler mô tả 2005): không lưu state hiện tại, lưu chuỗi event đã xảy ra — khi cần state thì replay từ đầu.
Mô hình CQRS:
flowchart TB
Client[Client]
Client -- Command --> CommandSide
Client -- Query --> QuerySide
subgraph CommandSide [Write Side]
CH[Command Handler]
WM[Write Model]
ES[(Event Store)]
CH --> WM --> ES
end
subgraph QuerySide [Read Side]
QH[Query Handler]
RM[Read Model]
QDB[(Read DB)]
QH --> RM --> QDB
end
ES -. Event .-> RM
classDef write fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
classDef read fill:#dcfce7,stroke:#166534,color:#14532d
class CH,WM write
class QH,RM read
Mô hình Event Sourcing:
flowchart LR
A[OrderCreated] --> B[ItemAdded]
B --> C[ItemAdded]
C --> D[PaymentReceived]
D --> E[OrderShipped]
E --> F[Current State]
G[Replay Events] -. Tái dựng .-> F
classDef event fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef state fill:#dcfce7,stroke:#166534,color:#14532d,stroke-width:2px
class A,B,C,D,E event
class F state
| Ưu điểm | Nhược điểm |
|---|---|
| Audit trail tự nhiên | Phức tạp hơn nhiều so với CRUD |
| Scale đọc/ghi độc lập | Eventually consistent |
| Phù hợp với DDD | Schema migration của event khó |
| Khả năng “time travel” | Dễ over-engineering |
10. Clean Architecture (2012)
Đến đây ngành đã có cả một dãy kiến trúc cùng nói một ý tưởng — Hexagonal, Onion, Screaming Architecture, DDD layered — đều nói chuyện “lõi không biết gì về rìa”, nhưng mỗi cái dùng một bộ thuật ngữ khác. Mỗi đội lại tranh cãi nên dùng tên nào. Robert C. Martin (Uncle Bob) gom chúng lại thành Clean Architecture (2012, sách 2017): bốn vòng đồng tâm, một quy tắc duy nhất — phụ thuộc chỉ hướng từ ngoài vào trong. Không có gì hoàn toàn mới, nhưng có một cái tên thống nhất để cả ngành tham chiếu.
Mô hình các vòng tròn đồng tâm:
flowchart TB
subgraph L4 [Frameworks & Drivers]
subgraph L3 [Interface Adapters]
subgraph L2 [Use Cases]
subgraph L1 [Entities]
E[Business Rules]
end
UC[Application Logic]
end
IA[Controllers, Presenters, Gateways]
end
FW[Web, DB, UI, External APIs]
end
FW -- Phụ thuộc --> IA
IA -- Phụ thuộc --> UC
UC -- Phụ thuộc --> E
classDef inner fill:#dcfce7,stroke:#166534,color:#14532d,stroke-width:3px
classDef use fill:#fef3c7,stroke:#92400e,color:#78350f
classDef adapter fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef ext fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
class E inner
class UC use
class IA adapter
class FW ext
Nguyên tắc Dependency Rule: Phụ thuộc chỉ hướng từ ngoài vào trong.
| Ưu điểm | Nhược điểm |
|---|---|
| Cấu trúc rõ ràng, dễ truyền đạt | Tăng số layer, boilerplate |
| Tách biệt business và infrastructure | Quá mức cho dự án nhỏ |
| Dễ test | Tranh cãi về việc đặt code ở layer nào |
| Phổ biến, dễ tìm tài liệu |
11. Microservices (2014)
Clean Architecture giải bài toán cấu trúc bên trong một ứng dụng. Nhưng khi tổ chức lớn lên, vấn đề mới chính là “một ứng dụng” đó: monolith vài triệu dòng, mỗi lần deploy là cả công ty hồi hộp; scale chỉ có một mức (tất cả hoặc không); người mới phải đọc hết codebase trước khi sửa được một thứ. Netflix và Amazon đã chạy theo hướng giải khác từ trước, nhưng phải đến bài của Martin Fowler và James Lewis năm 2014 thì “microservices” mới có tên gọi chính thức. Ý là: chia hệ thống thành các service nhỏ — mỗi service có database riêng, deploy riêng, scale riêng. Đổi lại, độ phức tạp vận hành tăng vọt.
So sánh Monolith vs Microservices:
flowchart TB
subgraph Monolith [Monolithic Architecture]
UI1[UI Layer]
BL1[Business Logic]
DB1[(Single DB)]
UI1 --> BL1 --> DB1
end
subgraph Microservices [Microservices Architecture]
GW[API Gateway]
S1[User Service]
S2[Order Service]
S3[Payment Service]
S4[Notification Service]
DB2[(User DB)]
DB3[(Order DB)]
DB4[(Payment DB)]
GW --> S1
GW --> S2
GW --> S3
GW --> S4
S1 --> DB2
S2 --> DB3
S3 --> DB4
S2 -. Event .-> S4
end
classDef mono fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
classDef micro fill:#dcfce7,stroke:#166534,color:#14532d
class UI1,BL1 mono
class GW,S1,S2,S3,S4 micro
| Ưu điểm | Nhược điểm |
|---|---|
| Scale độc lập | Phức tạp về vận hành |
| Deploy độc lập | Distributed system fallacies |
| Team autonomy cao | Khó debug |
| Tech stack đa dạng | Tốn chi phí infrastructure |
| Phù hợp với DDD bounded context | Không phù hợp team nhỏ |
Khi nhìn lại, chúng xếp chồng lên nhau
Đọc xong từ 1968 đến 2014, có một điều dễ bỏ lỡ: các phương pháp này không thay thế lẫn nhau — chúng chồng lên nhau. OOP không xóa Structured Programming, nó dựa vào. DDD không phế OOP, nó cho OOP một lý do để tồn tại. Microservices không bỏ Clean Architecture, nó áp Clean cho từng service.
Có thể vẽ thành sơ đồ:
flowchart TB
subgraph Foundation [Nền tảng]
OOP[OOP]
SP[Structured Programming]
end
subgraph Modeling [Modeling]
DDD[DDD]
DBC[Design by Contract]
end
subgraph Architecture [Kiến trúc]
Hex[Hexagonal]
Clean[Clean Architecture]
MS[Microservices]
end
subgraph Patterns [Patterns]
CQRS[CQRS]
ES[Event Sourcing]
AOP[AOP]
CBSE[Component-Based]
end
subgraph Process [Process]
BDD[BDD]
end
SP --> OOP
OOP --> DDD
OOP --> DBC
DDD --> Hex
Hex --> Clean
DDD --> MS
DDD --> CQRS
CQRS --> ES
DDD --> BDD
classDef found fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef model fill:#fef3c7,stroke:#92400e,color:#78350f
classDef arch fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
classDef pat fill:#dcfce7,stroke:#166534,color:#14532d
classDef proc fill:#f4f4f5,stroke:#52525b,color:#27272a
class SP,OOP found
class DDD,DBC model
class Hex,Clean,MS arch
class CQRS,ES,AOP,CBSE pat
class BDD proc
Một dự án 2026 thường đồng thời mang theo nhiều lớp di sản:
- OOP làm ngôn ngữ chung của codebase
- DDD để mô hình domain
- Clean Architecture cho cấu trúc tổng thể
- Microservices cho cách deploy
- CQRS + Event Sourcing cho một vài bounded context có yêu cầu đặc biệt
- BDD cho lớp acceptance test
Không có dự án nào dùng “tất cả” — mỗi dự án chọn cái phù hợp với điểm đau của nó. Câu hỏi quan trọng không phải “phương pháp nào đúng” mà “tôi đang đau ở đâu, và phương pháp nào sinh ra để giải đúng cái đau đó”.
Cách chọn cho dự án của bạn
Một sơ đồ ra quyết định đơn giản — không phải để theo cứng, mà để gợi lại các câu hỏi cần tự trả lời trước khi rủ team theo một trường phái nào đó:
flowchart TD
Start[Bắt đầu dự án] --> Q1{Domain phức tạp?}
Q1 -- Không --> Simple[CRUD đơn giản<br/>+ MVC]
Q1 -- Có --> Q2{Team lớn?}
Q2 -- Không --> Mono[Monolith<br/>+ DDD<br/>+ Clean Architecture]
Q2 -- Có --> Q3{Cần scale độc lập?}
Q3 -- Không --> Modular[Modular Monolith<br/>+ DDD]
Q3 -- Có --> Q4{Cần audit / lịch sử?}
Q4 -- Không --> MS2[Microservices<br/>+ DDD]
Q4 -- Có --> Full[Microservices<br/>+ DDD<br/>+ CQRS + Event Sourcing]
classDef start fill:#dcfce7,stroke:#166534,color:#14532d,stroke-width:2px
classDef good fill:#dcfce7,stroke:#166534,color:#14532d
classDef warn fill:#fef3c7,stroke:#92400e,color:#78350f
classDef bad fill:#fee2e2,stroke:#991b1b,color:#7f1d1d
class Start start
class Simple,Mono,Modular,MS2 good
class Full bad
Tóm lại
50 năm thiết kế phần mềm có thể tóm gọn thành một quy luật: mỗi phương pháp giải quyết một bài toán cụ thể của thời đại nó, đồng thời để lại bài toán mới cho thời đại sau. Không có phương pháp nào “đúng tuyệt đối”, và cũng không có phương pháp nào lỗi thời hoàn toàn — vì các bài toán cũ vẫn xuất hiện trong codebase mới mỗi ngày.
Cái thiếu nhất không phải là biết các phương pháp. Cái thiếu là biết vấn đề nào mỗi phương pháp được sinh ra để giải, và nhận ra trong codebase của mình lúc nào đang gặp đúng vấn đề đó.
Đó mới là kỹ năng thiết kế phần mềm — chứ không phải thuộc tên các pattern.