Chương 08 · Refactoring & Architecture

Anti-patterns + Refactoring + DDD-lite

Code smell catalog, refactoring techniques (Fowler), kiến trúc layered/hexagonal, và 5 building block của Domain-Driven Design.

1. Code Smells — dấu hiệu code có vấn đề

Martin Fowler (sách Refactoring, 1999) liệt kê > 20 code smell. Đây là những cái phổ biến nhất:

1.1. Bloaters — code phình

  • Long Method — function > 30-50 dòng. Khó đọc, khó test.
  • Large Class — > 500 dòng, > 20 method. Vi phạm SRP.
  • Long Parameter List — > 4 tham số. Khó nhớ thứ tự, dễ swap.
  • Data Clumps — 2-3 field luôn đi cùng nhau ở nhiều chỗ → tạo class chứa cả nhóm.
  • Primitive Obsession — dùng string/number raw cho concept (email, money) → tạo Value Object.

1.2. OO abusers — dùng OOP sai cách

  • Switch Statements — switch theo type → thường dấu hiệu thiếu polymorphism. Refactor sang Strategy/State/Visitor.
  • Temporary Field — field chỉ dùng trong vài method, lúc khác là null/0.
  • Refused Bequest — subclass không dùng phần lớn parent → có thể không nên kế thừa.
  • Alternative Classes with Different Interfaces — 2 class làm việc tương tự nhưng API khác.

1.3. Change preventers — khó thay đổi

  • Divergent Change — 1 class phải sửa vì nhiều lý do khác nhau (vi phạm SRP).
  • Shotgun Surgery — 1 thay đổi business → phải sửa nhiều class.
  • Parallel Inheritance Hierarchies — thêm class ở hierarchy A → phải thêm tương ứng ở B.

1.4. Dispensables — thừa thãi

  • Comments — comment giải thích "what" thay vì code rõ. Đa số comment có thể loại bằng đặt tên tốt.
  • Duplicated Code — copy-paste 3 chỗ trở lên → DRY.
  • Lazy Class — class quá nhỏ, không justify tồn tại.
  • Data Class — class chỉ field + getter/setter (anemic).
  • Speculative Generality — abstraction "phòng xa" mà không có biến thể thực.
  • Dead Code — code không bao giờ chạy.

1.5. Couplers — coupling chặt

  • Feature Envy — method 1 class dùng nhiều thứ của class khác hơn của chính nó. Có thể nên move method.
  • Inappropriate Intimacy — 2 class biết quá nhiều về nội bộ nhau.
  • Message Chainsa.b().c().d() — vi phạm Demeter.
  • Middle Man — class chỉ delegate gần như mọi thứ sang class khác.

2. Refactoring catalog — kỹ thuật sửa

Fowler liệt kê > 70 refactoring. Top 10 thực dụng nhất:

2.1. Extract Method

// ❌ Long method
function printOrder(order: Order) {
  console.log('=== Order ===');
  console.log('ID:', order.id);
  console.log('Customer:', order.customer.name);

  let total = 0;
  for (const item of order.items) {
    total += item.price * item.qty;
  }
  console.log('Total:', total);
}

// ✓ Extract:
function printOrder(order: Order) {
  printHeader(order);
  console.log('Total:', calculateTotal(order));
}
function printHeader(o: Order) {
  console.log('=== Order ===');
  console.log('ID:', o.id);
  console.log('Customer:', o.customer.name);
}
function calculateTotal(o: Order): number {
  return o.items.reduce((s, i) => s + i.price * i.qty, 0);
}

2.2. Extract Variable

// ❌
if (order.customer.country === 'VN' && order.total > 1_000_000 && order.items.length > 5) { ... }

// ✓
const isVnCustomer = order.customer.country === 'VN';
const isLargeOrder = order.total > 1_000_000;
const hasManyItems = order.items.length > 5;
if (isVnCustomer && isLargeOrder && hasManyItems) { ... }

2.3. Inline Method/Variable

Ngược của extract — nếu helper không add value (chỉ rename), inline lại.

2.4. Extract Class

Class quá lớn → tách field/method liên quan ra class riêng (đã thấy ở SRP refactor Ch2).

2.5. Move Method/Field

Feature Envy → move method sang class mà nó "đang ghen".

// ❌ Feature Envy
class Order {
  customer: Customer;
  isVipDiscount(): boolean {
    return this.customer.totalSpent() > 10_000_000 && this.customer.yearsActive() > 3;
  }
}

// ✓ Method thuộc về Customer
class Customer {
  totalSpent(): number { /* ... */ }
  yearsActive(): number { /* ... */ }
  isVip(): boolean {
    return this.totalSpent() > 10_000_000 && this.yearsActive() > 3;
  }
}
class Order {
  isVipDiscount() { return this.customer.isVip(); }
}

2.6. Replace Conditional with Polymorphism

Switch theo type → polymorphism (Strategy/State/inheritance).

// ❌
function pay(method: string, amount: number) {
  switch (method) {
    case 'card':   chargeCard(amount);
    case 'paypal': chargePaypal(amount);
    case 'crypto': chargeCrypto(amount);
  }
}

// ✓ Strategy
interface PaymentMethod { pay(amount: number): void; }
class CardPayment implements PaymentMethod { pay(amount: number) {} }
class PaypalPayment implements PaymentMethod { pay(amount: number) {} }
class CryptoPayment implements PaymentMethod { pay(amount: number) {} }

function pay(method: PaymentMethod, amount: number) {
  method.pay(amount);
}

2.7. Replace Magic Number with Named Constant

// ❌
if (employee.workedYears > 5) salary *= 1.15;

// ✓
const SENIORITY_THRESHOLD_YEARS = 5;
const SENIORITY_BONUS = 1.15;
if (employee.workedYears > SENIORITY_THRESHOLD_YEARS) salary *= SENIORITY_BONUS;

2.8. Introduce Parameter Object

Long parameter list → gom thành object.

// ❌
search(query, page, size, sortBy, order, filters, language, region) { }

// ✓
type SearchOptions = { query: string; page: number; size: number; sortBy: string; order: 'asc'|'desc'; filters: Filter[]; language: string; region: string; };
search(options: SearchOptions) { }

2.9. Replace Type Code with Subclasses (or Strategy)

// ❌
class Employee {
  type: 'engineer' | 'manager' | 'salesperson';
  payAmount(): number {
    if (this.type === 'engineer') return baseSalary;
    if (this.type === 'manager') return baseSalary + bonus;
    if (this.type === 'salesperson') return baseSalary + commission;
  }
}

// ✓ Subclass:
abstract class Employee { abstract payAmount(): number; }
class Engineer extends Employee { payAmount() { return baseSalary; } }
class Manager extends Employee { payAmount() { return baseSalary + bonus; } }

2.10. Encapsulate Field

Public field → private + getter/setter (hoặc method ý nghĩa hơn).

2.11. Extract Interface

Khi cần multiple implementation hoặc mock cho test.

3. Anti-patterns nổi tiếng

3.1. God Object / God Class

1 class biết và làm tất cả: 5000 dòng, 100 method, 50 field. Vi phạm SRP nặng. Sửa: tách theo concern.

3.2. Spaghetti Code

Logic nhảy linh tinh, GOTO, callback chained, không có cấu trúc. Sửa: refactor thành module + function thuần.

3.3. Lava Flow

Code chết / code "không ai biết để làm gì" tích lũy theo thời gian. Không ai dám xóa vì sợ break. Sửa: có test → mạnh dạn xóa.

3.4. Golden Hammer

"Khi bạn có búa, mọi vấn đề là cái đinh." Áp dụng 1 pattern/tool cho mọi thứ. Vd: dùng Singleton khắp nơi, dùng MongoDB cho mọi thứ.

3.5. Magic Numbers / Strings

if (status === 5), if (role === 'a'). Đặt tên const hoặc enum.

3.6. Yo-yo Problem

Hierarchy quá sâu, đọc 1 method phải nhảy lên xuống 5 cấp parent. Hậu quả của over-inheritance.

3.7. Boat Anchor

Code/feature thêm "vì có thể cần" nhưng không bao giờ dùng. YAGNI nói: xóa.

3.8. Premature Optimization

Tối ưu code chưa profile chỉ rõ là chậm. Donald Knuth: "Premature optimization is the root of all evil."

3.9. Copy-Paste Programming

3 chỗ giống nhau trở lên → DRY. Nhưng cẩn thận: không phải mọi giống nhau đều cùng 1 lý do thay đổi.

3.10. Cargo Cult Programming

Áp dụng pattern/practice mà không hiểu vì sao. "Vì big tech làm vậy". Mỗi quyết định phải có lý do cụ thể với context của bạn.

4. Case study refactor — Order Total Calculation

4.1. Code "trước"

function calculateOrderTotal(order: any) {
  let total = 0;
  for (let i = 0; i < order.items.length; i++) {
    let item = order.items[i];
    let lineTotal = item.price * item.qty;

    // Discount theo category
    if (item.category === 'electronics' && item.qty >= 3) {
      lineTotal = lineTotal * 0.95;
    }
    if (item.category === 'food' && item.expiry < new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)) {
      lineTotal = lineTotal * 0.7;
    }
    total += lineTotal;
  }

  // VIP discount
  if (order.customer.totalSpent > 10000000 && order.customer.yearsActive > 3) {
    total = total * 0.9;
  }

  // Holiday
  let now = new Date();
  if (now.getMonth() === 11 && now.getDate() >= 20) {
    total = total - 50000;
  }

  // Shipping
  if (order.customer.country === 'VN') {
    if (total > 500000) total += 0;
    else total += 30000;
  } else {
    total += 200000;
  }

  if (total < 0) total = 0;
  return total;
}

Vấn đề:

  • Long method, dày đặc magic numbers/dates.
  • Mọi rule mới phải sửa hàm này (vi phạm OCP).
  • Khó test từng rule riêng.
  • Logic VIP, holiday, shipping trộn lẫn.
  • Không có Value Object — Money là number.

4.2. Refactor

// Step 1: Value Object
class Money {
  constructor(public readonly amount: number, public readonly currency: string = 'VND') {}
  add(other: Money) { return new Money(this.amount + other.amount, this.currency); }
  subtract(other: Money) { return new Money(Math.max(0, this.amount - other.amount), this.currency); }
  multiply(factor: number) { return new Money(this.amount * factor, this.currency); }
  static zero() { return new Money(0); }
}

// Step 2: Strategy cho rule
interface PricingRule {
  apply(order: Order, current: Money): Money;
}

class CategoryDiscountRule implements PricingRule {
  apply(order: Order, current: Money): Money {
    let total = Money.zero();
    for (const item of order.items) {
      let line = new Money(item.price).multiply(item.qty);
      if (item.category === 'electronics' && item.qty >= 3) {
        line = line.multiply(0.95);
      }
      if (item.category === 'food' && item.willExpireWithin(7)) {
        line = line.multiply(0.70);
      }
      total = total.add(line);
    }
    return total;
  }
}

class VipDiscountRule implements PricingRule {
  static readonly MIN_SPENT = 10_000_000;
  static readonly MIN_YEARS = 3;
  static readonly DISCOUNT = 0.9;

  apply(order: Order, current: Money): Money {
    if (order.customer.totalSpent > VipDiscountRule.MIN_SPENT &&
        order.customer.yearsActive > VipDiscountRule.MIN_YEARS) {
      return current.multiply(VipDiscountRule.DISCOUNT);
    }
    return current;
  }
}

class HolidayPromoRule implements PricingRule {
  static readonly PROMO_AMOUNT = new Money(50_000);

  apply(order: Order, current: Money): Money {
    return this.isHoliday() ? current.subtract(HolidayPromoRule.PROMO_AMOUNT) : current;
  }
  private isHoliday(): boolean {
    const now = new Date();
    return now.getMonth() === 11 && now.getDate() >= 20;
  }
}

class ShippingRule implements PricingRule {
  static readonly DOMESTIC = new Money(30_000);
  static readonly INTERNATIONAL = new Money(200_000);
  static readonly FREE_THRESHOLD = new Money(500_000);

  apply(order: Order, current: Money): Money {
    if (order.customer.country !== 'VN') {
      return current.add(ShippingRule.INTERNATIONAL);
    }
    if (current.amount > ShippingRule.FREE_THRESHOLD.amount) return current;
    return current.add(ShippingRule.DOMESTIC);
  }
}

// Step 3: Pricing engine
class PricingEngine {
  constructor(private rules: PricingRule[]) {}

  calculate(order: Order): Money {
    return this.rules.reduce((current, rule) => rule.apply(order, current), Money.zero());
  }
}

// Wire:
const engine = new PricingEngine([
  new CategoryDiscountRule(),
  new VipDiscountRule(),
  new HolidayPromoRule(),
  new ShippingRule(),
]);
const total = engine.calculate(order);

4.3. So sánh

  • Mỗi rule test riêng được.
  • Thêm rule mới — chỉ implement PricingRule, inject vào engine. OCP.
  • Magic numbers thành named constant trong class.
  • Money là Value Object — không thể có total âm.
  • Tổng dòng tăng (~80 dòng vs 30) nhưng maintainability tốt hơn nhiều.
Lưu ý: KHÔNG luôn refactor Code 30 dòng chỉ chạy 1 lần và không thay đổi nữa — không cần refactor. Refactor cho code có nhiều người đọc/sửa, lifetime dài, và rule thay đổi thường xuyên.

5. Kiến trúc Layered (3-tier / N-tier)

Pattern kiến trúc cổ điển. Tách app thành các layer chồng nhau, mỗi layer chỉ phụ thuộc layer dưới.

┌────────────────────────────────────┐ │ Presentation Layer │ Controllers, UI, View │ - HTTP routes │ │ - Request/response DTOs │ ├────────────────────────────────────┤ │ Application Layer (Service) │ Use cases, orchestration │ - OrderService.checkout() │ │ - Coordinator giữa các domain object│ ├────────────────────────────────────┤ │ Domain Layer │ Business logic, entity │ - Order, Customer, Money │ │ - PricingEngine, validators │ ├────────────────────────────────────┤ │ Infrastructure Layer │ DB, HTTP client, file │ - PostgresOrderRepository │ │ - SendgridSender │ └────────────────────────────────────┘

Quy tắc: phụ thuộc đi xuống. Domain không import Infrastructure. Application không import Presentation.

5.1. Vấn đề với layered cổ điển

Domain phụ thuộc Infrastructure (qua import implementation cụ thể). Khi DB đổi, Domain phải sửa. Vi phạm DIP.

Hexagonal/Clean architecture giải quyết vấn đề này.

6. Hexagonal Architecture (Ports & Adapters)

Alistair Cockburn (2005): Domain ở giữa, mọi thứ khác (UI, DB, external service) ở ngoài, kết nối qua "port" (interface) + "adapter" (implementation).

┌──────── HTTP Adapter ──────┐ │ │ ▼ │ ┌───────────┐ │ │ │ │ UI ────► │ DOMAIN │ ◄── port: NotifSender ─┤ │ (core) │ (interface) │ │ │ │ └─────┬─────┘ │ │ │ port: OrderRepo │ (interface) │ │ │ ▼ │ ┌───────────┐ │ │ Postgres │ ┌── Sendgrid ─────┘ │ Adapter │ │ Adapter └───────────┘ └──────────

Domain chỉ định nghĩa port (interface) theo nhu cầu. Adapter là implementation cụ thể (DB driver, HTTP client). Domain không biết Postgres hay Sendgrid.

6.1. Lợi ích

  • Test domain với in-memory adapter (không cần DB thật).
  • Đổi DB từ Postgres sang MongoDB — chỉ thay adapter.
  • Domain logic thuần khiết, không bị "ô nhiễm" bởi tech detail.

6.2. Clean Architecture (Bob Martin, 2012)

Mở rộng Hexagonal với 4 layer concentric:

  1. Entities (innermost) — domain objects, business rules.
  2. Use Cases — application-specific logic.
  3. Interface Adapters — controllers, gateways, presenters.
  4. Frameworks & Drivers (outermost) — DB, web framework, devices.

Dependency rule: phụ thuộc chỉ đi vào trong (outer phụ thuộc inner, không ngược).

7. DDD-lite — 5 Building Blocks

Eric Evans (2003) đặt nền cho Domain-Driven Design. 5 building block là vốn tối thiểu để áp dụng:

7.1. Entity

Object có identity ổn định qua thời gian (ID không đổi, attribute có thể đổi). Hai entity khác nhau dù mọi field giống nhau, nếu khác ID.

class Customer {
  constructor(
    public readonly id: CustomerId,    // identity
    private name: string,
    private email: Email,
  ) {}

  changeEmail(newEmail: Email) { this.email = newEmail; }   // attribute thay đổi
  changeName(newName: string) { this.name = newName; }
  // id KHÔNG đổi
}

7.2. Value Object

Object định danh qua giá trị thuộc tính, không có ID, immutable. (Đã đào sâu Ch3.)

class Money { /* immutable, equals theo amount + currency */ }
class Address { /* equals theo street+city+country */ }
class DateRange { /* equals theo start+end */ }

7.3. Aggregate

Cụm Entity + Value Object được protect bằng boundary transaction. Có 1 "Aggregate Root" — gateway duy nhất để truy cập.

// Aggregate root
class Order {
  private _items: OrderItem[] = [];

  constructor(
    public readonly id: OrderId,
    public readonly customerId: CustomerId,
  ) {}

  addItem(productId: ProductId, qty: number, price: Money) {
    if (this._items.length >= 100) throw new Error('max 100 items');
    this._items.push(new OrderItem(productId, qty, price));
  }

  removeItem(productId: ProductId) { /* ... */ }

  total(): Money {
    return this._items.reduce((s, i) => s.add(i.subtotal()), Money.zero());
  }

  // External code KHÔNG được tham chiếu OrderItem trực tiếp
  get items(): ReadonlyArray<OrderItem> { return this._items; }
}

// OrderItem là entity nội bộ aggregate, không tự đứng riêng
class OrderItem {
  constructor(public readonly productId: ProductId, public qty: number, public price: Money) {}
  subtotal() { return this.price.multiply(this.qty); }
}

Quy tắc:

  • 1 transaction sửa duy nhất 1 aggregate (consistency boundary).
  • Aggregate khác chỉ tham chiếu qua ID, không tham chiếu trực tiếp.
  • Aggregate giữ invariant nội bộ.

7.4. Repository

Tập trừu tượng để load/save aggregate. Ẩn database.

// Domain interface
interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  save(order: Order): Promise<void>;
  findByCustomer(customerId: CustomerId): Promise<Order[]>;
}

// Infrastructure implementation
class PostgresOrderRepository implements OrderRepository {
  constructor(private db: PgClient) {}

  async findById(id: OrderId) {
    const row = await this.db.query('SELECT * FROM orders WHERE id = $1', [id.value]);
    return row ? this.toDomain(row) : null;
  }

  async save(order: Order) { /* ... */ }
  async findByCustomer(customerId: CustomerId) { /* ... */ return []; }

  private toDomain(row: any): Order { /* mapping */ return new Order(...); }
}

Repository không phải DAO. DAO trả flat data; Repository trả full aggregate (với behavior).

7.5. Domain Service

Logic domain mà không thuộc về 1 Entity hoặc VO cụ thể. Ví dụ: chuyển tiền giữa 2 account.

class TransferService {
  async transfer(from: AccountId, to: AccountId, amount: Money) {
    const sourceAcc = await this.repo.findById(from);
    const destAcc = await this.repo.findById(to);
    if (!sourceAcc || !destAcc) throw new Error('not found');

    sourceAcc.withdraw(amount);   // entity behavior
    destAcc.deposit(amount);

    await this.repo.save(sourceAcc);
    await this.repo.save(destAcc);
    // Lưu ý: 2 aggregate trong 1 transaction → cần xử lý nhất quán
  }
}

Domain Service ≠ Application Service. Domain Service vẫn ở domain layer, dùng entity/VO. Application Service orchestrate use case (gọi repository, gửi email).

8. Bounded Context

Khái niệm cốt lõi nhất của DDD chiến lược (strategic). Một Bounded Context là phạm vi mà 1 model có nghĩa nhất quán. Cùng 1 từ "Customer" có thể có 2 model khác nhau ở 2 context:

  • Sales context: Customer = (id, name, email, totalSpent, vipStatus, leadScore).
  • Shipping context: Customer = (id, addresses, deliveryPreferences, signatureRequired).

2 context KHÔNG share 1 class Customer khổng lồ. Mỗi context có model riêng. Ánh xạ giữa context qua "Context Map" (bằng ID + ACL — anti-corruption layer).

8.1. Microservice = Bounded Context?

Thường đúng. Mỗi microservice nên 1 BC. Nhưng KHÔNG nhất thiết — 1 monolith có thể có nhiều BC tách bằng module/package.

8.2. Ubiquitous Language

Trong 1 BC, dev và domain expert dùng cùng 1 ngôn ngữ. Code dùng đúng từ business: order.cancel(), không order.update(2).

9. Khi nào dùng DDD?

DDD KHÔNG phải cho mọi project.

9.1. Phù hợp khi

  • Domain phức tạp, business rule nhiều.
  • Project lifetime dài (3+ năm).
  • Có domain expert làm việc cùng dev.
  • Team đủ kinh nghiệm.

9.2. KHÔNG phù hợp

  • CRUD đơn giản (TODO list, blog).
  • Prototype, MVP, hackathon.
  • Domain chủ yếu là technical (compiler, search engine).
  • Team chưa quen với pattern OOP cơ bản.

Bắt đầu nhỏ: dùng Value Object + Rich Domain Model. Khi phức tạp đủ, thêm Aggregate boundaries. Khi team scale, tách Bounded Context.

Lời khuyên cuối Pattern, refactoring, DDD đều là công cụ. Khi nào dùng phụ thuộc problem, team, deadline. Học để có vốn từ vựng + nhận ra vấn đề. Đừng cố ép giải pháp lên problem — hãy hiểu problem trước.

10. Bài tập

  1. Tìm 5 code smell trong 1 file mã nguồn của project bạn từng làm. Đề xuất refactoring cụ thể cho từng cái.
  2. Refactor đoạn code dưới — áp dụng Replace Conditional with Polymorphism + Extract Class:
    function calculatePay(employee: any) {
      if (employee.type === 'hourly') return employee.hours * employee.rate;
      if (employee.type === 'salary') return employee.salary;
      if (employee.type === 'commission') return employee.salary + employee.salesAmount * 0.1;
    }
  3. Thiết kế DDD cho domain "Online Library":
    • Identify Entity vs Value Object.
    • Identify Aggregate boundaries.
    • Define Repository interface.
    • Identify possible Bounded Context.
  4. Convert 1 anemic domain model trong project bạn sang Rich Domain Model. Đo: cùng tính năng tốn bao nhiêu dòng code service trước/sau?
  5. So sánh: Hexagonal Architecture vs Layered Architecture cổ điển. Khi nào dùng cái nào?
  6. Liệt kê 5 anti-pattern bạn đã gặp trong code thực tế (của bản thân hoặc đồng nghiệp). Mỗi cái nói cách bạn fix/sẽ fix.

11. Quiz

Quiz cuối Chương 8 (chương cuối)

"Feature Envy" code smell:

  • Class có quá nhiều feature
  • Method 1 class dùng nhiều thứ của class khác hơn của chính nó → có thể nên move method
  • Class thiếu feature
  • Constructor dài
Nếu method Order.isVipDiscount() chủ yếu gọi customer.totalSpent() + customer.yearsActive(), method này "envy" Customer — di chuyển method sang Customer (customer.isVip()) là hợp lý.

Replace Conditional with Polymorphism áp dụng khi:

  • Có quá nhiều generic
  • Class abstract
  • Cần Singleton
  • Có switch/if-else theo type — refactor sang Strategy/State/inheritance để OCP
switch theo type là dấu hiệu thiếu polymorphism. Thay if(type==='card') chargeCard()... bằng method.pay(amount) với strategy. Thêm payment method mới = thêm class, không sửa code cũ.

"God Object" anti-pattern:

  • 1 class biết và làm tất cả — vi phạm SRP nặng
  • Tốt vì tập trung logic
  • Chỉ tồn tại trong Java
  • Bắt buộc cho Singleton
Class 5000 dòng, 100 method, biết về mọi thứ. Vi phạm SRP, khó test, khó maintain. Sửa: tách theo concern (Extract Class). Phổ biến trong code legacy / monolith cũ.

Hexagonal Architecture (Ports & Adapters):

  • Layered cổ điển
  • DB ở giữa, app ở ngoài
  • Domain ở giữa, port (interface) định bởi domain, adapter (impl) ở ngoài cho UI/DB/external
  • Microservice mesh
Domain logic thuần khiết, không phụ thuộc tech. Port = interface domain định nghĩa theo nhu cầu. Adapter = implementation (Postgres, HTTP, Stripe). Test domain dễ — in-memory adapter. Đổi DB chỉ thay adapter.

Trong DDD, Aggregate Root là:

  • Class root của hierarchy
  • Entity duy nhất là gateway truy cập aggregate; client không tham chiếu entity bên trong trực tiếp
  • DB primary key
  • Singleton
Aggregate = cụm entity + VO bảo vệ invariant cùng nhau. Root là entity "gateway" — code ngoài chỉ tham chiếu root, không OrderItem riêng lẻ. 1 transaction sửa 1 aggregate. Aggregate khác tham chiếu qua ID.

Bounded Context giải quyết:

  • Performance
  • Memory
  • Database scaling
  • "Customer" có nhiều nghĩa khác nhau ở Sales vs Shipping — mỗi BC có model riêng, không share class khổng lồ
Cùng từ "Customer" có ngữ nghĩa khác ở Sales (totalSpent, vipStatus) vs Shipping (addresses, deliveryPref). 1 class Customer 50 field cho mọi BC = anti-pattern. BC tách model riêng, ánh xạ qua ID + Anti-Corruption Layer.

Donald Knuth: "Premature optimization is the root of all evil" có nghĩa:

  • Đừng tối ưu code chưa profile chỉ rõ là chậm
  • Không bao giờ tối ưu
  • Tối ưu cuối project
  • Microbenchmark mọi function
Tối ưu sớm thường: tăng phức tạp code, hi sinh readability cho gain không đáng kể, tối ưu sai chỗ. Code rõ trước, đo bằng profiler nếu chậm thật, mới optimize đúng chỗ. Thường 5% code chiếm 95% time.

Khi nào KHÔNG nên áp dụng DDD?

  • App với 1 user
  • Domain phức tạp
  • CRUD đơn giản, prototype/MVP, domain technical (compiler), team chưa quen OOP cơ bản
  • App enterprise
DDD cho domain phức tạp + lifetime dài + có domain expert. CRUD app, hackathon, technical core (search, compiler) — DDD overkill. Bắt đầu nhỏ với Value Object + Rich Domain, scale dần khi cần.

🎉 Hoàn thành toàn bộ 8 chương OOP & Design Patterns!

Bạn đã đi qua: 4 trụ cột OOP → 5 nguyên lý SOLID → Class design mastery → 23 GoF patterns → Anti-patterns + Refactoring + DDD-lite. Đây là vốn từ vựng thiết yếu để bạn nói chuyện thiết kế phần mềm với đồng nghiệp senior. Pattern không phải đích đến — chúng là công cụ để bạn nhận ra vấn đề và lựa chọn giải pháp đúng.

Về lộ trình · Đọc lại giáo trình tổng quan