Structure
1. Tổng quan cấu trúc dự án
Cấu trúc thư mục bạn đề xuất được tổ chức theo mô hình multi-module trong Maven, với mỗi module chịu trách nhiệm cho một tầng (layer) trong DDD:
tetticket-application: Chứa logic ứng dụng, bao gồm các Application Service để điều phối giữa domain và infrastructure.
tetticket-domain: Chứa mô hình miền (Entities, Value Objects, Aggregates, Domain Services, Domain Events), là trái tim của DDD.
tetticket-infrastructure: Chứa các chi tiết triển khai như Repository, cơ sở dữ liệu, hoặc tích hợp với bên thứ ba.
tetticket-controller: Chứa các REST Controller để xử lý yêu cầu HTTP.
tetticket-start: Module chính để khởi động ứng dụng Spring Boot.
tetticket.com: Module cha (parent) để quản lý các module con.
Để chuyển từ mô hình phân tầng truyền thống (với các thư mục như common
, config
, controller
, dto
, entity
, exception
, repository
, service
, util
) sang Domain-Driven Design (DDD) với cấu trúc bạn đã đề xuất cho dự án TetTicket, tôi sẽ hướng dẫn từng bước cách tổ chức và viết mã theo DDD, ánh xạ các khái niệm từ mô hình cũ sang DDD, và giải thích logic của từng thư mục trong cấu trúc mới. Tôi sẽ sử dụng dự án TetTicket (hệ thống đặt vé trực tuyến) làm ví dụ, tập trung vào chức năng quản lý đơn hàng (Order) để minh họa.
1. So sánh mô hình truyền thống và DDD
Trước tiên, hãy ánh xạ các thư mục trong mô hình truyền thống sang DDD để bạn hiểu cách chuyển đổi:
Thư mục truyền thống
Tương ứng trong DDD
Giải thích
common
tetticket-domain
(phần Value Objects hoặc utilities liên quan đến domain)
Các lớp tiện ích chung liên quan đến logic nghiệp vụ nên nằm trong tetticket-domain
. Các tiện ích kỹ thuật (như StringUtils) có thể nằm trong tetticket-infrastructure
.
config
tetticket-infrastructure
hoặc tetticket-start
Cấu hình Spring Boot (beans, database, v.v.) thuộc về tetticket-infrastructure
. Cấu hình ứng dụng tổng thể (như @SpringBootApplication
) nằm trong tetticket-start
.
controller
tetticket-controller
REST Controller vẫn giữ vai trò xử lý HTTP, không thay đổi nhiều, nhưng chỉ gọi đến Application Service trong tetticket-application
.
dto
tetticket-controller
hoặc tetticket-application
DTO (Data Transfer Objects) được dùng trong tetticket-controller
để nhận/gửi dữ liệu qua API, hoặc trong tetticket-application
để chuyển đổi dữ liệu giữa domain và API.
entity
tetticket-domain
Trong DDD, Entity và Value Object nằm trong tetticket-domain
. Entity có danh tính (ID), còn Value Object không. Cả hai đều chứa logic nghiệp vụ.
exception
tetticket-domain
(domain exceptions) hoặc tetticket-controller
(API exceptions)
Các ngoại lệ liên quan đến logic nghiệp vụ (như OrderNotFoundException
) thuộc tetticket-domain
. Ngoại lệ liên quan đến API (như BadRequestException
) thuộc tetticket-controller
.
repository
tetticket-domain
(interface) và tetticket-infrastructure
(triển khai)
Interface Repository nằm trong tetticket-domain
để định nghĩa hợp đồng truy xuất Aggregate. Triển khai cụ thể (như JPA) nằm trong tetticket-infrastructure
.
service
tetticket-application
(Application Service) hoặc tetticket-domain
(Domain Service)
Logic điều phối (orchestration) thuộc tetticket-application
(Application Service). Logic nghiệp vụ thuần túy thuộc tetticket-domain
(Domain Service).
util
tetticket-infrastructure
Các tiện ích kỹ thuật (như DateUtils, StringUtils) nằm trong tetticket-infrastructure
.
Khác biệt chính của DDD:
Tập trung vào domain: Mô hình truyền thống thường tổ chức mã theo kỹ thuật (controller, service, repository), trong khi DDD tổ chức theo nghiệp vụ (domain), với các thư mục như
tetticket-domain
chứa logic cốt lõi.Tách biệt tầng: DDD chia rõ ràng giữa domain (logic nghiệp vụ), application (điều phối), infrastructure (kỹ thuật), và controller (giao tiếp API).
Ubiquitous Language: Mã nguồn sử dụng ngôn ngữ chung, phản ánh đúng thuật ngữ nghiệp vụ (ví dụ:
Order
,Ticket
).
2. Cấu trúc thư mục DDD và logic từng thư mục
Dựa trên cấu trúc bạn cung cấp, tôi sẽ giải thích vai trò của từng thư mục và cách viết mã theo DDD:
tetticket.com: Module cha, quản lý các module con và dependency chung (Maven parent POM).
tetticket-domain: Chứa mô hình miền (Entities, Value Objects, Aggregates, Domain Services, Domain Events, Repository interfaces), là nơi định nghĩa logic nghiệp vụ thuần túy.
tetticket-application: Chứa Application Services, điều phối giữa domain và infrastructure, xử lý các lệnh (commands) từ client.
tetticket-infrastructure: Chứa các triển khai kỹ thuật như Repository (JPA), cấu hình database, hoặc tích hợp với Kafka, Redis.
tetticket-controller: Chứa REST Controllers và DTOs, xử lý yêu cầu HTTP và ánh xạ dữ liệu sang domain.
tetticket-start: Chứa lớp khởi động Spring Boot và các cấu hình tổng thể.
3. Hướng dẫn từng bước viết mã DDD
Tôi sẽ hướng dẫn cách xây dựng chức năng tạo đơn hàng (Order) và xác nhận đơn hàng trong hệ thống TetTicket, với các bước cụ thể cho từng thư mục. Dự án sử dụng Java 21, Spring Boot 3.3.4, và Maven multi-module. Tôi sẽ ánh xạ các khái niệm từ mô hình truyền thống để bạn dễ hiểu.
Bước 1: Thiết lập module cha (tetticket.com)
Mục đích: Định nghĩa cấu trúc multi-module và các dependency chung.
File: tetticket.com/pom.xml
Logic:
Đây là file Maven parent, khai báo các module con và các dependency chung như Spring Boot.
Tương tự thư mục
config
trong mô hình cũ, nhưng chỉ tập trung vào cấu hình build.
Hướng dẫn:
Tạo thư mục
TetTicket/tetticket.com
và thêm filepom.xml
như trên.File này giống như một "khuôn mẫu" để các module con kế thừa, tương tự cách bạn đặt các cấu hình chung trong thư mục
config
.
Bước 2: Xây dựng module Domain (tetticket-domain)
Mục đích: Định nghĩa mô hình miền (logic nghiệp vụ) với Entities, Value Objects, Aggregates, Repository interfaces, và Domain Events.
Logic:
Đây là trái tim của DDD, chứa logic nghiệp vụ thuần túy, không phụ thuộc vào công nghệ (Spring, JPA, v.v.).
Tương ứng với thư mục
entity
và một phầnservice
(logic nghiệp vụ) trong mô hình cũ.Các lớp ở đây sử dụng Ubiquitous Language, ví dụ:
Order
,Ticket
,OrderLine
.
Các file chính:
Order.java (Entity, Aggregate Root)
Lớp Order
là Aggregate Root, quản lý danh sách các OrderLine
và trạng thái đơn hàng.
Giải thích:
Order
là Entity (có ID) và Aggregate Root, tương tự một lớp trong thư mụcentity
cũ.Phương thức
addTicket
vàconfirm
chứa logic nghiệp vụ, tương tự logic bạn từng đặt trongservice
.publishEvent
mô phỏng phát sự kiện, có thể tích hợp Kafka (như trong CV của bạn) sau này.
OrderLine.java (Value Object)
Lớp OrderLine
là Value Object, không có danh tính riêng.
Giải thích:
OrderLine
là Value Object, tương tự một lớp phụ trongentity
cũ, nhưng không có ID.Sử dụng
record
để đảm bảo bất biến (immutable), thay vì lớp thông thường.
Ticket.java (Entity)
Lớp Ticket
là Entity, đại diện cho một vé.
Giải thích:
Ticket
là Entity, tương tự một lớp trongentity
cũ.Chứa các thuộc tính cơ bản, nhưng có thể thêm logic nghiệp vụ (ví dụ: kiểm tra giá vé) nếu cần.
OrderRepository.java (Repository Interface)
Giao diện OrderRepository
định nghĩa hợp đồng truy xuất Aggregate.
Giải thích:
Tương tự giao diện trong thư mục
repository
cũ, nhưng chỉ định nghĩa hợp đồng, không triển khai.Đặt trong
tetticket-domain
để đảm bảo domain không phụ thuộc vào công nghệ.
OrderConfirmedEvent.java (Domain Event)
Lớp OrderConfirmedEvent
đại diện cho sự kiện khi đơn hàng được xác nhận.
Giải thích:
Domain Event là mới trong DDD, không có trong mô hình cũ.
Dùng để thông báo trạng thái thay đổi, có thể tích hợp với Kafka sau này.
POM file for tetticket-domain
Hướng dẫn:
Tạo thư mục
tetticket.com/tetticket-domain/src/main/java/com/tetticket/domain
.Tạo các package
order
vàticket
, sau đó thêm các file trên.Module này không phụ thuộc vào Spring hoặc JPA, chỉ chứa logic nghiệp vụ thuần túy.
Bước 3: Xây dựng module Application (tetticket-application)
Mục đích: Chứa Application Services, điều phối giữa domain và infrastructure, xử lý các lệnh từ client.
Logic:
Tương ứng với thư mục
service
trong mô hình cũ, nhưng chỉ xử lý điều phối (orchestration), không chứa logic nghiệp vụ.Application Service gọi Repository để lưu/truy xuất dữ liệu và gọi các phương thức trên Aggregate.
File chính: OrderApplicationService.java
Giải thích:
Tương tự lớp trong
service
cũ, nhưng chỉ điều phối: tạoOrder
, gọiaddTicket
, và lưu quaOrderRepository
.Logic nghiệp vụ (như kiểm tra trạng thái) nằm trong
Order
(domain), không phải ở đây.IllegalArgumentException
tạm thời thay cho một custom exception trongtetticket-domain
.
POM file for tetticket-application
Hướng dẫn:
Tạo thư mục
tetticket.com/tetticket-application/src/main/java/com/tetticket/application
.Thêm file
OrderApplicationService.java
vàpom.xml
.Module này phụ thuộc vào
tetticket-domain
để sử dụngOrder
vàOrderRepository
.
Bước 4: Xây dựng module Infrastructure (tetticket-infrastructure)
Mục đích: Triển khai các chi tiết kỹ thuật như Repository (JPA), cấu hình database, hoặc tích hợp với Kafka, Redis.
Logic:
Tương ứng với thư mục
repository
(triển khai),config
, vàutil
trong mô hình cũ.Chứa các lớp sử dụng công nghệ cụ thể (JPA, Redis, v.v.), nhưng tách biệt khỏi domain.
File chính: JpaOrderRepository.java
Giải thích:
Tương tự lớp trong
repository
cũ, nhưng triển khai giao diệnOrderRepository
từtetticket-domain
.Hiện tại chỉ mô phỏng lưu/truy xuất, nhưng bạn có thể tích hợp JPA với PostgreSQL (như trong CV của bạn).
POM file for tetticket-infrastructure
Hướng dẫn:
Tạo thư mục
tetticket.com/tetticket-infrastructure/src/main/java/com/tetticket/infrastructure/repository
.Thêm file
JpaOrderRepository.java
vàpom.xml
.Module này phụ thuộc vào
tetticket-domain
và Spring Data JPA.
Bước 5: Xây dựng module Controller (tetticket-controller)
Mục đích: Xử lý yêu cầu HTTP, ánh xạ DTO sang domain, và gọi Application Service.
Logic:
Tương ứng với thư mục
controller
vàdto
trong mô hình cũ.Chỉ chứa logic giao tiếp API, không chứa logic nghiệp vụ.
File chính: OrderController.java
Giải thích:
Tương tự lớp trong
controller
cũ, nhưng chỉ gọiOrderApplicationService
.CreateOrderRequest
là DTO, tương tự các lớp trongdto
cũ, dùng để nhận dữ liệu từ API.
POM file for tetticket-controller
Hướng dẫn:
Tạo thư mục
tetticket.com/tetticket-controller/src/main/java/com/tetticket/controller
.Thêm file
OrderController.java
vàpom.xml
.Module này phụ thuộc vào
tetticket-application
.
Bước 6: Xây dựng module Start (tetticket-start)
Mục đích: Chứa lớp khởi động Spring Boot và các cấu hình tổng thể.
Logic:
Tương ứng với một phần của
config
trong mô hình cũ, nhưng chủ yếu để khởi động ứng dụng.Kéo tất cả các module lại để chạy.
File chính: TetTicketApplication.java
POM file for tetticket-start
Hướng dẫn:
Tạo thư mục
tetticket.com/tetticket-start/src/main/java/com/tetticket
.Thêm file
TetTicketApplication.java
vàpom.xml
.Module này phụ thuộc vào
tetticket-controller
vàtetticket-infrastructure
.
4. Cách chạy dự án
Cài đặt môi trường:
Cài Java 21 và Maven.
Sử dụng IDE như IntelliJ IDEA hoặc Eclipse.
Build dự án:
Chạy ứng dụng:
Kiểm tra API:
Tạo đơn hàng:
Xác nhận đơn hàng:
5. Chuyển đổi tư duy từ mô hình cũ sang DDD
Để giúp bạn quen với DDD, dưới đây là các mẹo dựa trên kinh nghiệm của bạn với mô hình truyền thống:
Từ
entity
sang Aggregate:Trong mô hình cũ, bạn đặt tất cả các lớp có
@Entity
vào thư mụcentity
. Trong DDD, bạn cần xác định Aggregate Root (nhưOrder
) và các thành phần liên quan (nhưOrderLine
). Chỉ Aggregate Root được truy xuất trực tiếp.
Từ
service
sang Application/Domain Service:Trong mô hình cũ, thư mục
service
chứa cả logic nghiệp vụ và điều phối. Trong DDD, tách biệt:Logic nghiệp vụ (như kiểm tra trạng thái đơn hàng) vào
tetticket-domain
(trong Entity hoặc Domain Service).Logic điều phối (như gọi Repository, ánh xạ dữ liệu) vào
tetticket-application
.
Từ
repository
sang Domain/Infrastructure:Trong mô hình cũ, bạn đặt cả interface và triển khai trong
repository
. Trong DDD, interface nằm trongtetticket-domain
để domain không phụ thuộc vào công nghệ, còn triển khai (JPA) nằm trongtetticket-infrastructure
.
Từ
dto
sang Controller/Application:DTO vẫn được dùng, nhưng chỉ trong
tetticket-controller
(cho API) hoặctetticket-application
(cho ánh xạ dữ liệu). Domain không sử dụng DTO.
Thêm Domain Events:
Mô hình cũ thường không có khái niệm Domain Event. Trong DDD, sử dụng Domain Events (như
OrderConfirmedEvent
) để thông báo thay đổi trạng thái, đặc biệt khi tích hợp Kafka (như trong CV của bạn).
6. Gợi ý mở rộng
Dựa trên CV của bạn (TetTicket sử dụng PostgreSQL, Kafka, Redis, Docker), bạn có thể mở rộng dự án:
Tích hợp PostgreSQL:
Cập nhật
JpaOrderRepository
để sử dụng Spring Data JPA với PostgreSQL.Thêm file
application.yml
trongtetticket-start
để cấu hình datasource.
Tích hợp Kafka:
Tạo một
EventPublisher
trongtetticket-infrastructure
để gửiOrderConfirmedEvent
tới Kafka.Thêm dependency
spring-kafka
vàotetticket-infrastructure
.
Tích hợp Redis:
Sử dụng Redis để cache thông tin vé trong
tetticket-infrastructure
.Thêm dependency
spring-boot-starter-data-redis
.
Container hóa với Docker:
Tạo file
Dockerfile
trongtetticket-start
để đóng gói ứng dụng.Thêm
docker-compose.yml
để chạy ứng dụng cùng PostgreSQL, Kafka, Redis.
Last updated