System Design & Design Patterns: Từ Ý tưởng đến Kiến trúc Vững chắc

Sau khi đã hiểu rõ yêu cầu của hệ thống qua Requirements Engineering, câu hỏi tiếp theo là: "Làm thế nào để xây dựng kiến trúc đáp ứng những yêu cầu đó?" Đây chính là lúc System Design phát huy vai trò.

System Design giống như việc vẽ bản thiết kế cho một tòa nhà trước khi xây. Bạn cần quyết định: mấy tầng? cầu thang để đâu? hệ thống điện nước chạy thế nào? Với phần mềm cũng vậy - bạn cần thiết kế kiến trúc trước khi viết dòng code đầu tiên.

High-Level Design (HLD) vs Low-Level Design (LLD)

High-Level Design (HLD)

HLD là bức tranh toàn cảnh - kiến trúc tổng thể của hệ thống.

HLD trả lời các câu hỏi:

  • Hệ thống có những components/modules nào?
  • Chúng giao tiếp với nhau như thế nào?
  • Dữ liệu flow ra sao?
  • Công nghệ stack nào được sử dụng?

Ví dụ HLD cho e-commerce app:

[Web Browser] 
    ↓ HTTPS
[Load Balancer]
    ↓
[API Gateway]
    ↓
┌─────────────┬──────────────┬──────────────┐
│   Product   │   Order      │   Payment    │
│   Service   │   Service    │   Service    │
└─────────────┴──────────────┴──────────────┘
    ↓               ↓              ↓
┌─────────────┬──────────────┬──────────────┐
│  Product DB │   Order DB   │  Payment DB  │
│  (MongoDB)  │  (PostgreSQL)│   (MySQL)    │
└─────────────┴──────────────┴──────────────┘

Output của HLD:

  • Architecture diagrams
  • Component diagrams
  • Data flow diagrams
  • Technology stack decisions
  • High-level API specifications

Low-Level Design (LLD)

LLD đi sâu vào chi tiết implementation của từng module.

LLD trả lời:

  • Class nào cần tạo?
  • Methods/functions có signature thế nào?
  • Database schema chi tiết ra sao?
  • Algorithms cụ thể để solve từng bài toán?

Ví dụ LLD cho Payment Service:

class PaymentProcessor:
    def __init__(self, gateway: PaymentGateway):
        self.gateway = gateway
        
    def process_payment(self, order: Order) -> PaymentResult:
        """
        Process payment for an order
        Returns: PaymentResult with status and transaction_id
        Raises: PaymentException if processing fails
        """
        # Validate order amount
        # Call payment gateway
        # Update order status
        # Send confirmation email
        pass

Output của LLD:

  • Class diagrams với methods
  • Sequence diagrams
  • Database schemas (chi tiết từng field, constraints, indexes)
  • Detailed API documentation
  • Pseudocode hoặc actual code

Khi nào dùng HLD, khi nào dùng LLD?

  • HLD trước: Để stakeholders và architects đồng ý về hướng đi tổng thể
  • LLD sau: Để developers có blueprint cụ thể để implement

Trong Agile, bạn có thể làm HLD cho toàn bộ hệ thống ngay từ đầu, nhưng LLD được làm iteratively cho từng sprint/feature.

Các Nguyên lý Thiết kế Cốt lõi

Trước khi học patterns, bạn cần hiểu các nguyên lý nền tảng. Đây là "kim chỉ nam" để đánh giá xem design có tốt không.

SOLID Principles

S - Single Responsibility Principle (SRP)

Mỗi class chỉ nên có một lý do duy nhất để thay đổi.

# ❌ BAD: Class làm quá nhiều việc
class User:
    def save_to_database(self): pass
    def send_email(self): pass
    def generate_report(self): pass

# ✅ GOOD: Mỗi class một trách nhiệm
class User:
    def __init__(self, name, email): pass

class UserRepository:
    def save(self, user): pass

class EmailService:
    def send(self, to, subject, body): pass

Tại sao quan trọng? Khi business logic thay đổi, bạn chỉ cần sửa đúng class chịu trách nhiệm, không ảnh hưởng các phần khác.

O - Open/Closed Principle

"Open for extension, closed for modification" - Có thể mở rộng tính năng mà không sửa code cũ.

# ❌ BAD: Mỗi lần thêm payment method phải sửa class
class PaymentProcessor:
    def process(self, method):
        if method == "credit_card":
            # process credit card
        elif method == "paypal":
            # process paypal
        # Cứ thêm elif mãi...

# ✅ GOOD: Sử dụng interface/abstract class
class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount): pass

class CreditCard(PaymentMethod):
    def pay(self, amount): pass

class PayPal(PaymentMethod):
    def pay(self, amount): pass

# Thêm method mới không cần sửa code cũ
class Crypto(PaymentMethod):
    def pay(self, amount): pass

L - Liskov Substitution Principle

Subclass phải có thể thay thế base class mà không làm hỏng chương trình.

# ❌ BAD: Square không thực sự là Rectangle
class Rectangle:
    def set_width(self, w): self.width = w
    def set_height(self, h): self.height = h
    def area(self): return self.width * self.height

class Square(Rectangle):
    def set_width(self, w): 
        self.width = self.height = w  # Breaks expectation!

# Test code expects: r.set_width(5), r.set_height(10) → area = 50
# But with Square: area = 100 (unexpected!)

I - Interface Segregation Principle

Không ép client implement methods không dùng đến.

# ❌ BAD: Printer chỉ cần print nhưng phải implement scan/fax
class Machine(ABC):
    @abstractmethod
    def print(self): pass
    @abstractmethod
    def scan(self): pass
    @abstractmethod
    def fax(self): pass

# ✅ GOOD: Tách thành nhiều interface nhỏ
class Printer(ABC):
    @abstractmethod
    def print(self): pass

class Scanner(ABC):
    @abstractmethod
    def scan(self): pass

# SimplePrinter chỉ implement Printer
# MultiFunctionDevice implement cả Printer, Scanner, Fax

D - Dependency Inversion Principle

High-level modules không phụ thuộc vào low-level modules. Cả hai phụ thuộc vào abstractions.

# ❌ BAD: OrderService phụ thuộc trực tiếp vào MySQLDatabase
class OrderService:
    def __init__(self):
        self.db = MySQLDatabase()  # Tight coupling!

# ✅ GOOD: Phụ thuộc vào interface
class OrderService:
    def __init__(self, db: Database):  # Inject dependency
        self.db = db

# Có thể swap MySQL → PostgreSQL → MongoDB mà không sửa OrderService

DRY - Don't Repeat Yourself

Mỗi piece of knowledge nên có một nguồn gốc duy nhất trong hệ thống.

# ❌ BAD: Logic validation bị duplicate
def create_user(email):
    if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
        raise ValueError("Invalid email")
    # create user...

def update_user(email):
    if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
        raise ValueError("Invalid email")
    # update user...

# ✅ GOOD: Extract thành function
def validate_email(email):
    if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
        raise ValueError("Invalid email")

def create_user(email):
    validate_email(email)
    # create user...

Nguy hiểm của vi phạm DRY: Khi business rule thay đổi (ví dụ email validation thêm rule mới), bạn phải sửa ở 10 chỗ → dễ quên và tạo bugs.

KISS - Keep It Simple, Stupid

Đơn giản luôn tốt hơn phức tạp. Đừng over-engineer.

# ❌ BAD: Over-engineered
class ComplexCalculator:
    def __init__(self):
        self.strategy_factory = CalculationStrategyFactory()
        self.result_transformer = ResultTransformer()
        
    def calculate(self, a, b):
        strategy = self.strategy_factory.create_strategy("addition")
        raw_result = strategy.execute(a, b)
        return self.result_transformer.transform(raw_result)

# ✅ GOOD: Simple and clear
def add(a, b):
    return a + b

Khi nào cần phức tạp? Khi requirements thực sự phức tạp và cần flexibility. Nhưng hãy bắt đầu simple, chỉ refactor khi cần.

YAGNI - You Aren't Gonna Need It

Đừng implement tính năng "có thể cần trong tương lai". Implement khi thực sự cần.

# ❌ BAD: Thêm features "phòng hờ"
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.phone = None  # "Có thể cần sau này"
        self.address = None
        self.social_security = None
        self.preferred_language = None
        # 20 fields nữa...

# ✅ GOOD: Chỉ thêm khi cần
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

Object-Oriented Design Patterns

Design Patterns là các giải pháp đã được chứng minh cho các vấn đề tái diễn trong thiết kế phần mềm. Gang of Four (GoF) phân loại thành 3 nhóm:

Creational Patterns - Khởi tạo đối tượng

1. Singleton Pattern

Đảm bảo một class chỉ có duy nhất một instance trong toàn bộ ứng dụng.

Khi nào dùng:

  • Database connection pool
  • Logger
  • Configuration manager
class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            # Initialize connection
        return cls._instance

# Dù gọi bao nhiêu lần cũng chỉ có 1 instance
db1 = DatabaseConnection()
db2 = DatabaseConnection()
assert db1 is db2  # True

Lưu ý: Singleton có thể gây khó khăn cho testing (khó mock) và vi phạm Single Responsibility. Dùng cẩn thận.

2. Factory Method Pattern

Delegate việc khởi tạo object cho subclass quyết định.

from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def process_payment(self, amount): pass

class StripeGateway(PaymentGateway):
    def process_payment(self, amount):
        print(f"Processing ${amount} via Stripe")

class PayPalGateway(PaymentGateway):
    def process_payment(self, amount):
        print(f"Processing ${amount} via PayPal")

class PaymentFactory:
    @staticmethod
    def create_gateway(gateway_type: str) -> PaymentGateway:
        if gateway_type == "stripe":
            return StripeGateway()
        elif gateway_type == "paypal":
            return PayPalGateway()
        else:
            raise ValueError(f"Unknown gateway: {gateway_type}")

# Client code không cần biết class cụ thể
gateway = PaymentFactory.create_gateway("stripe")
gateway.process_payment(100)

Lợi ích: Loose coupling - client code không phụ thuộc vào concrete classes.

3. Builder Pattern

Xây dựng object phức tạp từng bước, tránh constructor với quá nhiều parameters.

class Pizza:
    def __init__(self):
        self.dough = None
        self.sauce = None
        self.toppings = []

class PizzaBuilder:
    def __init__(self):
        self.pizza = Pizza()
    
    def set_dough(self, dough):
        self.pizza.dough = dough
        return self  # Return self để chain methods
    
    def set_sauce(self, sauce):
        self.pizza.sauce = sauce
        return self
    
    def add_topping(self, topping):
        self.pizza.toppings.append(topping)
        return self
    
    def build(self):
        return self.pizza

# Sử dụng - rất dễ đọc và flexible
pizza = (PizzaBuilder()
    .set_dough("thin crust")
    .set_sauce("tomato")
    .add_topping("mozzarella")
    .add_topping("basil")
    .build())

Khi nào dùng: Object có nhiều optional parameters hoặc quá trình khởi tạo phức tạp (nhiều bước).

Structural Patterns - Tổ chức cấu trúc

4. Adapter Pattern

"Chuyển đổi" interface này sang interface khác mà client mong đợi.

# Legacy code với interface cũ
class OldPaymentSystem:
    def make_payment(self, amount):
        print(f"Old system: paying {amount}")

# Interface mới mà app expect
class PaymentProcessor(ABC):
    @abstractmethod
    def process(self, amount): pass

# Adapter để bridge giữa old và new
class PaymentAdapter(PaymentProcessor):
    def __init__(self, old_system: OldPaymentSystem):
        self.old_system = old_system
    
    def process(self, amount):
        # Translate new interface -> old interface
        self.old_system.make_payment(amount)

# Client code dùng interface mới
processor = PaymentAdapter(OldPaymentSystem())
processor.process(100)  # Works!

Use case thực tế: Integrate với third-party API, legacy system, hoặc khi migrate từ library này sang library khác.

5. Decorator Pattern

Thêm tính năng mới cho object mà không sửa class gốc.

class Coffee:
    def cost(self):
        return 5

class MilkDecorator:
    def __init__(self, coffee):
        self._coffee = coffee
    
    def cost(self):
        return self._coffee.cost() + 2

class SugarDecorator:
    def __init__(self, coffee):
        self._coffee = coffee
    
    def cost(self):
        return self._coffee.cost() + 1

# Tạo coffee với milk và sugar
coffee = Coffee()
coffee_with_milk = MilkDecorator(coffee)
coffee_with_milk_and_sugar = SugarDecorator(coffee_with_milk)
print(coffee_with_milk_and_sugar.cost())  # 5 + 2 + 1 = 8

Khi nào dùng: Khi cần add functionality dynamically, tránh tạo explosion of subclasses (CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar...).

6. Facade Pattern

Cung cấp interface đơn giản cho một hệ thống phức tạp.

# Complex subsystems
class CPU:
    def freeze(self): pass
    def jump(self, position): pass
    def execute(self): pass

class Memory:
    def load(self, position, data): pass

class HardDrive:
    def read(self, lba, size): pass

# Facade đơn giản hóa
class ComputerFacade:
    def __init__(self):
        self.cpu = CPU()
        self.memory = Memory()
        self.hdd = HardDrive()
    
    def start(self):
        self.cpu.freeze()
        self.memory.load(0, self.hdd.read(0, 1024))
        self.cpu.jump(0)
        self.cpu.execute()

# Client chỉ cần gọi 1 method
computer = ComputerFacade()
computer.start()  # Đơn giản!

Lợi ích: Giảm complexity cho client, loose coupling với subsystems.

Behavioral Patterns - Hành vi và interaction

7. Strategy Pattern

Định nghĩa family of algorithms, encapsulate từng cái, và make chúng interchangeable.

class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data): pass

class QuickSort(SortStrategy):
    def sort(self, data):
        print("Sorting using QuickSort")
        # implementation...

class MergeSort(SortStrategy):
    def sort(self, data):
        print("Sorting using MergeSort")
        # implementation...

class Sorter:
    def __init__(self, strategy: SortStrategy):
        self.strategy = strategy
    
    def sort_data(self, data):
        self.strategy.sort(data)

# Client có thể swap strategy runtime
sorter = Sorter(QuickSort())
sorter.sort_data([3, 1, 2])

sorter.strategy = MergeSort()  # Change strategy
sorter.sort_data([3, 1, 2])

Khi nào dùng: Khi có nhiều cách để làm một việc và cần switch giữa chúng (pricing strategies, compression algorithms, routing algorithms).

8. Observer Pattern

Định nghĩa one-to-many dependency: khi object thay đổi state, tất cả dependents được notify tự động.

class Subject:
    def __init__(self):
        self._observers = []
    
    def attach(self, observer):
        self._observers.append(observer)
    
    def notify(self, event):
        for observer in self._observers:
            observer.update(event)

class EmailObserver:
    def update(self, event):
        print(f"Sending email about: {event}")

class SMSObserver:
    def update(self, event):
        print(f"Sending SMS about: {event}")

# Usage
subject = Subject()
subject.attach(EmailObserver())
subject.attach(SMSObserver())

subject.notify("New order placed")
# Output:
# Sending email about: New order placed
# Sending SMS about: New order placed

Use case: Event handling systems, UI updates (Model-View-Controller), notification systems.

9. Command Pattern

Encapsulate một request thành object, cho phép parameterize, queue, log requests.

class Command(ABC):
    @abstractmethod
    def execute(self): pass

class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()

class RemoteControl:
    def __init__(self):
        self.command = None
    
    def set_command(self, command):
        self.command = command
    
    def press_button(self):
        self.command.execute()

# Usage
light = Light()
remote = RemoteControl()
remote.set_command(LightOnCommand(light))
remote.press_button()  # Light turns on

Lợi ích: Decouple sender và receiver, dễ dàng implement undo/redo, macro commands.

Architectural Patterns

Ngoài design patterns cấp class/object, còn có patterns cấp architecture:

Monolithic Architecture

Toàn bộ application là một unit duy nhất.

Ưu điểm:

  • Đơn giản để develop và deploy
  • Dễ debug (everything in one place)
  • Performance tốt (no network calls giữa modules)

Nhược điểm:

  • Khó scale (phải scale toàn bộ app)
  • Deploy rủi ro cao (một bug có thể crash toàn bộ)
  • Khó maintain khi app lớn

Khi nào dùng: Startup, MVP, team nhỏ, app đơn giản.

Microservices Architecture

Chia app thành nhiều services nhỏ, độc lập, communicate qua network.

Ưu điểm:

  • Scale từng service riêng
  • Deploy độc lập (giảm rủi ro)
  • Technology flexibility (service A dùng Python, service B dùng Node.js)
  • Team autonomy

Nhược điểm:

  • Complexity cao (distributed system challenges)
  • Network latency
  • Data consistency khó (distributed transactions)
  • Cần infrastructure tốt (container orchestration, service mesh)

Khi nào dùng: App lớn, team nhiều, cần scale cao, organization muốn autonomy.

Event-Driven Architecture

Components communicate qua events thay vì direct calls.

Ưu điểm:

  • Loose coupling
  • Easy to add new consumers
  • Asynchronous processing

Nhược điểm:

  • Eventual consistency
  • Debugging khó hơn (event flow không rõ ràng)

Use case: Real-time systems, IoT, activity streams, notification systems.

Key Takeaways

  • HLD cho big picture, LLD cho implementation details
  • SOLID principles là nền tảng cho good design
  • DRY, KISS, YAGNI giúp code maintainable
  • Design Patterns: Creational (khởi tạo), Structural (cấu trúc), Behavioral (hành vi)
  • Singleton, Factory, Builder cho object creation
  • Adapter, Decorator, Facade cho structure
  • Strategy, Observer, Command cho behavior
  • Architectural patterns: Monolithic (simple), Microservices (scalable), Event-driven (reactive)

Trong bài tiếp theo, chúng ta sẽ khám phá Database Design & Management - cách thiết kế schema, chọn database phù hợp, và optimize performance.


Bài viết thuộc series "From Zero to AI Engineer" - Module 2: Design & Architecture