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)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.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tetticket</groupId>
    <artifactId>tetticket.com</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>TetTicket Parent</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
        <relativePath/>
    </parent>

    <modules>
        <module>tetticket-application</module>
        <module>tetticket-domain</module>
        <module>tetticket-infrastructure</module>
        <module>tetticket-controller</module>
        <module>tetticket-start</module>
    </modules>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

Hướng dẫn:

  • Tạo thư mục TetTicket/tetticket.com và thêm file pom.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ần service (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.

package com.tetticket.domain.order;

import com.tetticket.domain.ticket.Ticket;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class Order {
    private UUID id;
    private List<OrderLine> orderLines = new ArrayList<>();
    private String customerId;
    private OrderStatus status;

    public Order(UUID id, String customerId) {
        this.id = id;
        this.customerId = customerId;
        this.status = OrderStatus.PENDING;
    }

    public void addTicket(Ticket ticket, int quantity) {
        OrderLine orderLine = new OrderLine(ticket, quantity);
        orderLines.add(orderLine);
    }

    public void confirm() {
        if (status != OrderStatus.PENDING) {
            throw new IllegalStateException("Order is not in PENDING state");
        }
        status = OrderStatus.CONFIRMED;
        publishEvent(new OrderConfirmedEvent(id));
    }

    private void publishEvent(OrderConfirmedEvent event) {
        // In a real system, use an Event Publisher
        System.out.println("Event published: " + event);
    }

    public UUID getId() {
        return id;
    }

    public List<OrderLine> getOrderLines() {
        return List.copyOf(orderLines);
    }

    public String getCustomerId() {
        return customerId;
    }

    public OrderStatus getStatus() {
        return status;
    }
}

enum OrderStatus {
    PENDING, CONFIRMED, CANCELLED
}

Giải thích:

  • Order là Entity (có ID) và Aggregate Root, tương tự một lớp trong thư mục entity cũ.

  • Phương thức addTicketconfirm chứa logic nghiệp vụ, tương tự logic bạn từng đặt trong service.

  • 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.

package com.tetticket.domain.order;

import com.tetticket.domain.ticket.Ticket;

public record OrderLine(Ticket ticket, int quantity) {
    public OrderLine {
        if (quantity <= 0) {
            throw new IllegalArgumentException("Quantity must be positive");
        }
    }
}

Giải thích:

  • OrderLine là Value Object, tương tự một lớp phụ trong entity 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é.

package com.tetticket.domain.ticket;

import java.util.UUID;

public class Ticket {
    private UUID id;
    private String eventName;
    private double price;

    public Ticket(UUID id, String eventName, double price) {
        this.id = id;
        this.eventName = eventName;
        this.price = price;
    }

    public UUID getId() {
        return id;
    }

    public String eventName() {
        return eventName;
    }

    public double price() {
        return price;
    }
}

Giải thích:

  • Ticket là Entity, tương tự một lớp trong entity 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.

package com.tetticket.domain.order;

import java.util.Optional;
import java.util.UUID;

public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(UUID id);
}

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.

package com.tetticket.domain.order;

import java.util.UUID;

public record OrderConfirmedEvent(UUID orderId) {
}

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.tetticket</groupId>
        <artifactId>tetticket.com</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>tetticket-domain</artifactId>
    <name>TetTicket Domain</name>
</project>

Hướng dẫn:

  • Tạo thư mục tetticket.com/tetticket-domain/src/main/java/com/tetticket/domain.

  • Tạo các package orderticket, 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

package com.tetticket.application;

import com.tetticket.domain.order.Order;
import com.tetticket.domain.order.OrderRepository;
import com.tetticket.domain.ticket.Ticket;
import java.util.UUID;
import org.springframework.stereotype.Service;

@Service
public class OrderApplicationService {
    private final OrderRepository orderRepository;

    public OrderApplicationService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public UUID createOrder(String customerId, UUID ticketId, int quantity) {
        Order order = new Order(UUID.randomUUID(), customerId);
        // In a real system, fetch Ticket from TicketRepository
        Ticket ticket = new Ticket(ticketId, "Sample Event", 50.0);
        order.addTicket(ticket, quantity);
        orderRepository.save(order);
        return order.getId();
    }

    public void confirmOrder(UUID orderId) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new IllegalArgumentException("Order not found"));
        order.confirm();
        orderRepository.save(order);
    }
}

Giải thích:

  • Tương tự lớp trong service cũ, nhưng chỉ điều phối: tạo Order, gọi addTicket, và lưu qua OrderRepository.

  • 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 trong tetticket-domain.

POM file for tetticket-application

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.tetticket</groupId>
        <artifactId>tetticket.com</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>tetticket-application</artifactId>
    <name>TetTicket Application</name>

    <dependencies>
        <dependency>
            <groupId>com.tetticket</groupId>
            <artifactId>tetticket-domain</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

Hướng dẫn:

  • Tạo thư mục tetticket.com/tetticket-application/src/main/java/com/tetticket/application.

  • Thêm file OrderApplicationService.javapom.xml.

  • Module này phụ thuộc vào tetticket-domain để sử dụng OrderOrderRepository.


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

package com.tetticket.infrastructure.repository;

import com.tetticket.domain.order.Order;
import com.tetticket.domain.order.OrderRepository;
import java.util.Optional;
import java.util.UUID;
import org.springframework.stereotype.Repository;

@Repository
public class JpaOrderRepository implements OrderRepository {
    @Override
    public void save(Order order) {
        // Simulate database save
        System.out.println("Saving order: " + order.getId());
    }

    @Override
    public Optional<Order> findById(UUID id) {
        // Simulate database fetch
        return Optional.empty();
    }
}

Giải thích:

  • Tương tự lớp trong repository cũ, nhưng triển khai giao diện OrderRepository 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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.tetticket</groupId>
        <artifactId>tetticket.com</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>tetticket-infrastructure</artifactId>
    <name>TetTicket Infrastructure</name>

    <dependencies>
        <dependency>
            <groupId>com.tetticket</groupId>
            <artifactId>tetticket-domain</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>
</project>

Hướng dẫn:

  • Tạo thư mục tetticket.com/tetticket-infrastructure/src/main/java/com/tetticket/infrastructure/repository.

  • Thêm file JpaOrderRepository.javapom.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 controllerdto 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

package com.tetticket.controller;

import com.tetticket.application.OrderApplicationService;
import java.util.UUID;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final OrderApplicationService orderApplicationService;

    public OrderController(OrderApplicationService orderApplicationService) {
        this.orderApplicationService = orderApplicationService;
    }

    @PostMapping
    public ResponseEntity<UUID> createOrder(@RequestBody CreateOrderRequest request) {
        UUID orderId = orderApplicationService.createOrder(
                request.customerId(), request.ticketId(), request.quantity());
        return ResponseEntity.ok(orderId);
    }

    @PostMapping("/{orderId}/confirm")
    public ResponseEntity<Void> confirmOrder(@PathVariable UUID orderId) {
        orderApplicationService.confirmOrder(orderId);
        return ResponseEntity.ok().build();
    }
}

record CreateOrderRequest(String customerId, UUID ticketId, int quantity) {
}

Giải thích:

  • Tương tự lớp trong controller cũ, nhưng chỉ gọi OrderApplicationService.

  • CreateOrderRequest là DTO, tương tự các lớp trong dto cũ, dùng để nhận dữ liệu từ API.

POM file for tetticket-controller

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.tetticket</groupId>
        <artifactId>tetticket.com</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>tetticket-controller</artifactId>
    <name>TetTicket Controller</name>

    <dependencies>
        <dependency>
            <groupId>com.tetticket</groupId>
            <artifactId>tetticket-application</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Hướng dẫn:

  • Tạo thư mục tetticket.com/tetticket-controller/src/main/java/com/tetticket/controller.

  • Thêm file OrderController.javapom.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

package com.tetticket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TetTicketApplication {
    public static void main(String[] args) {
        SpringApplication.run(TetTicketApplication.class, args);
    }
}

POM file for tetticket-start

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.tetticket</groupId>
        <artifactId>tetticket.com</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <artifactId>tetticket-start</artifactId>
    <name>TetTicket Start</name>

    <dependencies>
        <dependency>
            <groupId>com.tetticket</groupId>
            <artifactId>tetticket-controller</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.tetticket</groupId>
            <artifactId>tetticket-infrastructure</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Hướng dẫn:

  • Tạo thư mục tetticket.com/tetticket-start/src/main/java/com/tetticket.

  • Thêm file TetTicketApplication.javapom.xml.

  • Module này phụ thuộc vào tetticket-controllertetticket-infrastructure.


4. Cách chạy dự án

  1. Cài đặt môi trường:

    • Cài Java 21 và Maven.

    • Sử dụng IDE như IntelliJ IDEA hoặc Eclipse.

  2. Build dự án:

    cd TetTicket
    mvn clean install
  3. Chạy ứng dụng:

    cd tetticket.com/tetticket-start
    mvn spring-boot:run
  4. Kiểm tra API:

    • Tạo đơn hàng:

      curl -X POST http://localhost:8080/api/orders \
      -H "Content-Type: application/json" \
      -d '{"customerId":"cust123","ticketId":"550e8400-e29b-41d4-a716-446655440000","quantity":2}'
    • Xác nhận đơn hàng:

      curl -X POST http://localhost:8080/api/orders/550e8400-e29b-41d4-a716-446655440000/confirm

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:

  1. 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ục entity. 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.

  2. 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.

  3. 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 trong tetticket-domain để domain không phụ thuộc vào công nghệ, còn triển khai (JPA) nằm trong tetticket-infrastructure.

  4. Từ dto sang Controller/Application:

    • DTO vẫn được dùng, nhưng chỉ trong tetticket-controller (cho API) hoặc tetticket-application (cho ánh xạ dữ liệu). Domain không sử dụng DTO.

  5. 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:

  1. Tích hợp PostgreSQL:

    • Cập nhật JpaOrderRepository để sử dụng Spring Data JPA với PostgreSQL.

    • Thêm file application.yml trong tetticket-start để cấu hình datasource.

  2. Tích hợp Kafka:

    • Tạo một EventPublisher trong tetticket-infrastructure để gửi OrderConfirmedEvent tới Kafka.

    • Thêm dependency spring-kafka vào tetticket-infrastructure.

  3. 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.

  4. Container hóa với Docker:

    • Tạo file Dockerfile trong tetticket-start để đóng gói ứng dụng.

    • Thêm docker-compose.yml để chạy ứng dụng cùng PostgreSQL, Kafka, Redis.

Last updated