Lesson 0: Bản chất của Decorator
📋 Agenda
Thời gian đọc ước tính: ~20 phút
Sau bài này, bạn sẽ:
- ✅ Giải thích được Decorator Pattern (GoF) là gì và tại sao nó ra đời
- ✅ Phân biệt được Decorator Pattern (thiết kế) vs. decorator syntax (
@) trong TypeScript - ✅ Hiểu cơ chế
reflect-metadata— "bộ nhớ bí mật" mà TypeORM dựa vào để hoạt động - ✅ Tự tay viết được một custom decorator đơn giản để thấy rõ bản chất
- ✅ Đọc hiểu code khi gặp
@Entity(),@Column()trong TypeORM
Yêu cầu đầu vào (Prerequisites):
- 🔹 Biết cơ bản TypeScript (class, interface, generic)
- 🔹 Đã cài TypeORM và chạy thử ít nhất một lần (xem Lesson 1)
❓ Vấn đề & Giải pháp
Vấn đề (Problem Statement)
Khi làm việc với TypeORM, bạn sẽ gặp ngay đoạn code như sau:
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
email: string;
}
Hai câu hỏi tự nhiên xuất hiện trong đầu mọi Junior Dev:
- "Cái
@Entity()này thực ra là gì? Nó không phải comment, không phải function call thông thường..." - "TypeORM 'biết' class
Usertương ứng với tableusertrong database bằng cách nào?"
Nếu không hiểu câu trả lời, bạn sẽ dùng TypeORM như "phép màu" — gặp lỗi là bí, không sửa được.
Giải pháp (Solution)
@Entity() là một decorator — một hàm TypeScript đặc biệt chạy lúc class được định nghĩa (không phải lúc tạo object), với nhiệm vụ duy nhất: ghi thông tin cấu hình vào một kho lưu trữ metadata toàn cục (global metadata store).
Khi bạn khởi tạo AppDataSource.initialize(), TypeORM đọc lại kho đó và biết được mọi thứ: table nào, column nào, kiểu dữ liệu gì, quan hệ ra sao.
Bài này sẽ "phẫu thuật" cơ chế đó từng bước một.
📖 Phần 1: Decorator Pattern — Nền tảng tư duy
Định nghĩa kỹ thuật
Decorator Pattern (GoF — Gang of Four, 1994) là một Structural Design Pattern cho phép gắn thêm hành vi mới vào một object một cách động mà không thay đổi class gốc của nó, bằng cách bọc object đó trong một wrapper có cùng interface.
"Giải phẫu" định nghĩa:
| Từ khóa | Ý nghĩa |
|---|---|
| Structural | Liên quan đến cách tổ chức, kết hợp object/class — không phải cách tạo (Creational) hay cách tương tác (Behavioral) |
| gắn thêm hành vi | Thêm tính năng mới mà không sửa code cũ (Open/Closed Principle) |
| một cách động | Xảy ra lúc runtime, không phải compile-time |
| wrapper có cùng interface | Decorator implement cùng interface với object gốc → client không biết mình đang dùng wrapper hay object thật |
Tại sao GoF tạo ra pattern này?
Vấn đề của kế thừa (Inheritance):
Giả sử bạn có Coffee. Muốn thêm tính năng: CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar... Kế thừa sẽ tạo ra "class explosion" (bùng nổ số lượng class).
Giải pháp Decorator Pattern — Composition:
Bọc object trong các wrapper linh hoạt, chồng lên nhau tùy ý:
Kết quả: SugarDecorator.cost() = gọi MilkDecorator.cost() + thêm giá đường. Linh hoạt, không cần tạo thêm class.
Code minh họa Decorator Pattern thuần GoF
// ✅ filename: decorator-pattern-gof.ts
// Bước 1: Định nghĩa interface chung (Decorator và Component cùng implement)
interface Coffee {
cost(): number;
description(): string;
}
// Bước 2: Component gốc
class SimpleCoffee implements Coffee {
cost() { return 20_000; }
description() { return "Cà phê đen"; }
}
// Bước 3: Base Decorator — bọc object và delegate về component
abstract class CoffeeDecorator implements Coffee {
// Composition: GIỮ THAM CHIẾU đến object cần bọc
constructor(protected wrapped: Coffee) {}
// Delegate mặc định: gọi về component gốc (có thể override)
cost() { return this.wrapped.cost(); }
description() { return this.wrapped.description(); }
}
// Bước 4: Concrete Decorators — chỉ override phần mình quan tâm
class MilkDecorator extends CoffeeDecorator {
// Override để THÊM hành vi, rồi gọi về wrapped
cost() { return this.wrapped.cost() + 5_000; }
description() { return this.wrapped.description() + " + Sữa"; }
}
class SugarDecorator extends CoffeeDecorator {
cost() { return this.wrapped.cost() + 2_000; }
description() { return this.wrapped.description() + " + Đường"; }
}
// Client code: Chồng decorator tùy ý
const myOrder = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
console.log(myOrder.description()); // "Cà phê đen + Sữa + Đường"
console.log(myOrder.cost()); // 27_000
Composition over Inheritance — không kế thừa để mở rộng, mà bọc (wrap) để thêm hành vi. Decorator và Component cùng implement một interface → client không phân biệt được.
📖 Phần 2: Decorator Syntax @ trong TypeScript
Đây KHÔNG phải là GoF Decorator Pattern
Decorator syntax @Something trong TypeScript có tên giống nhau nhưng hoạt động khác hoàn toàn so với GoF Decorator Pattern. Đây là một quyết định đặt tên gây nhầm lẫn trong lịch sử JavaScript.
- GoF Pattern: Bọc object tại runtime để thêm hành vi
- TypeScript
@syntax: Một hàm đặc biệt chạy lúc class được load (định nghĩa), dùng để ghi metadata hoặc sửa class
Lịch sử ra đời
TypeORM hiện tại dùng phiên bản legacy (experimentalDecorators: true). Đây là lý do bạn thấy config đó trong tsconfig.json.
Decorator là hàm — không hơn không kém
Khi bạn viết @Entity(), thực chất TypeScript biên dịch nó thành:
// ✅ Bạn viết:
@Entity()
class User {}
// 🔧 TypeScript dịch ra tương đương:
let User = class User {};
User = Entity()(User) || User;
// ^^^^^^^^ Entity() trả về một hàm decorator
// hàm đó nhận class User làm argument
Có 4 loại decorator tương ứng với 4 vị trí gắn:
| Loại | Cú pháp | Đối số nhận vào |
|---|---|---|
| Class Decorator | @Entity() trên class | constructor của class |
| Property Decorator | @Column() trên property | prototype, propertyKey |
| Method Decorator | @BeforeInsert() trên method | prototype, methodName, descriptor |
| Parameter Decorator | @Param() trên tham số | prototype, methodName, paramIndex |