VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
Kiến trúc và chất lượng codeKiến trúc ứng dụng
Bài 17/17
6 phút

Tách nhiều module Maven

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

Package core và infrastructure của bài trước vẫn nằm chung một project: không gì ngăn core lỡ import JPA, và job chạy đêm muốn dùng OrderService thì phải chép code sang. Bài này đưa Core và Infrastructure thành module riêng, để shop-api và mọi app sau này dùng chung một bản code.

Khái niệm

📁 Maven multi-module: project cha có pom.xml với <packaging>pom</packaging>, liệt kê các thư mục module con trong <modules> để build tất cả cùng một lệnh.

📗 Module thư viện (library module): module không có method main, chỉ chứa class để module khác khai báo <dependency> tới.

Như bài JPA và repository, <dependency> trong pom.xml của module A cho A dùng class public của module B. Chiều dependency chính là chiều phụ thuộc của bài Clean Architecture.

Ví dụ

Trong thư mục chứa shop-api, tạo pom.xml của project cha, rồi chuyển mvnw, mvnw.cmd, .gitignore và thư mục .mvn từ shop-api ra đây:

<!-- pom.xml của thư mục cha -->
<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
  </parent>
  <groupId>com.shop</groupId>
  <artifactId>shop</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>
  <properties>
    <java.version>25</java.version>
  </properties>
  <modules>
    <module>shop-core</module>
    <module>shop-infrastructure</module>
    <module>shop-api</module>
  </modules>
</project>

Mỗi module con có pom.xml riêng, <parent> trỏ về project cha. Đây là shop-core/pom.xml:

<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.shop</groupId>
    <artifactId>shop</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>shop-core</artifactId>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
      <groupId>jakarta.persistence</groupId>
      <artifactId>jakarta.persistence-api</artifactId>
    </dependency>
  </dependencies>
</project>

shop-infrastructure/pom.xml viết cùng mẫu. Trong shop-api/pom.xml, thay <parent> bằng khối <parent> như trên, xoá <groupId>, <version>, <properties> vì đã nhận từ project cha. Module cần dùng module khác thì thêm dependency tới nó:

<dependency>
  <groupId>com.shop</groupId>
  <artifactId>shop-core</artifactId>
  <version>${project.version}</version>
</dependency>

Sau đó chuyển code:

Module Nhận những file
shop-core Product, Order, OrderLine, ProductStore, OrderService, EmailSender, CheckoutService
shop-infrastructure ProductRepository, OrderRepository, DbProductStore, ConsoleEmailSender, thư mục db/migration, dependency JPA, ojdbc17, Flyway; dependency tới shop-core
shop-api giữ controller, ShopApiApplication, application.properties; dependency tới shop-core và shop-infrastructure

Package giữ gốc com.shop.api nên Spring vẫn tìm thấy bean, entity và repository ở mọi module, như bài Tạo Web API đầu tiên. Từ thư mục cha, build và chạy server:

./mvnw install
./mvnw -pl shop-api spring-boot:run

install build ba module theo chiều dependency rồi lưu vào kho Maven trên máy. -pl shop-api chỉ chạy shop-api.

// shop-core
// Product.java
package com.shop.api.core;

import jakarta.persistence.*;

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name = "";
    private long price;
    private int stock;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) {
        this.name = name;
    }
    public long getPrice() { return price; }
    public void setPrice(long price) {
        this.price = price;
    }
    public int getStock() { return stock; }
    public void setStock(int stock) {
        this.stock = stock;
    }
}
  • File nằm trong shop-core/src/main/java/com/shop/api/core/. File nào ở module khác dùng Product thì thêm import com.shop.api.core.*;.
  • jakarta.persistence-api chỉ chứa annotation như @Entity, không có code kết nối database. Hibernate, Spring Data JPA và ojdbc17 nằm ở shop-infrastructure.
  • Sửa Product một lần, ./mvnw install là mọi module cùng nhận.
shop-api shop-core shop-infrastructure
Mọi module cùng trỏ về một lõi

Thử ngay

Thử phá quy tắc của bài trước: thêm file này vào shop-core rồi chạy ./mvnw -q compile -pl shop-core.

// shop-core
// Bad.java
package com.shop.api.core;

import org.springframework.data.jpa.repository.*;

public class Bad {
}

Đoán trước khi chạy: compile được hay báo lỗi?

Xem kết quả
[ERROR] COMPILATION ERROR :
[ERROR] .../core/Bad.java:[3,36] package
  org.springframework.data.jpa.repository does not exist

Lỗi. shop-core không có dependency Spring Data JPA, nên không thể lỡ tay dùng database trong lõi.

Compiler giờ kiểm tra hộ quy tắc "lõi không biết database". Xoá Bad.java.

Lỗi hay gặp

Quên public. Class không ghi gì chỉ dùng được trong chính package của nó. Module khác có import com.shop.api.core.* cũng không thấy, javac báo cannot find symbol ở mọi chỗ dùng Product.

// SAI — shop-core: module khác báo cannot find symbol
// Product.java
package com.shop.api.core;

class Product {
    private Long id;
}
// ĐÚNG — shop-core: public để module khác dùng được
// Product.java
package com.shop.api.core;

public class Product {
    private Long id;

    public Long getId() { return id; }
}

Tóm tắt

  • Project cha gom các module trong <modules>. Module thư viện chứa code dùng chung.
  • <dependency> tới module B cho module A dùng class public của B.
  • Core và Infrastructure thành module riêng, API và mọi app sau này cùng khai báo dependency tới.
  • Lõi không có dependency database, nên compiler chặn luôn việc dùng nhầm.
  • shop-core mà khai báo dependency tới shop-infrastructure thì Maven báo cyclic reference và không build.

Tự kiểm tra

0/3 câu
Câu 1

Module mới shop-batch (job chạy đêm) cần dùng OrderService nằm trong shop-core. Làm thế nào?

Câu 2

shop-api đã có dependency shop-core và import com.shop.api.core.*, nhưng javac vẫn báo cannot find symbol, symbol: class Product. Nguyên nhân?

Câu 3

Lợi ích lớn nhất của việc tách shop-core thành module riêng là gì?

Clean Architecture

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt