Tách nhiều module Maven
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 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:runinstall 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ùngProductthì thêmimport com.shop.api.core.*;. jakarta.persistence-apichỉ chứa annotation như@Entity, không có code kết nối database. Hibernate, Spring Data JPA vàojdbc17nằm ởshop-infrastructure.- Sửa
Productmột lần,./mvnw installlà mọi module cùng nhận.
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 existLỗ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 classpubliccủ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-coremà khai báo dependency tớishop-infrastructurethì Maven báocyclic referencevà không build.
Tự kiểm tra
0/3 câuModule mới shop-batch (job chạy đêm) cần dùng OrderService nằm trong shop-core. Làm thế nào?
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?
Lợi ích lớn nhất của việc tách shop-core thành module riêng là gì?