0

Tìm hiểu nhập môn về DDD(Domain-Driven Design) cho người mới và áp dụng trong E-Com Flask Project.

DDD(Domain-Driven Design)

Tác giả: Đoàn Phúc

Ngày: 6/3/2026

PHẦN I: LÝ THUYẾT - GIỚI THIỆU VỀ DDD

1. Khái niệm: DDD là gì?

Domain-Driven Design (DDD) là một phương pháp thiết kế phần mềm ở mức kiến trúc và mô hình hóa, lấy Nghiệp vụ (Domain) làm trung tâm. Thay vì bắt đầu bằng việc thiết kế Database hay UI, DDD yêu cầu chúng ta phải hiểu thấu đáo cách doanh nghiệp vận hành, sau đó mô hình hóa những quy tắc đó vào trong mã nguồn.

Cốt lõi: "Phần mềm phải là hình ảnh phản chiếu của nghiệp vụ thực tế." Nói đơn giản: DDD = thiết kế phần mềm xoay quanh logic nghiệp vụ (business domain)

2. DDD giải quyết vấn đề gì?

Trước khi có DDD, các dự án lớn thường rơi vào 3 vấn đề phổ biến sau:

Vấn đề Mô tả Hậu quả
Lệch pha ngôn ngữ (The Gap) Chuyên gia nghiệp vụ nói tiếng "Kinh doanh", Lập trình viên nói tiếng "Kỹ thuật" Hệ thống chạy đúng logic máy nhưng sai ý đồ thực tế
Mô hình nghèo nàn (Anemic Domain Model) Các Class chỉ chứa Getter/Setter, logic nghiệp vụ bị ném rải rác ở Service/Controller Code cực kỳ khó bảo trì khi phình to vì "dữ liệu một nơi, xử lý một nẻo"
Quả cầu bùn (Big Ball of Mud) Mọi thứ dính chùm vào nhau, ranh giới giữa các module quá lỏng lẻo Sửa một chỗ ở module A lại làm hỏng module B

DDD xử lý bằng cách:

Ép mọi người dùng chung một ngôn ngữ, thống nhất một ngôn ngữ chung. Chia nhỏ hệ thống thành các vùng độc lập (Bounded Context). Đóng gói logic nghiệp vụ vào đúng thực thể (Entity) của nó.

3. Các thành phần chính của DDD

DDD được chia làm 2 phần chính:

A. Thiết kế chiến lược (Strategic Design)

Strategic Design tập trung vào việc xác định và phân tích toàn bộ hệ thống ở mức cao — xác định các miền, ranh giới và cách chúng tương tác với nhau.

Thành phần Ý nghĩa & Chức năng
Ubiquitous Language Ngôn ngữ chung. Mọi từ ngữ dùng trong Code, Tài liệu và Giao tiếp phải thống nhất. Giúp loại bỏ sự hiểu lầm giữa kỹ thuật và nghiệp vụ.
Bounded Context Ngữ cảnh có ranh giới. Phân chia hệ thống thành các vùng độc lập. Trong mỗi vùng, các từ ngữ và mô hình chỉ có một ý nghĩa duy nhất.
Context Map Bản đồ quan hệ. Mô tả cách các Bounded Context tương tác với nhau (Ví dụ: Vùng Order cung cấp dữ liệu cho vùng Payment).
Domain Là lĩnh vực mà ứng dụng hướng tới, bao gồm tất cả quy trình nghiệp vụ, đối tượng và quy tắc liên quan.
Domain Model Biểu diễn của domain dưới dạng sơ đồ hoặc mô hình, giúp mô tả các khái niệm chính và cách chúng tương tác.

B. Thiết kế chiến thuật (Tactical Design)

Tactical Design tập trung vào các khái niệm cụ thể trong DDD như: Entities, Value Objects, Aggregates và Repositories. Đây là nơi mà các nhà phát triển thực hiện việc thiết kế chi tiết cho từng thành phần trong mô hình miền.

Tactical Design thường bao gồm việc sử dụng các mẫu thiết kế (design patterns) để giải quyết các vấn đề cụ thể, từ đó giúp cải thiện tính linh hoạt và khả năng mở rộng của hệ thống.

Các khái niệm:

Thành phần Ý nghĩa & Chức năng
Entity Đối tượng có định danh (ID) riêng biệt và xuyên suốt. Dùng để theo dõi trạng thái qua thời gian.
Ví dụ: Khách hàng — dù đổi tên hay địa chỉ, vẫn là khách hàng đó theo ID.
Value Object Đối tượng không cần ID, chỉ quan trọng giá trị. Tính Bất biến (Immutable) — muốn đổi thì tạo mới chứ không sửa cũ.
Ví dụ: Địa chỉ, Số tiền, Màu sắc.
Aggregate & Aggregate Root Gom nhóm Entity và Value Object liên quan. Mọi thay đổi bên trong cụm phải thông qua "Trưởng nhóm" (Root).
Ví dụ: Đơn hàng (Root) chứa nhiều Món hàng — không thêm Món hàng trực tiếp.
Domain Service Chứa logic nghiệp vụ không thuộc về bất kỳ Entity cụ thể nào.
Ví dụ: Dịch vụ tính phí vận chuyển dựa trên nhiều yếu tố.
Domain Event Ghi lại những sự kiện quan trọng đã xảy ra. Giúp các phần khác của hệ thống phản ứng lại.
Ví dụ: ThanhToanThanhCong → module GiaoHang bắt đầu làm việc.
Repository Cầu nối giữa domain và database. Tách biệt logic nghiệp vụ khỏi chi tiết lưu trữ. Có thể đổi DB mà không ảnh hưởng domain.
Factory Đóng gói logic khởi tạo phức tạp cho Entity hoặc Aggregate. Giữ cho constructor đơn giản và tập trung vào nghiệp vụ.

4. Lợi ích của việc áp dụng DDD

Lợi ích Mô tả thực tế
Cải thiện chất lượng sản phẩm Code phản ánh đúng nghiệp vụ → ít bug do hiểu sai yêu cầu, tăng sự hài lòng của khách hàng.
Dễ bảo trì & mở rộng Mỗi Bounded Context độc lập → thêm feature mới ít ảnh hưởng đến các phần còn lại.
Dễ test Domain logic không phụ thuộc framework (Flask, SQLAlchemy) → unit test thuần túy, nhanh và ổn định.
Chuẩn bị cho Microservices Mỗi Bounded Context có thể tách thành service độc lập khi cần scale.
Giảm rủi ro phát triển Phát hiện vấn đề sớm qua giao tiếp liên tục giữa kỹ thuật và nghiệp vụ nhờ Ubiquitous Language.

5. Khi nào NÊN và KHÔNG NÊN dùng DDD?

Tiêu chí NÊN dùng DDD KHÔNG NÊN dùng DDD
Quy mô dự án Hệ thống lớn, nhiều domain phức tạp Dự án nhỏ, CRUD đơn giản
Đội nhóm Team lớn, nhiều domain expert Team nhỏ 1-3 người
Thời gian Dự án dài hạn, cần maintainability cao Prototype, MVP cần ra nhanh
Business logic Logic nghiệp vụ phức tạp, thay đổi thường xuyên Logic đơn giản, ít thay đổi

PHẦN II: THỰC HÀNH - ÁP DỤNG DDD VÀO PROJECT E-COMMERCE

1. So sánh cấu trúc thư mục

Sự khác biệt căn bản giữa kiến trúc truyền thống (Layer-based) và kiến trúc DDD:

Kiến trúc cũ (Layer-based) Kiến trúc mới (Domain-based)
app/
├── models/ → DB models
├── services/ → Business logic
├── routes/ → APIs
├── utils/ → Helpers
ddd/
├── user_management/ → Domain: Quản lý user
├── order_management/ → Domain: Quản lý đơn hàng
├── payment/ → Domain: Thanh toán
├── product_catalog/ → Domain: Danh mục sản phẩm
└── shared/ → Dùng chung cho tất cả

Sự thay đổi cốt lõi: Từ chia theo kỹ thuật (models, services, routes) → Chia theo nghiệp vụ (user, order, payment). Mỗi domain là một "mini-application" độc lập.

2. Phân tích điểm yếu của kiến trúc cũ

2.1. Logic nghiệp vụ bị phân tán

Với kiến trúc layer-based, một nghiệp vụ như Order bị xé nhỏ ra nhiều folder:

models/order.py          ← chỉ chứa data

services/order_service.py ← business logic

routes/customer/order_routes.py ← API

schemas/order_schema.py  ← validation

workers/order_worker.py  ← background tasks

Khi muốn sửa logic Order phải mở 5-6 file khác nhau → Khó hiểu, tốn thời gian, dễ bỏ sót.

2.2. Coupling giữa các layer rất cao

Trong thực tế, Service layer thường import rất nhiều thứ:

order_service.py — phụ thuộc vào:

from db import session          ← database

from redis import client        ← cache

from kafka import producer      ← message queue

from models.order import Order  ← ORM model

from utils import helpers        ← utils

Hậu quả: Logic nghiệp vụ không độc lập. Khi đổi database, đổi framework, hoặc đổi message queue → Phải sửa rất nhiều chỗ.

2.3. Anemic Domain Model

Model chỉ là "data container" — không chứa behavior. Logic business bị rải rác:

Cách cũ: Model chỉ có data

class Order(db.Model):

id = Column(Integer)

status = Column(String)  ← chỉ là dữ liệu

Cách DDD: Model có behavior

class Order:

def confirm(self):       ← logic nằm trong entity

if self.status != 'pending':

raise InvalidStatusError()

self.status = 'confirmed'

self.raise_event(OrderConfirmedEvent(self.id))

2.4. Service layer trở thành "God Object"

File service phình to, chứa hàng trăm đến 1000+ dòng — khó đọc, khó test, logic bị trộn lẫn. DDD tách nhỏ thành use cases, domain services, và event handlers.

3. Kiến trúc trong DDD

DDD chia mỗi domain thành 3 layer chính với nguyên tắc phụ thuộc một chiều:

Layer Mục đích Chứa gì Phụ thuộc vào
Domain Chứa logic nghiệp vụ cốt lõi Entity, Value Object, Domain Service, Domain Event, Repository Interface Không phụ thuộc gì — đây là trái tim của DDD
Application Điều phối các use case Command, Query, Use Case, Handler, DTO Domain Layer
Infrastructure Kết nối hệ thống bên ngoài SQLAlchemy ORM, Kafka, Redis, REST API Application + Domain

Luồng xử lý request:

                                                           HTTP Request

                                                                                  ↓

                                                    Flask Route (Infrastructure/API)

                                                                                  ↓

                                                         Command Handler (Application)

                                                                                  ↓

                                                 Domain Entity / Domain Service (Domain)

                                                                                  ↓ 

                            Repository Interface (Domain) ← implement bởi Infrastructure

                                                                                  ↓

                                                             Database (Infrastructure)

4. Chi tiết từng Domain

📁 shared — Code dùng chung

File Tác Dụng
base_entity.py Base class cho tất cả entities (định nghĩa ID, timestamps, domain events)
value_object.py Base class cho value objects (immutable, no ID, equality by value)
event_dispatcher.py Gửi và lắng nghe sự kiện giữa các domain (Kafka/in-memory)
repository.py Interface chuẩn cho tất cả repositories (generic CRUD)

📁 user_management — Domain: Quản lý người dùng

Folder/File Layer Vai Trò
domain/entities/user.py Domain User entity với 7 properties, 10 business methods
domain/value_objects/ Domain Validate: Email, Password, PhoneNumber, Role
domain/repositories/ Domain Interface: UserRepositoryInterface (abstract)
domain/events.py Domain UserCreatedEvent, UserPasswordChangedEvent, ...
application/commands/ Application RegisterUserCommand, ChangePasswordCommand
application/handlers/ Application Xử lý commands → gọi domain → lưu Repo
infrastructure/api/user_routes.py Infrastructure 5 Flask endpoints
infrastructure/persistence/ Infrastructure SQLAlchemy ORM model + Repository implement

Ví dụ luồng user register:

  • API nhận request → user_routes.py
  • Gọi RegisterUserCommandHandler → application/
  • Handler tạo User entity → user.py (với Email/Password value objects validate)
  • Handler save vào DB → sqlalchemy_user_repository.py
  • Raise event UserCreatedEvent → event_dispatcher.py

📁 order_management — Domain: Quản lý đơn hàng

Folder Chi Tiết
domain/entities/ Order (Aggregate Root), OrderItem (child entity)
domain/value_objects/ OrderStatus, OrderItemStatus, Money
domain/services/ Business logic: kiểm tra tồn kho, tính tổng tiền
application/use_cases/ CreateOrderUseCase — orchestrate (reserve stock + kafka)
infrastructure/api/ 9 endpoints: create, get, confirm, ship, complete, cancel
infrastructure/services/ InventoryService (Redis atomic Lua script — tránh race condition)

📁 payment — Domain: Thanh toán

File Tác Dụng
entities/wallet.py Wallet aggregate (ID, balance, trạng thái kích hoạt)
repositories/wallet_repository.py Interface (abstract)
api/wallet_routes.py 5 endpoints: get balance, deposit, withdraw
persistence/sqlalchemy_wallet_model.py SQLAlchemy ORM model
persistence/sqlalchemy_wallet_repository.py Implement repository interface

📁 product_catalog — Domain: Danh mục sản phẩm

File Tác Dụng
entities/product.py Product aggregate (name, price, stock, trạng thái)
api/product_routes.py 6 endpoints: create, get, update, activate, deactivate
persistence/ SQLAlchemy models + SQLAlchemy repository implementation

5. Tổng kết lợi ích thực tế khi áp dụng DDD

Feature Trước (Layer-based) Sau (DDD)
Tìm code Mở 5-6 file ở nhiều folder khác nhau Tất cả logic Order nằm trong order_management/
Test Phải mock Flask, SQLAlchemy, Redis... Test domain thuần, không cần framework
Đổi database Sửa service, model, nhiều chỗ Chỉ implement lại repository
Thêm feature Rủi ro ảnh hưởng nhiều module Chỉ thêm trong domain liên quan
Scale to microservices Refactor lớn, rủi ro cao Tách từng Bounded Context thành service riêng
Onboarding member mới Phải hiểu toàn bộ codebase Chỉ cần hiểu domain mình phụ trách

KẾT LUẬN

DDD không phải là một framework hay thư viện — đó là một triết lý thiết kế phần mềm. Nó đòi hỏi đầu tư ban đầu nhiều hơn, nhưng mang lại hệ thống dễ hiểu, dễ bảo trì và dễ mở rộng về lâu dài.

Qua việc áp dụng DDD vào project E-Commerce, có thể thấy rõ:

  • Code phản ánh nghiệp vụ thực tế — developer và domain expert nói cùng ngôn ngữ
  • Mỗi Bounded Context (user, order, payment, product) độc lập, dễ phát triển song song
  • Domain layer thuần túy — không phụ thuộc framework, dễ test và dễ thay thế công nghệ
  • Sẵn sàng cho microservices — mỗi domain có thể tách ra khi cần scale

Lưu ý: DDD phù hợp với hệ thống có business logic phức tạp. Với các dự án nhỏ hay CRUD đơn giản, chi phí áp dụng DDD có thể cao hơn lợi ích mang lại.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí