多模块项目设计

Maven 多模块项目设计

核心概念

Maven 多模块项目是一个父项目包含多个子模块的结构,通过统一的 POM 文件管理依赖版本、编译配置等,实现代码复用和项目解耦。

为什么使用多模块?

优势 说明
依赖管理 统一管理版本号,避免版本冲突
代码复用 公共代码提取到独立模块
模块解耦 各模块职责清晰,便于维护
构建优化 支持增量构建,提高效率
团队协作 不同团队可独立开发不同模块

典型的多模块结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my-project/                    # 父项目
├── pom.xml # 父 POM(聚合和依赖管理)
├── my-common/ # 公共模块
│ ├── pom.xml
│ └── src/
├── my-service/ # 业务服务模块
│ ├── pom.xml
│ └── src/
├── my-web/ # Web 模块
│ ├── pom.xml
│ └── src/
└── my-app/ # 应用启动模块
├── pom.xml
└── src/

模块分类

  • 父模块:只包含 POM,不含源代码
  • 公共模块:提供通用工具、常量、基类等
  • 业务模块:核心业务逻辑
  • Web/API 模块:对外接口层
  • 启动模块:应用入口

父 POM 配置

1. 聚合配置(Aggregation)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging> <!-- 父模块必须是 pom -->

<!-- 聚合子模块 -->
<modules>
<module>my-common</module>
<module>my-service</module>
<module>my-web</module>
<module>my-app</module>
</modules>
</project>

作用:执行 mvn clean install 时,会自动构建所有子模块

2. 依赖管理(Dependency Management)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencyManagement>
<dependencies>
<!-- 定义版本,子模块不需要指定版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>com.example</groupId>
<artifactId>my-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

关键点

  • 只声明版本,不引入依赖
  • 子模块可选择是否使用
  • 避免版本冲突

3. 插件管理(Plugin Management)

1
2
3
4
5
6
7
8
9
10
11
12
13
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>

子模块 POM 配置

基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<project>
<modelVersion>4.0.0</modelVersion>

<!-- 继承父模块 -->
<parent>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>my-common</artifactId>
<name>Common Module</name>

<!-- 依赖(版本从父 POM 继承) -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 不需要指定版本 -->
</dependency>
</dependencies>
</project>

模块间依赖

1
2
3
4
5
6
7
8
<!-- my-service 模块依赖 my-common -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common</artifactId>
<!-- 版本从 dependencyManagement 继承 -->
</dependency>
</dependencies>

设计最佳实践

1. 模块职责划分

1
2
3
4
5
6
7
my-project/
├── my-common/ # 工具类、常量、基类
├── my-domain/ # 领域模型、实体
├── my-repository/ # 数据访问层
├── my-service/ # 业务逻辑层
├── my-controller/ # 控制层
└── my-app/ # 启动模块

依赖流向app → controller → service → repository → domain ← common

2. 版本管理策略

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 父 POM 中定义版本变量 -->
<properties>
<project.version>1.0.0</project.version>
<spring-boot.version>2.7.0</spring-boot.version>
<java.version>11</java.version>
</properties>

<!-- 子模块引用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>

3. 避免循环依赖

1
2
❌ 错误:ABA
✅ 正确:AB → C(单向依赖)

检查命令

1
mvn dependency:tree

4. 模块粒度控制

粒度 优点 缺点
粗粒度 构建快,管理简单 模块职责混乱
细粒度 职责清晰,复用性好 构建复杂,依赖管理困难

建议:根据团队规模和项目复杂度平衡


常用 Maven 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 构建所有模块
mvn clean install

# 构建特定模块
mvn clean install -pl my-common

# 构建模块及其依赖
mvn clean install -pl my-service -am

# 跳过测试
mvn clean install -DskipTests

# 查看依赖树
mvn dependency:tree

# 检查循环依赖
mvn dependency:tree | grep "CIRCULAR"

实战示例:电商系统

1
2
3
4
5
6
7
8
ecommerce-platform/
├── pom.xml # 父 POM
├── ecommerce-common/ # 通用工具、常量
├── ecommerce-domain/ # 领域模型
├── ecommerce-repository/ # 数据访问
├── ecommerce-service/ # 业务逻辑
├── ecommerce-api/ # REST API
└── ecommerce-app/ # Spring Boot 启动类

依赖关系

1
2
3
4
5
6
7
8
ecommerce-app
├── ecommerce-api
│ ├── ecommerce-service
│ │ ├── ecommerce-repository
│ │ │ └── ecommerce-domain
│ │ └── ecommerce-common
│ └── ecommerce-common
└── ...

常见问题

Q1: 子模块如何访问其他子模块的资源?

A: 通过依赖声明,不要直接引用文件路径

1
2
3
4
5
6
7
8
<!-- ✅ 正确 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common</artifactId>
</dependency>

<!-- ❌ 错误 -->
<!-- 不要使用相对路径 -->

Q2: 如何只构建某个模块?

A: 使用 -pl 参数

1
mvn clean install -pl my-service

Q3: 子模块版本与父模块不同可以吗?

A: 不建议。应该保持一致,使用 ${project.version}

Q4: 如何处理模块间的版本冲突?

A: 在父 POM 的 dependencyManagement 中统一定义


总结

要点 说明
聚合 父 POM 通过 <modules> 统一构建
继承 子模块通过 <parent> 继承配置
依赖管理 使用 <dependencyManagement> 统一版本
模块划分 按职责清晰划分,避免循环依赖
版本策略 统一版本号,便于维护和发布

框架类型项目设计(SPI 模式)

框架类型项目与普通业务系统不同,需要支持可插拔扩展性。典型场景:

  • RAG 系统:多种向量数据库(ES、PG、Neo4j、Milvus)
  • ORM 框架:多种数据库方言(MySQL、PostgreSQL、Oracle)
  • 消息队列:多种实现(Kafka、RabbitMQ、RocketMQ)

核心设计思想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────┐
│ 用户应用层 │
│ (只依赖 API + 需要的实现) │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ rag-core (核心模块) │
│ 定义接口 + 默认实现 + SPI 加载机制 │
└─────────────────────────────────────────────────────────────┘

┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ rag-store-es │ │ rag-store-pg │ │ rag-store-neo4j │
│ (ES 实现) │ │ (PgVector) │ │ (Neo4j 实现) │
└─────────────────┘ └─────────────────┘ └─────────────────┘

关键原则

  • 依赖倒置:核心模块定义接口,实现模块依赖核心
  • 按需引入:用户只引入需要的实现模块
  • SPI 发现:运行时自动发现并加载实现

实战示例:RAG 系统模块结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
rag-framework/
├── pom.xml # 父 POM

├── rag-core/ # 核心模块
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/rag/
│ ├── api/ # 核心接口定义
│ │ ├── VectorStore.java
│ │ └── DocumentSplitter.java
│ ├── spi/ # SPI 加载机制
│ │ └── SpiLoader.java
│ └── model/ # 领域模型
│ └── Document.java

├── rag-store-elasticsearch/ # ES 实现
│ ├── pom.xml
│ └── src/main/
│ ├── java/.../ElasticsearchVectorStore.java
│ └── resources/META-INF/services/
│ └── com.example.rag.api.VectorStore

├── rag-store-pgvector/ # PgVector 实现
│ ├── pom.xml
│ └── src/main/
│ ├── java/.../PgVectorStore.java
│ └── resources/META-INF/services/
│ └── com.example.rag.api.VectorStore

├── rag-store-neo4j/ # Neo4j 实现
│ └── ...

├── rag-splitter-fixed/ # 固定长度切片
│ └── ...

├── rag-splitter-sentence/ # 按句子切片
│ └── ...

├── rag-splitter-semantic/ # 语义切片
│ └── ...

└── rag-spring-boot-starter/ # Spring Boot 自动配置
└── ...

核心模块设计(rag-core)

1. 定义 SPI 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// VectorStore.java - 向量存储接口
public interface VectorStore {

/** SPI 扩展点标识 */
String getType();

void store(List<Document> documents);

List<Document> search(String query, int topK);
}

// DocumentSplitter.java - 文档切片接口
public interface DocumentSplitter {

String getType();

List<Document> split(Document document);
}

2. SPI 加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SpiLoader {

private static final Map<Class<?>, Map<String, Object>> CACHE =
new ConcurrentHashMap<>();

@SuppressWarnings("unchecked")
public static <T> T load(Class<T> clazz, String type) {
Map<String, Object> implementations = CACHE.computeIfAbsent(
clazz,
k -> loadImplementations(clazz)
);

T impl = (T) implementations.get(type);
if (impl == null) {
throw new IllegalArgumentException(
"No implementation found for type: " + type
);
}
return impl;
}

private static <T> Map<String, Object> loadImplementations(Class<T> clazz) {
Map<String, Object> map = new HashMap<>();
ServiceLoader<T> loader = ServiceLoader.load(clazz);

for (T impl : loader) {
// 假设接口有 getType() 方法
String type = getType(impl);
map.put(type, impl);
}
return map;
}
}

3. 核心模块 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- rag-core/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-framework</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-core</artifactId>
<name>RAG Core</name>

<dependencies>
<!-- 只放最基础的依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

实现模块设计

1. ES 实现模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ElasticsearchVectorStore.java
public class ElasticsearchVectorStore implements VectorStore {

@Override
public String getType() {
return "elasticsearch";
}

@Override
public void store(List<Document> documents) {
// ES 特定实现
}

@Override
public List<Document> search(String query, int topK) {
// ES 特定实现
return null;
}
}

2. SPI 配置文件

1
2
# src/main/resources/META-INF/services/com.example.rag.api.VectorStore
com.example.rag.store.es.ElasticsearchVectorStore

3. 实现模块 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- rag-store-elasticsearch/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-framework</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-store-elasticsearch</artifactId>

<dependencies>
<!-- 依赖核心模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-core</artifactId>
</dependency>

<!-- ES 特定依赖 -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.10.0</version>
</dependency>
</dependencies>
</project>

用户使用方式

按需引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 用户项目 pom.xml -->
<dependencies>
<!-- 核心(必需) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-core</artifactId>
<version>1.0.0</version>
</dependency>

<!-- 只引入需要的实现 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-store-pgvector</artifactId>
<version>1.0.0</version>
</dependency>

<dependency>
<groupId>com.example</groupId>
<artifactId>rag-splitter-semantic</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>

代码使用

1
2
3
4
5
6
7
8
9
// 通过 SPI 自动加载
VectorStore store = SpiLoader.load(VectorStore.class, "pgvector");
DocumentSplitter splitter = SpiLoader.load(DocumentSplitter.class, "semantic");

// 或通过配置文件指定
@Value("${rag.store.type}")
private String storeType;

VectorStore store = SpiLoader.load(VectorStore.class, storeType);

依赖关系图

1
2
3
4
5
6
7
8
9
用户项目

├── rag-core (必需)

├── rag-store-xxx (选一个)
│ └── rag-core

└── rag-splitter-xxx (选一个)
└── rag-core

关键点

  • 用户不需要同时引入所有实现
  • 运行时通过 SPI 自动发现引入的实现
  • 新增实现只需要添加新模块,不修改核心代码

Spring Boot Starter 封装

为了更好的集成体验,可以提供 Starter:

1
2
3
4
5
6
7
8
9
10
11
<!-- rag-spring-boot-starter/pom.xml -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// RagAutoConfiguration.java
@Configuration
@EnableConfigurationProperties(RagProperties.class)
public class RagAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public VectorStore vectorStore(RagProperties props) {
return SpiLoader.load(VectorStore.class, props.getStore().getType());
}

@Bean
@ConditionalOnMissingBean
public DocumentSplitter documentSplitter(RagProperties props) {
return SpiLoader.load(DocumentSplitter.class, props.getSplitter().getType());
}
}
1
2
3
4
5
6
7
# 用户配置 application.yml
rag:
store:
type: pgvector
splitter:
type: semantic
chunk-size: 500

框架设计对比

设计模式 适用场景 典型框架
SPI + 多实现模块 同一接口多种实现 JDBC、SLF4J
策略模式 算法/行为可替换 切片方式、嵌入模型
模板方法 固定流程可扩展步骤 Spring Template
工厂模式 对象创建解耦 各种 Factory

扩展性设计要点

  1. 接口隔离:核心接口放在独立模块,无第三方依赖
  2. 可选依赖:使用 <optional>true</optional> 避免传递依赖
  3. 默认实现:核心模块提供简单默认实现
  4. 配置驱动:通过配置切换实现,而非代码修改
  5. 版本兼容:接口变更需考虑向后兼容

完整框架结构(含应用层)

实际项目中,除了可插拔的底层模块,还需要应用层来组合这些模块并对外提供服务。

完整模块结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
rag-framework/
├── pom.xml # 父 POM

===== 框架层(可独立发布给第三方使用)=====

├── rag-core/ # 核心接口 + SPI
│ └── 无外部依赖,纯接口定义

├── rag-stores/ # 向量存储实现(聚合模块)
│ ├── pom.xml # packaging=pom,聚合子模块
│ ├── rag-store-core/ # 🔑 抽象层:基类 + 公共工具
│ ├── rag-store-elasticsearch/ # 继承抽象层
│ ├── rag-store-pgvector/
│ └── rag-store-neo4j/

├── rag-splitters/ # 切片策略实现(聚合模块)
│ ├── pom.xml
│ ├── rag-splitter-core/ # 🔑 抽象层
│ ├── rag-splitter-fixed/
│ ├── rag-splitter-sentence/
│ └── rag-splitter-semantic/

├── rag-embeddings/ # 嵌入模型实现(聚合模块)
│ ├── pom.xml
│ ├── rag-embedding-core/ # 🔑 抽象层
│ ├── rag-embedding-openai/
│ └── rag-embedding-local/

├── rag-spring-boot-starter/ # Spring Boot 自动配置

===== 应用层(组合模块,对外提供服务)=====

├── rag-service/ # 业务服务层
├── rag-api/ # Controller 层(REST API)
└── rag-server/ # 启动模块

抽象层设计(rag-store-core)

抽象层的职责:

  • 抽象基类:实现公共逻辑,子类只需实现差异部分
  • 工具类:连接池管理、序列化、重试机制等
  • 配置基类:公共配置项

依赖关系

1
2
3
4
5
6
7
rag-store-elasticsearch


rag-store-core (抽象层)


rag-core (接口)

抽象层代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// rag-store-core 模块

/**
* VectorStore 抽象基类
* 实现公共逻辑,子类只需关注特定数据库操作
*/
public abstract class AbstractVectorStore implements VectorStore {

protected final VectorStoreConfig config;

protected AbstractVectorStore(VectorStoreConfig config) {
this.config = config;
}

@Override
public void store(List<Document> documents) {
// 1. 公共逻辑:参数校验
validateDocuments(documents);

// 2. 公共逻辑:批量处理
List<List<Document>> batches = partition(documents, config.getBatchSize());

for (List<Document> batch : batches) {
// 3. 调用子类实现的具体存储逻辑
doStore(batch);
}
}

@Override
public List<Document> search(String query, int topK) {
// 公共逻辑:参数校验
if (topK <= 0 || topK > config.getMaxTopK()) {
topK = config.getDefaultTopK();
}

// 调用子类实现
return doSearch(query, topK);
}

// ===== 子类必须实现的抽象方法 =====

/** 具体的存储实现 */
protected abstract void doStore(List<Document> documents);

/** 具体的搜索实现 */
protected abstract List<Document> doSearch(String query, int topK);

// ===== 公共工具方法 =====

protected void validateDocuments(List<Document> docs) {
if (docs == null || docs.isEmpty()) {
throw new IllegalArgumentException("Documents cannot be empty");
}
}

protected <T> List<List<T>> partition(List<T> list, int size) {
// 分批工具方法
List<List<T>> result = new ArrayList<>();
for (int i = 0; i < list.size(); i += size) {
result.add(list.subList(i, Math.min(i + size, list.size())));
}
return result;
}
}

/**
* 公共配置基类
*/
@Data
public class VectorStoreConfig {
private int batchSize = 100;
private int defaultTopK = 5;
private int maxTopK = 100;
private int connectTimeout = 5000;
private int readTimeout = 30000;
}

具体实现(继承抽象层)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// rag-store-pgvector 模块

public class PgVectorStore extends AbstractVectorStore {

private final JdbcTemplate jdbcTemplate;

public PgVectorStore(PgVectorConfig config, JdbcTemplate jdbcTemplate) {
super(config); // 调用父类构造器
this.jdbcTemplate = jdbcTemplate;
}

@Override
public String getType() {
return "pgvector";
}

@Override
protected void doStore(List<Document> documents) {
// 只需实现 PgVector 特定的存储逻辑
String sql = "INSERT INTO documents (content, embedding) VALUES (?, ?::vector)";
jdbcTemplate.batchUpdate(sql, documents, documents.size(),
(ps, doc) -> {
ps.setString(1, doc.getContent());
ps.setArray(2, toSqlArray(doc.getEmbedding()));
});
}

@Override
protected List<Document> doSearch(String query, int topK) {
// 只需实现 PgVector 特定的搜索逻辑
String sql = """
SELECT content, 1 - (embedding <=> ?::vector) as score
FROM documents
ORDER BY embedding <=> ?::vector
LIMIT ?
""";
return jdbcTemplate.query(sql, documentRowMapper, queryVector, queryVector, topK);
}
}

// 继承公共配置,添加特定配置
@Data
@EqualsAndHashCode(callSuper = true)
public class PgVectorConfig extends VectorStoreConfig {
private String tableName = "documents";
private int dimension = 1536;
private String indexType = "ivfflat";
}

聚合模块 POM 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- rag-stores/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-framework</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-stores</artifactId>
<packaging>pom</packaging>
<name>RAG Vector Stores</name>

<modules>
<module>rag-store-core</module> <!-- 抽象层优先构建 -->
<module>rag-store-elasticsearch</module>
<module>rag-store-pgvector</module>
<module>rag-store-neo4j</module>
</modules>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- rag-stores/rag-store-core/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-stores</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-store-core</artifactId>

<dependencies>
<!-- 只依赖核心接口 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-core</artifactId>
</dependency>
</dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- rag-stores/rag-store-pgvector/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-stores</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-store-pgvector</artifactId>

<dependencies>
<!-- 依赖抽象层(会传递 rag-core) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-store-core</artifactId>
</dependency>

<!-- PgVector 特定依赖 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.pgvector</groupId>
<artifactId>pgvector</artifactId>
</dependency>
</dependencies>
</project>

父 POM 聚合配置

1
2
3
4
5
6
7
8
9
10
11
<!-- 根 pom.xml -->
<modules>
<module>rag-core</module>
<module>rag-stores</module> <!-- 聚合目录 -->
<module>rag-splitters</module> <!-- 聚合目录 -->
<module>rag-embeddings</module> <!-- 聚合目录 -->
<module>rag-spring-boot-starter</module>
<module>rag-service</module>
<module>rag-api</module>
<module>rag-server</module>
</modules>

模块依赖全景图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
         ┌─────────────────────────────────┐
│ rag-server │
│ (启动类 + 配置 + 打包) │
└─────────────────────────────────┘

┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ rag-api │ │rag-store-pg │ │rag-splitter- │
│ (Controller) │ │ (选择的实现) │ │ semantic │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ │ │
│ rag-service │ │ │
│ (业务逻辑) │◄───────┴───────────────┘
└──────────────┘


┌──────────────┐
│ rag-core │
│ (核心接口) │
└──────────────┘

Service 层设计(rag-service)

Service 层负责组合底层组件,实现具体业务逻辑。

模块 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- rag-service/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-framework</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-service</artifactId>

<dependencies>
<!-- 依赖核心模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-core</artifactId>
</dependency>

<!-- Spring 依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>

Service 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// RagService.java - 组合底层组件
@Service
public class RagService {

private final VectorStore vectorStore;
private final DocumentSplitter splitter;
private final EmbeddingModel embeddingModel;

// 通过构造器注入(具体实现由 Starter 自动配置)
public RagService(VectorStore vectorStore,
DocumentSplitter splitter,
EmbeddingModel embeddingModel) {
this.vectorStore = vectorStore;
this.splitter = splitter;
this.embeddingModel = embeddingModel;
}

/**
* 文档入库流程
*/
public void ingest(Document document) {
// 1. 切片
List<Document> chunks = splitter.split(document);

// 2. 生成向量
for (Document chunk : chunks) {
float[] embedding = embeddingModel.embed(chunk.getContent());
chunk.setEmbedding(embedding);
}

// 3. 存储
vectorStore.store(chunks);
}

/**
* 检索流程
*/
public List<Document> retrieve(String query, int topK) {
return vectorStore.search(query, topK);
}

/**
* RAG 完整流程
*/
public String chat(String question) {
// 1. 检索相关文档
List<Document> contexts = retrieve(question, 5);

// 2. 构建 Prompt
String prompt = buildPrompt(question, contexts);

// 3. 调用 LLM(这里可以再抽象一个 LLM 接口)
return llmClient.generate(prompt);
}
}

Controller 层设计(rag-api)

Controller 层负责对外暴露 HTTP 接口

模块 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- rag-api/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-framework</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-api</artifactId>

<dependencies>
<!-- 依赖 service 层 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-service</artifactId>
</dependency>

<!-- Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

Controller 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// RagController.java
@RestController
@RequestMapping("/api/rag")
public class RagController {

private final RagService ragService;

public RagController(RagService ragService) {
this.ragService = ragService;
}

/**
* 文档入库
*/
@PostMapping("/ingest")
public ResponseEntity<Void> ingest(@RequestBody IngestRequest request) {
Document doc = new Document(request.getContent(), request.getMetadata());
ragService.ingest(doc);
return ResponseEntity.ok().build();
}

/**
* 检索
*/
@GetMapping("/retrieve")
public ResponseEntity<List<DocumentVO>> retrieve(
@RequestParam String query,
@RequestParam(defaultValue = "5") int topK) {
List<Document> docs = ragService.retrieve(query, topK);
return ResponseEntity.ok(DocumentVO.from(docs));
}

/**
* 问答
*/
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
String answer = ragService.chat(request.getQuestion());
return ResponseEntity.ok(new ChatResponse(answer));
}
}

DTO 定义

1
2
3
4
// 请求/响应对象放在 api 模块
public record IngestRequest(String content, Map<String, Object> metadata) {}
public record ChatRequest(String question) {}
public record ChatResponse(String answer) {}

启动模块设计(rag-server)

启动模块负责选择具体实现 + 打包部署

模块 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!-- rag-server/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag-framework</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag-server</artifactId>
<packaging>jar</packaging>

<dependencies>
<!-- API 层(包含 Controller) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-api</artifactId>
</dependency>

<!-- 自动配置 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-spring-boot-starter</artifactId>
</dependency>

<!-- ===== 选择具体实现 ===== -->

<!-- 向量存储:选择 PgVector -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-store-pgvector</artifactId>
</dependency>

<!-- 切片方式:选择语义切片 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag-splitter-semantic</artifactId>
</dependency>

<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<!-- 打包成可执行 JAR -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

启动类

1
2
3
4
5
6
7
// RagServerApplication.java
@SpringBootApplication
public class RagServerApplication {
public static void main(String[] args) {
SpringApplication.run(RagServerApplication.class, args);
}
}

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# application.yml
server:
port: 8080

rag:
store:
type: pgvector
datasource:
url: jdbc:postgresql://localhost:5432/rag
username: postgres
password: secret
splitter:
type: semantic
chunk-size: 500
overlap: 50
embedding:
type: openai
api-key: ${OPENAI_API_KEY}

多种部署方式

通过不同的 server 模块,可以灵活组合:

1
2
3
4
5
6
7
8
9
10
11
rag-server-pg/          # PgVector + 语义切片
├── pom.xml # 引入 pgvector + semantic
└── application.yml

rag-server-es/ # ES + 固定切片
├── pom.xml # 引入 elasticsearch + fixed
└── application.yml

rag-server-all/ # 全部实现(通过配置切换)
├── pom.xml # 引入所有实现
└── application.yml # 配置指定使用哪个

模块职责总结

模块 职责 依赖
rag-core 核心接口定义
rag-store-xxx 向量存储实现 core
rag-splitter-xxx 切片策略实现 core
rag-spring-boot-starter 自动配置 core
rag-service 业务逻辑,组合组件 core
rag-api REST Controller service
rag-server 启动模块,选择实现 api + starter + 具体实现

框架使用者 vs 框架开发者

角色 使用模块 说明
框架使用者 core + starter + 实现模块 集成到自己项目
框架开发者 全部模块 开发和维护框架
独立部署 server 直接运行服务

简化三层结构(推荐)

对于中小型项目,可以采用更简洁的三层结构:

1
2
3
4
5
6
rag4j/
├── pom.xml # 父 POM
├── rag4j-framework/ # 框架层 - 核心抽象与通用能力
├── rag4j-project/ # 实现层 - 具体技术实现
├── rag4j-app/ # 应用层 - Web服务入口
└── test/ # 测试模块

各层详细结构

1. 框架层(rag4j-framework)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
rag4j-framework/
├── pom.xml
└── src/main/java/com/example/rag4j/framework/

├── core/ # 核心接口定义
│ ├── VectorStore.java
│ ├── DocumentSplitter.java
│ ├── EmbeddingModel.java
│ └── Document.java

├── store/ # 向量存储抽象
│ ├── AbstractVectorStore.java
│ └── VectorStoreConfig.java

├── splitter/ # 切片器抽象
│ ├── AbstractSplitter.java
│ └── SplitterConfig.java

├── embedding/ # 嵌入模型抽象
│ └── AbstractEmbeddingModel.java

├── spi/ # SPI 加载机制
│ └── SpiLoader.java

└── util/ # 通用工具
├── RetryUtil.java
└── BatchUtil.java

特点:纯抽象,无第三方依赖(或最小依赖)

2. 实现层(rag4j-project)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
rag4j-project/
├── pom.xml # 聚合模块

├── rag4j-store-pgvector/ # PgVector 实现
│ ├── pom.xml
│ └── src/.../PgVectorStore.java

├── rag4j-store-elasticsearch/ # ES 实现
│ └── ...

├── rag4j-store-neo4j/ # Neo4j 实现
│ └── ...

├── rag4j-splitter-fixed/ # 固定切片
│ └── ...

├── rag4j-splitter-semantic/ # 语义切片
│ └── ...

├── rag4j-embedding-openai/ # OpenAI 嵌入
│ └── ...

└── rag4j-spring-boot-starter/ # Spring Boot 自动配置
└── ...

特点:每个实现独立模块,按需引入

3. 应用层(rag4j-app)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rag4j-app/
├── pom.xml
└── src/main/
├── java/com/example/rag4j/app/
│ ├── Rag4jApplication.java # 启动类
│ ├── config/ # 配置类
│ │ └── Rag4jConfig.java
│ ├── service/ # 业务服务
│ │ └── RagService.java
│ ├── controller/ # REST API
│ │ └── RagController.java
│ └── dto/ # 请求/响应对象
│ ├── ChatRequest.java
│ └── ChatResponse.java
└── resources/
└── application.yml

特点:组合框架层和实现层,对外提供服务


依赖关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────────┐
│ rag4j-app │
│ (Controller + Service + 启动配置) │
│ │
│ 依赖: framework + 选择的实现模块 + starter │
└─────────────────────────────────────────────────────────────┘

┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ rag4j-store-pg │ │rag4j-splitter- │ │ rag4j-embedding │
│ │ │ semantic │ │ -openai │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────┼───────────────────┘

┌───────────────────────────┐
│ rag4j-framework │
│ (接口 + 抽象类 + SPI) │
└───────────────────────────┘

POM 配置示例

父 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!-- rag4j/pom.xml -->
<project>
<groupId>com.example</groupId>
<artifactId>rag4j</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<modules>
<module>rag4j-framework</module>
<module>rag4j-project</module>
<module>rag4j-app</module>
<module>test</module>
</modules>

<properties>
<java.version>17</java.version>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- 内部模块版本管理 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag4j-framework</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 其他模块... -->
</dependencies>
</dependencyManagement>
</project>

应用层 POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!-- rag4j-app/pom.xml -->
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>rag4j</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>rag4j-app</artifactId>

<dependencies>
<!-- 框架层 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag4j-framework</artifactId>
</dependency>

<!-- 选择具体实现 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag4j-store-pgvector</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>rag4j-splitter-semantic</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>rag4j-embedding-openai</artifactId>
</dependency>

<!-- 自动配置 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>rag4j-spring-boot-starter</artifactId>
</dependency>

<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

两种结构对比

对比项 细粒度结构 三层简化结构
模块数量 多(10+) 少(3-4 + 实现)
适用规模 大型框架 中小型项目
灵活性 适中
维护成本
学习曲线 平缓
典型案例 Spring Framework 业务系统

建议

  • 对外发布的框架 → 细粒度结构,方便用户按需引入
  • 内部项目/业务系统 → 三层简化结构,开发效率高

多模块项目设计
https://zmmmmy.github.io/2026/01/12/多模块项目设计/
作者
ZhiMy
发布于
2026年1月12日
许可协议