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.
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:
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:
LLD đi sâu vào chi tiết implementation của từng module.
LLD trả lời:
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:
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.
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.
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
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.
Đơ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.
Đừ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
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:
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:
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).
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.
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.
Ngoài design patterns cấp class/object, còn có patterns cấp architecture:
Toàn bộ application là một unit duy nhất.
Ưu điểm:
Nhược điểm:
Khi nào dùng: Startup, MVP, team nhỏ, app đơn giản.
Chia app thành nhiều services nhỏ, độc lập, communicate qua network.
Ưu điểm:
Nhược điểm:
Khi nào dùng: App lớn, team nhiều, cần scale cao, organization muốn autonomy.
Components communicate qua events thay vì direct calls.
Ưu điểm:
Nhược điểm:
Use case: Real-time systems, IoT, activity streams, notification systems.
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