Chương 12. Domain Là Gì?

Khi bắt đầu thiết kế một hệ thống, nhiều người mở database trước.

Họ hỏi:

  • Có những bảng nào?
  • Bảng user gồm những cột gì?
  • Bảng order nối với bảng product thế nào?
  • Foreign key đặt ở đâu?

Những câu hỏi này cần thiết, nhưng nếu bắt đầu từ database quá sớm, ta dễ thiết kế hệ thống theo dữ liệu thô thay vì theo nghiệp vụ.

Trước database, trước API, trước service, ta cần hiểu domain.

Domain là thế giới nghiệp vụ mà phần mềm đang phục vụ.

---

12.1. Ví dụ quán bánh: domain không phải cái bảng

Nếu xây phần mềm cho quán bánh, ta có thể nghĩ ngay đến bảng:

  • users
  • cakes
  • orders
  • payments
  • deliveries

Nhưng quán bánh trong đời thật không vận hành bằng bảng. Nó vận hành bằng nghiệp vụ:

  • Khách xem bánh.
  • Khách đặt bánh.
  • Quán xác nhận đơn.
  • Bếp làm bánh.
  • Khách thanh toán.
  • Shipper giao bánh.
  • Khách hủy đơn.
  • Quán hoàn tiền.
  • Quản lý xem doanh thu.

Đây mới là domain.

Database chỉ là một cách lưu lại sự thật của domain.

Nếu không hiểu domain, ta có thể tạo bảng đúng cú pháp nhưng sai nghiệp vụ.

Ví dụ:

  • Đơn hàng có thể hủy ở trạng thái nào?
  • Bánh đặt trước 3 ngày có khác bánh có sẵn không?
  • Thanh toán trước và thanh toán khi nhận hàng khác nhau thế nào?
  • Nếu bếp bắt đầu làm rồi, khách có được hủy không?
  • Nếu giao hàng thất bại, tiền xử lý ra sao?

Những câu hỏi này không nằm trong tên bảng. Chúng nằm trong domain.

---

12.2. Domain là gì?

Domain là lĩnh vực nghiệp vụ mà hệ thống cần hiểu và phục vụ.

Ví dụ:

  • Thương mại điện tử.
  • Giáo dục.
  • Ngân hàng.
  • Đặt lịch.
  • Giao đồ ăn.
  • Mạng xã hội.
  • Quản lý kho.
  • Y tế.
  • Logistics.
  • AI chấm bài.

Mỗi domain có:

  • Người tham gia.
  • Hành động.
  • Quy tắc.
  • Dữ liệu.
  • Trạng thái.
  • Ngoại lệ.
  • Ngôn ngữ riêng.

Ví dụ domain đặt lịch:

  • Người đặt.
  • Người cung cấp dịch vụ.
  • Khung giờ trống.
  • Đặt chỗ.
  • Hủy lịch.
  • Đổi lịch.
  • No-show.
  • Chính sách hoàn tiền.

Nếu không hiểu domain đặt lịch, ta dễ thiết kế sai, ví dụ cho hai người đặt cùng một khung giờ hoặc không xử lý timezone.

---

12.3. Domain khác database thế nào?

Database trả lời:

Dữ liệu được lưu như thế nào?

Domain trả lời:

Nghiệp vụ hoạt động như thế nào?

Ví dụ bảng orders có thể có cột:

  • id
  • user_id
  • status
  • total_amount
  • created_at

Nhưng domain order cần hiểu:

  • Khi nào order được tạo?
  • Khi nào order được xác nhận?
  • Khi nào được hủy?
  • Khi nào được hoàn tiền?
  • Ai được đổi trạng thái?
  • Trạng thái nào không được quay lại?
  • Nếu payment thành công nhưng hết hàng thì sao?

Database lưu trạng thái. Domain định nghĩa ý nghĩa và quy tắc của trạng thái đó.

Một database schema đẹp nhưng không phản ánh đúng domain vẫn là thiết kế sai.

---

12.4. Domain khác UI thế nào?

UI là cách người dùng tương tác với hệ thống.

Domain là logic đằng sau tương tác đó.

Ví dụ UI có nút:

Hủy đơn

Nhưng domain phải trả lời:

  • Đơn ở trạng thái nào được hủy?
  • Ai được hủy?
  • Có mất phí không?
  • Có hoàn tiền không?
  • Nếu shipper đã nhận hàng thì sao?
  • Nếu bếp đang làm thì sao?
  • Có gửi thông báo không?

UI chỉ là bề mặt. Domain là quy tắc thật.

Nếu chỉ thiết kế theo màn hình, hệ thống dễ bị rời rạc: mỗi màn hình tự xử lý logic riêng. Sau này nhiều nơi cùng có nút hủy đơn, logic hủy bị copy và khác nhau.

Thiết kế tốt: UI gọi một use case/domain service chung:

cancel_order(order_id, actor)

Logic hủy đơn nằm ở domain/application layer, không nằm rải rác trong từng màn hình.

---

12.5. Domain khác framework thế nào?

Framework là công cụ.

Domain là vấn đề cần giải quyết.

Ví dụ:

  • Django, FastAPI, Spring Boot, Rails là framework.
  • Đặt hàng, thanh toán, hoàn tiền, giao hàng là domain.

Framework giúp ta viết API, query database, validate input, auth, routing. Nhưng framework không tự hiểu nghiệp vụ.

Nếu domain bị đặt sai, framework tốt cũng không cứu được.

Một hệ thống có thể đổi framework nhưng domain vẫn giữ:

PlaceOrder
CancelOrder
RefundPayment
BookSlot
GradeSubmission

Đây là lý do ta nên tách business logic khỏi controller/view càng nhiều càng tốt. Domain không nên bị chôn hoàn toàn trong chi tiết framework.

---

12.6. Domain có ngôn ngữ riêng

Mỗi domain có từ vựng riêng.

Ví dụ thương mại điện tử:

  • Cart
  • Checkout
  • Order
  • Payment
  • Refund
  • Inventory
  • Shipment
  • Coupon

Ví dụ giáo dục:

  • Course
  • Lesson
  • Assignment
  • Submission
  • Rubric
  • Grade
  • Feedback
  • Enrollment

Ví dụ booking:

  • Availability
  • Slot
  • Reservation
  • Booking
  • Cancellation
  • No-show
  • Reschedule

Những từ này không chỉ là tên bảng. Chúng mang ý nghĩa nghiệp vụ.

Ví dụ reservationbooking có thể khác nhau:

  • Reservation: giữ chỗ tạm thời.
  • Booking: đặt chỗ đã xác nhận.

Nếu team dùng lẫn hai từ này, hệ thống sẽ dễ sai.

Domain-Driven Design gọi ngôn ngữ chung này là ubiquitous language: ngôn ngữ mà cả người làm sản phẩm, kỹ thuật và nghiệp vụ cùng hiểu.

Không cần làm DDD hàn lâm. Chỉ cần nhớ: đặt tên đúng giúp nghĩ đúng.

---

12.7. Cùng một từ có thể có nghĩa khác nhau ở domain khác nhau

Từ user nghe đơn giản, nhưng trong các hệ khác nhau có thể khác:

  • Người mua hàng.
  • Người bán.
  • Admin.
  • Giáo viên.
  • Học viên.
  • Shipper.
  • Nhân viên vận hành.

Từ course cũng có thể khác:

  • Trong catalog: sản phẩm được bán.
  • Trong learning: nội dung học.
  • Trong payment: một item có giá.
  • Trong analytics: một nguồn hành vi học tập.

Nếu ép mọi nghĩa vào một model duy nhất, model đó sẽ phình to và rối.

Đây là lý do sau này ta cần học bounded context: cùng một khái niệm có thể có hình dạng khác nhau trong ngữ cảnh khác nhau.

Ở chương này chỉ cần nhớ:

> Đừng tưởng một danh từ trong hệ thống chỉ có một nghĩa duy nhất.

---

12.8. Domain gồm người, hành động và quy tắc

Khi phân tích domain, đừng bắt đầu bằng bảng. Hãy bắt đầu bằng ba thứ:

Người tham gia

Ai dùng hệ thống?

Ví dụ:

  • Khách hàng.
  • Nhân viên.
  • Admin.
  • Giáo viên.
  • Học viên.
  • Người bán.
  • Shipper.
  • Đối tác.

Hành động

Họ làm gì?

Ví dụ:

  • Đăng ký.
  • Đặt hàng.
  • Thanh toán.
  • Hủy.
  • Đổi lịch.
  • Upload bài.
  • Chấm bài.
  • Gửi phản hồi.

Quy tắc

Hành động đó bị ràng buộc bởi gì?

Ví dụ:

  • Chỉ được hủy trước khi giao.
  • Chỉ giáo viên mới được sửa rubric.
  • Không được đặt trùng slot.
  • Refund chỉ được thực hiện sau payment thành công.
  • Một bài đã được giáo viên chấm thì AI không được ghi đè.

Nếu nắm được người, hành động, quy tắc, ta sẽ thiết kế hệ thống đúng hơn rất nhiều.

---

12.9. Domain có trạng thái

Nhiều thực thể trong domain có vòng đời.

Ví dụ order:

Draft -> Pending Payment -> Paid -> Preparing -> Delivering -> Completed

Hoặc:

Pending Payment -> Cancelled
Paid -> Refunded
Delivering -> Failed Delivery

Trạng thái không chỉ là một cột status. Nó là luật chuyển đổi.

Cần hỏi:

  • Có những trạng thái nào?
  • Ai được chuyển trạng thái?
  • Chuyển từ A sang B có hợp lệ không?
  • Có hành động phụ nào khi chuyển trạng thái không?
  • Trạng thái nào là cuối?
  • Có cần lưu lịch sử trạng thái không?

Ví dụ payment:

Created -> Processing -> Succeeded
Created -> Failed
Succeeded -> Refunded

Nếu không hiểu state machine, hệ thống có thể cho phép trạng thái vô lý:

Refunded -> Processing
Cancelled -> Paid
Completed -> Draft

Domain không chỉ là object. Domain là hành vi và vòng đời.

---

12.10. Domain có ngoại lệ

Nghiệp vụ thật luôn có ngoại lệ.

Ví dụ:

  • Khách trả tiền nhưng hệ thống không nhận webhook.
  • Shipper không giao được hàng.
  • Khách muốn hủy sau khi bếp đã làm.
  • Thanh toán thành công nhưng order hết hàng.
  • Giá thay đổi trong lúc khách checkout.
  • Giáo viên sửa điểm sau khi AI đã chấm.
  • File upload thành công nhưng xử lý thumbnail thất bại.

Nếu chỉ thiết kế "happy path", hệ thống sẽ vỡ khi gặp đời thật.

Khi phân tích domain, luôn hỏi:

  • Nếu bước này lỗi thì sao?
  • Có được retry không?
  • Có cần bù trừ không?
  • Có cần người vận hành xử lý thủ công không?
  • Có cần audit log không?
  • User cần thấy trạng thái gì?

Kiến trúc tốt không chỉ xử lý luồng đúng, mà còn xử lý luồng lệch.

---

12.11. Domain có mức độ quan trọng khác nhau

Không phải phần nào của hệ thống cũng quan trọng như nhau.

Ví dụ trong e-commerce:

  • Payment rất quan trọng.
  • Order rất quan trọng.
  • Inventory quan trọng.
  • Email notification quan trọng vừa.
  • Recommendation có thể lỗi mà hệ thống vẫn bán được.
  • Analytics có thể chậm vài phút.

Mức độ quan trọng ảnh hưởng kiến trúc:

  • Phần tiền bạc cần transaction, idempotency, audit.
  • Phần notification có thể async/retry.
  • Phần analytics có thể eventual consistency.
  • Phần recommendation có thể fallback.

Nếu không phân biệt mức độ quan trọng, ta dễ thiết kế quá nặng cho phần không quan trọng hoặc quá nhẹ cho phần sống còn.

Tư duy:

> Không phải domain nào cũng cần cùng mức độ consistency, reliability và realtime.

---

12.12. Core domain và supporting domain

Trong một sản phẩm, không phải domain nào cũng tạo lợi thế cạnh tranh.

Core domain

Đây là phần làm sản phẩm khác biệt.

Ví dụ:

  • Với mạng xã hội: news feed/ranking/social graph.
  • Với app gọi xe: matching tài xế và khách, ETA, pricing.
  • Với nền tảng học tập AI: đánh giá/chấm bài/cá nhân hóa học tập.
  • Với e-commerce lớn: search, recommendation, logistics, pricing.

Core domain đáng đầu tư kỹ.

Supporting domain

Đây là phần cần có nhưng không nhất thiết tạo khác biệt.

Ví dụ:

  • Gửi email.
  • Basic admin.
  • Basic billing.
  • Export CSV.
  • Đăng nhập bằng Google.

Supporting domain có thể dùng thư viện, service có sẵn, hoặc làm đơn giản trước.

Generic domain

Đây là phần rất phổ biến, nên mua/dùng managed service nếu phù hợp.

Ví dụ:

  • Auth provider.
  • Payment provider.
  • Email provider.
  • Object storage.
  • Monitoring SaaS.

Tư duy thực dụng:

> Tự xây sâu ở core domain. Dùng công cụ có sẵn cho phần không tạo khác biệt nếu nó đủ tốt.

---

12.13. Domain quyết định service boundary

Khi tách module/service, đừng tách theo cảm giác hoặc theo bảng.

Hãy hỏi:

  • Domain nào thay đổi cùng nhau?
  • Domain nào có rule riêng?
  • Domain nào có dữ liệu riêng?
  • Domain nào có team/owner riêng?
  • Domain nào có workload riêng?
  • Domain nào cần consistency riêng?

Ví dụ:

Không nên tách:

OrderService
OrderItemService
OrderStatusService

vì chúng có thể thuộc cùng domain ordering.

Có thể tách:

Ordering
Payment
Fulfillment
Notification

vì mỗi phần có capability rõ hơn.

Tách theo domain giúp service có ý nghĩa nghiệp vụ, không chỉ là CRUD wrapper cho bảng.

---

12.14. Cách khám phá domain

Để hiểu domain, hãy nói chuyện bằng use case.

Hỏi:

  • Người dùng chính là ai?
  • Họ muốn làm gì?
  • Hành động nào quan trọng nhất?
  • Trạng thái của đối tượng thay đổi thế nào?
  • Khi nào hành động bị từ chối?
  • Ai có quyền làm gì?
  • Điều gì không được sai?
  • Nếu lỗi thì xử lý sao?
  • Có quy trình thủ công hiện tại không?
  • Có thuật ngữ nội bộ nào không?

Ví dụ với đặt lịch:

  • Ai tạo lịch trống?
  • Ai được đặt?
  • Một slot giữ trong bao lâu?
  • Thanh toán trước hay sau?
  • Hủy có mất phí không?
  • Nếu giáo viên bận đột xuất thì sao?
  • Nếu học viên không tham gia thì sao?
  • Có đổi lịch không?

Những câu hỏi này dẫn đến thiết kế tốt hơn nhiều so với chỉ hỏi "cần bảng nào".

---

12.15. Từ domain đến model

Sau khi hiểu domain, ta mới thiết kế model/database/API.

Ví dụ từ domain order:

Hành động:

  • Tạo đơn.
  • Thanh toán.
  • Hủy.
  • Hoàn tiền.
  • Giao hàng.

Trạng thái:

  • Pending payment.
  • Paid.
  • Preparing.
  • Delivering.
  • Completed.
  • Cancelled.
  • Refunded.

Quy tắc:

  • Chỉ hủy trước khi preparing.
  • Paid mới được refund.
  • Completed không quay lại preparing.
  • Payment webhook có thể đến trễ.

Từ đó mới ra:

  • Bảng orders.
  • Bảng order_items.
  • Bảng payments.
  • Bảng order_status_history.
  • API cancel order.
  • Use case refund.
  • Event PaymentSucceeded.

Database xuất hiện sau khi domain rõ hơn.

---

12.16. Những lỗi tư duy phổ biến

Lỗi 1: Bắt đầu bằng bảng

Bảng quan trọng, nhưng nếu bắt đầu bằng bảng, ta dễ bỏ quên hành vi và quy tắc.

Lỗi 2: Nghĩ CRUD là đủ

Nhiều hệ thống không chỉ create/read/update/delete. Chúng có workflow, state, permission, audit, retry, exception.

Lỗi 3: Một model ôm quá nhiều nghĩa

User, Course, Order, Product có thể mang nhiều nghĩa trong nhiều context. Ép tất cả vào một model khổng lồ làm hệ thống rối.

Lỗi 4: Dùng thuật ngữ không thống nhất

Một nơi gọi booking, nơi khác gọi reservation, nơi khác gọi appointment nhưng cùng nghĩa hoặc khác nghĩa không rõ. Điều này gây lỗi rất lâu dài.

Lỗi 5: Bỏ qua ngoại lệ

Happy path dễ thiết kế. Ngoại lệ mới làm hệ thống production phức tạp.

Lỗi 6: Xem mọi domain quan trọng như nhau

Không phân biệt core/supporting/generic domain dẫn đến đầu tư sai chỗ.

---

12.17. Checklist hiểu domain

Trước khi thiết kế kiến trúc sâu, hãy trả lời:

  • Người tham gia chính là ai?
  • Họ làm những hành động nào?
  • Đối tượng chính trong domain là gì?
  • Những đối tượng đó có trạng thái nào?
  • Trạng thái chuyển đổi ra sao?
  • Quy tắc quan trọng nhất là gì?
  • Ai có quyền làm gì?
  • Điều gì không được sai?
  • Luồng ngoại lệ là gì?
  • Phần nào là core domain?
  • Phần nào có thể dùng công cụ có sẵn?
  • Thuật ngữ nào cần thống nhất?
  • Domain nào có thể là module/service riêng?

Nếu chưa trả lời được, đừng vội thiết kế microservices, database hay API chi tiết.

---

12.18. Kết luận của chương

Domain là thế giới nghiệp vụ mà hệ thống phục vụ.

Nó không phải database, không phải UI, không phải framework.

Domain gồm:

  • Người tham gia.
  • Hành động.
  • Quy tắc.
  • Trạng thái.
  • Ngoại lệ.
  • Ngôn ngữ.
  • Mức độ quan trọng.

Thiết kế hệ thống tốt bắt đầu từ hiểu domain. Database, API, service, queue, event đều nên xuất phát từ domain, không phải ngược lại.

Thông điệp quan trọng:

> Đừng hỏi "cần những bảng nào?" quá sớm. Hãy hỏi "nghiệp vụ này thật sự vận hành như thế nào?"

Khi hiểu domain, ta sẽ chia module tốt hơn, đặt tên tốt hơn, chọn boundary tốt hơn, và tránh được rất nhiều kiến trúc sai ngay từ gốc.