feat: add documentation for cross-domain collaboration and aggregation
- Introduced a new section on cross-domain collaboration and aggregation, detailing decision-making processes, contract module usage for cross-domain reads, and domain events for writes. - Added guidelines for parallel aggregation using a dedicated thread pool and context propagation. - Established rules for transaction boundaries, idempotency, optimistic locking, scheduled tasks, and caching strategies in a concurrent environment. - Included examples and best practices for implementing these concepts in the application.
This commit is contained in:
+144
-50
@@ -4,6 +4,29 @@
|
||||
|
||||
Kotlin + Spring Boot + Gradle(Groovy DSL,`build.gradle`)。
|
||||
|
||||
## 版本基线
|
||||
|
||||
| 组件 | 版本 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| Spring Boot | `4.1.0` | OSS 支持至 2027-07。3.x 全系列 OSS 支持已结束(3.5 于 2026-06-30 结束),新项目不应再从 3.x 起步 |
|
||||
| Spring Cloud | `2025.1.2`(Oakwood) | Boot 4.1 需要 2025.1.2 及以上;提供 `spring-cloud-kubernetes` 5.0.2,见 [07-config-governance.md](./07-config-governance.md) |
|
||||
| Kotlin | `2.4.10` | |
|
||||
| Java | `21`(LTS,用 Gradle toolchain 锁定) | |
|
||||
|
||||
Spring Boot 4 连带升级了 Spring Framework 7 / Spring Security 7 / **Jackson 3**,第三方库必须选对应版本线,各篇文档里出现的坐标已按此对齐:
|
||||
|
||||
| 库 | 版本 | 注意点 |
|
||||
| --- | --- | --- |
|
||||
| Resilience4j | `2.4.0` | artifact 是 **`resilience4j-spring-boot4`**,不是 `-spring-boot3`;2.4.0 才加的 Boot 4 支持 |
|
||||
| springdoc-openapi | `3.0.3` | Boot 4 对应 springdoc **3.x**,Boot 3 才是 2.x |
|
||||
| logstash-logback-encoder | `9.0` | 9.0 起迁到 Jackson 3,正好匹配 Boot 4;8.x 及以前是 Jackson 2 |
|
||||
| MapStruct | `1.6.3` + kapt | MapStruct 至今没有正式的 KSP 支持,Kotlin 项目仍走 kapt |
|
||||
| springmockk | `5.0.1` | Boot 4 删除了 `@MockBean`/`@SpyBean`;springmockk 5.x 对应 Framework 7,`@SpykBean` 已改名 `@MockkSpyBean` |
|
||||
| ArchUnit | `1.4.2` | |
|
||||
| Testcontainers | 跟随 Spring Boot BOM | 2.x 做了模块化拆分,坐标和包名都变了(`org.testcontainers:testcontainers-mysql` / `org.testcontainers.mysql.MySQLContainer`),升级时以 `./gradlew dependencies` 的实际解析结果为准,见 [10-testing.md](./10-testing.md) |
|
||||
|
||||
升级 Boot 版本时,这张表要整体复核一遍,不要只改 Boot 版本号。
|
||||
|
||||
## 决策
|
||||
|
||||
单一 Gradle 多模块工程,落地为**模块化单体(Modular Monolith)**:只有一个可执行部署单元,内部按 [architecture-diagram](../Architecture-Diagram/architecture-diagram-explanation.md) 里 App Backend 的职责边界拆成多个 Gradle 子模块,用编译期依赖规则强制边界,而不是先拆成多个独立部署的微服务。
|
||||
@@ -12,71 +35,122 @@ Kotlin + Spring Boot + Gradle(Groovy DSL,`build.gradle`)。
|
||||
|
||||
- 现阶段团队规模和运维能力还撑不起"多个独立部署单元 + 服务发现 + 分布式事务/一致性"的复杂度。
|
||||
- App Backend 内部这几个模块(Identity/BFF/Workbench/WebView Ticket/Integration)本来就是高内聚的一套业务,拆早了只是把进程内调用换成网络调用,徒增延迟和故障点,业务上并没有获得隔离收益。
|
||||
- 但如果不做任何边界约束、堆成一个大包,后期想拆也拆不动。Gradle 多模块正好卡在中间:**部署简单(一个 jar),边界物理存在(模块间依赖编译期强制)**,未来如果某个模块(比如 `f6-integration`)流量或迭代速度明显超过其他模块,再单独拆出来部署成本也低。
|
||||
- 但如果不做任何边界约束、堆成一个大包,后期想拆也拆不动。Gradle 多模块正好卡在中间:**部署简单(一个 jar),边界物理存在(模块间依赖编译期强制)**,未来如果某个模块(比如 `f6-adapter`)流量或迭代速度明显超过其他模块,再单独拆出来部署成本也低。
|
||||
|
||||
## 模块结构总览
|
||||
|
||||
```
|
||||
conti-backend/
|
||||
settings.gradle
|
||||
build.gradle # 根工程:统一插件版本、公共依赖约束(Spring Boot BOM)
|
||||
build.gradle # 根工程:统一插件版本、公共依赖约束(Spring Boot / Spring Cloud BOM)
|
||||
bootstrap/ # 唯一可执行模块:装配所有模块,产出单一 jar/镜像
|
||||
build.gradle # 依赖所有 platform-* / domains/* 模块 + @SpringBootApplication 启动类
|
||||
build.gradle # 依赖所有 platform-* / domains/* / integration/* + @SpringBootApplication 启动类
|
||||
platform/
|
||||
platform-web/ # 统一异常处理、Result包装、参数校验、GlobalExceptionHandler
|
||||
platform-web/ # 统一异常处理、ApiResult 包装、参数校验、GlobalExceptionHandler
|
||||
platform-security/ # Spring Security + JWT 解析、门店/角色上下文注入
|
||||
platform-persistence/ # JPA 基础设施:审计字段、BaseEntity、分页封装、Flyway 配置
|
||||
platform-observability/ # 日志格式、Trace ID 透传、Micrometer配置
|
||||
platform-integration/ # WebClient/Resilience4j 基础封装(超时/重试/熔断通用能力)
|
||||
platform-persistence/ # JPA 基础设施:审计字段、BaseEntity、分页封装、Flyway 多实例配置
|
||||
platform-observability/ # 日志格式、Trace 透传、Micrometer 配置
|
||||
platform-integration/ # RestClient/Resilience4j 基础封装(超时/重试/熔断/舱壁通用能力)
|
||||
domains/
|
||||
identity-store/ # Identity & Store Center:登录、token、门店上下文、菜单权限
|
||||
bff-orchestration/ # BFF Orchestration:面向APP的统一接口聚合与协议标准化
|
||||
workbench/ # Workbench Aggregation:首页聚合、局部降级
|
||||
webview-ticket/ # WebView Ticket Center:F6 WebView 换票、会话绑定
|
||||
f6-integration/ # Integration Layer:F6 Adapter,对应架构图里的适配层
|
||||
mini-clients/ # 对 O2O/Warranty/Retail Store/ROOS 的只读客户端封装
|
||||
identity-store-contract/ # ↑ 对外契约:接口 + 传输模型 + 领域事件,其他 domain 只能依赖这个
|
||||
bff-orchestration/ # BFF Orchestration:面向 APP 的统一接口聚合与协议标准化
|
||||
workbench/ # Workbench Aggregation:首页聚合、局部降级
|
||||
webview-ticket/ # WebView Ticket Center:F6 WebView 换票、会话绑定
|
||||
integration/
|
||||
f6-adapter/ # F6 Adapter:对应架构图里的适配层,换票、供应商访问上下文准备
|
||||
mini-clients/ # 对 O2O/Warranty/Retail Store/ROOS 的只读客户端封装
|
||||
architecture-test/ # ArchUnit 架构规则测试,见 10-testing.md
|
||||
```
|
||||
|
||||
(模块名称先按架构图职责命名,实际开发中如果和团队习惯冲突可以再改,不影响这套结构本身。)
|
||||
|
||||
### 为什么把集成层从 `domains/` 里拿出来
|
||||
|
||||
`f6-adapter` 和 `mini-clients` 不是业务域,是**对外部系统的适配层**——架构图里 Integration Layer 本来也是单独一层。放在 `domains/` 下会直接和"domains 之间不允许互相依赖"这条规则冲突:`workbench` 要拿 O2O 的数据,就必须依赖 `mini-clients`,于是要么破规则,要么给规则打补丁。单独一组 `integration/` 之后,规则变成干净的一句"所有 domain 都可以依赖 integration",不需要例外。
|
||||
|
||||
## 依赖规则(编译期强制边界,是这套结构的核心价值)
|
||||
|
||||
- `bootstrap` 是唯一持有 `@SpringBootApplication` 的模块,依赖所有 `domains/*` 和 `platform-*`;其余模块都是普通 library 模块(没有主启动类),不能单独跑成一个服务。
|
||||
- `domains/*` 之间**不允许**互相依赖,`bff-orchestration` 是唯一例外——它是编排层,天然需要依赖其他所有 domain 才能做聚合。
|
||||
- `platform-*` 不含业务逻辑,各 `domains/*` 均可依赖;`platform-*` 之间尽量不互相依赖(`platform-security` 依赖 `platform-web` 里的异常类型是可以接受的例外)。
|
||||
- 跨 domain 的数据/事件传递,只能走 `bff-orchestration` 编排,或者在某个 `platform-*` 定义抽象接口、各 domain 各自实现(依赖倒置),不允许 `identity-store` 直接 import `workbench` 的类。
|
||||
- `bootstrap` 是唯一持有 `@SpringBootApplication` 的模块,依赖所有 `domains/*`、`integration/*` 和 `platform-*`;其余模块都是普通 library 模块(没有主启动类),不能单独跑成一个服务。
|
||||
- **`domains/x` 不可以依赖 `domains/y`。**
|
||||
- **`domains/x` 可以依赖 `domains/y-contract`**(契约模块,见下一节)。
|
||||
- **`domains/*` 都可以依赖 `integration/*` 和 `platform-*`。**
|
||||
- `platform-*` 不含业务逻辑;`platform-*` 之间尽量不互相依赖(`platform-security` 依赖 `platform-web` 里的异常类型是可以接受的例外)。
|
||||
|
||||
这些规则由 Gradle 的模块依赖机制**物理强制**:`domains/identity-store` 的 `build.gradle` 里根本不会声明对 `domains/workbench` 的依赖,编译时 import 不到,不是靠 code review 口头约束。
|
||||
这三条规则的价值在于**可以被 ArchUnit 精确表达**,不是靠 code review 口头约束——测试写法见 [10-testing.md](./10-testing.md)。同时它们也由 Gradle 物理强制:`domains/workbench` 的 `build.gradle` 里根本不会声明对 `domains/identity-store` 的依赖,编译时 import 不到。
|
||||
|
||||
### 模块目录名与包名的对应关系
|
||||
|
||||
目录名带连字符,包名不能带——这个映射必须写死,因为 [10-testing.md](./10-testing.md) 的 ArchUnit 规则是按包名匹配的,改一个就要改另一个:
|
||||
|
||||
| 模块目录 | 基础包名 |
|
||||
| --- | --- |
|
||||
| `domains/identity-store`、`domains/identity-store-contract` | `com.continental.retailapp.identitystore`(契约在 `.identitystore.contract`) |
|
||||
| `domains/bff-orchestration` | `com.continental.retailapp.bff` |
|
||||
| `domains/workbench` | `com.continental.retailapp.workbench` |
|
||||
| `domains/webview-ticket` | `com.continental.retailapp.webviewticket` |
|
||||
| `integration/f6-adapter` | `com.continental.retailapp.integration.f6` |
|
||||
| `integration/mini-clients` | `com.continental.retailapp.integration.mini` |
|
||||
| `platform/platform-*` | `com.continental.retailapp.platform.*` |
|
||||
|
||||
规则是**去掉连字符直接拼接**(`identity-store` → `identitystore`),唯一的例外是 `bff-orchestration` → `bff`(`bfforchestration` 读不出来)。新增模块时按这个规则取名,并同步更新 `10-testing.md` 里 ArchUnit 的 `domains` 列表——那个列表漏了谁,谁的边界就没人管。
|
||||
|
||||
`integration/*` 和 `platform/*` 不按 api/application/domain/infrastructure 分四层(它们本来就不是业务域),内部按自己的职责组织包即可,见 [02-layering.md](./02-layering.md)。
|
||||
|
||||
### 契约模块(`-contract`):跨 domain 协作的唯一通道
|
||||
|
||||
`workbench` 聚合首页时需要门店名称,`webview-ticket` 需要知道"用户切了门店"——这类跨域需求是真实存在的,不可能全部塞进 `bff-orchestration`(那会让 BFF 变成什么都知道的上帝模块)。做法是让被依赖方**显式发布一个最小契约**:
|
||||
|
||||
```
|
||||
domains/identity-store-contract/
|
||||
src/main/kotlin/com/continental/retailapp/identitystore/contract/
|
||||
StoreQueryService.kt # 接口:fun listStoresByUserId(userId: Long): List<StoreInfo>
|
||||
StoreInfo.kt # 传输模型:只含对外承诺的字段
|
||||
StoreSwitchedEvent.kt # 领域事件,见 11-cross-domain-collaboration.md
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- **契约模块里只放接口、传输模型和事件类型**,不放实现、不依赖 Spring Web/JPA,不依赖任何其他 domain。
|
||||
- **实现方(`identity-store`)依赖自己的契约模块并实现它**;调用方(`workbench`)只依赖契约模块,拿不到 `identity-store` 内部的任何类型(包括 Entity)。
|
||||
- **按需创建**,不预先给每个 domain 都建一个空的 contract 模块——没有跨域调用就不需要它。
|
||||
- 命名用 `-contract` 而不是 `-api`,避免和模块内部的 `api/` 包(Controller 层,见 [02-layering.md](./02-layering.md))混淆。
|
||||
|
||||
跨 domain 的写操作和状态联动优先走**领域事件**而不是直接调接口,规则见 [11-cross-domain-collaboration.md](./11-cross-domain-collaboration.md)。
|
||||
|
||||
### 根 `build.gradle` 示例
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '2.0.20' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.spring' version '2.0.20' apply false
|
||||
id 'org.springframework.boot' version '3.3.4' apply false
|
||||
id 'io.spring.dependency-management' version '1.1.6' apply false
|
||||
id 'org.jetbrains.kotlin.jvm' version '2.4.10' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.spring' version '2.4.10' apply false
|
||||
id 'org.jetbrains.kotlin.kapt' version '2.4.10' apply false
|
||||
id 'org.springframework.boot' version '4.1.0' apply false
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.spring'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
group = 'com.continental.retailapp'
|
||||
version = '0.1.0-SNAPSHOT'
|
||||
|
||||
// 用 toolchain 统一 Java 版本:Kotlin 插件会自动把 jvmTarget 对齐到同一个版本。
|
||||
// 只写 sourceCompatibility 是不够的——Kotlin 的 jvmTarget 默认值和它无关,
|
||||
// 两边不一致时 Gradle 会直接报 "Inconsistent JVM-target compatibility" 构建失败。
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework.boot:spring-boot-dependencies:3.3.4"
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// 用 Gradle 原生的 platform() 做版本对齐,不再引入 io.spring.dependency-management 插件:
|
||||
// 少一个需要跟着 Boot 一起升级的插件,行为也更符合 Gradle 自身的依赖解析语义。
|
||||
// testImplementation 继承自 implementation,所以测试依赖同样受这两个 BOM 约束。
|
||||
implementation platform('org.springframework.boot:spring-boot-dependencies:4.1.0')
|
||||
implementation platform('org.springframework.cloud:spring-cloud-dependencies:2025.1.2')
|
||||
|
||||
implementation 'org.jetbrains.kotlin:kotlin-reflect'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
@@ -99,43 +173,56 @@ subprojects {
|
||||
rootProject.name = 'conti-backend'
|
||||
|
||||
include 'bootstrap'
|
||||
include 'architecture-test'
|
||||
|
||||
include 'platform:platform-web'
|
||||
include 'platform:platform-security'
|
||||
include 'platform:platform-persistence'
|
||||
include 'platform:platform-observability'
|
||||
include 'platform:platform-integration'
|
||||
|
||||
include 'domains:identity-store'
|
||||
include 'domains:identity-store-contract'
|
||||
include 'domains:bff-orchestration'
|
||||
include 'domains:workbench'
|
||||
include 'domains:webview-ticket'
|
||||
include 'domains:f6-integration'
|
||||
include 'domains:mini-clients'
|
||||
|
||||
include 'integration:f6-adapter'
|
||||
include 'integration:mini-clients'
|
||||
```
|
||||
|
||||
### `domains/workbench/build.gradle` 示例(体现依赖规则)
|
||||
|
||||
```groovy
|
||||
apply plugin: 'org.springframework.boot' // 只用来获得 bootJar 之外的 starter 依赖管理,不产出可执行 jar
|
||||
|
||||
bootJar {
|
||||
enabled = false // 非 bootstrap 模块不产出可执行 jar
|
||||
}
|
||||
jar {
|
||||
enabled = true
|
||||
}
|
||||
// 注意:库模块不 apply 'org.springframework.boot' 插件。
|
||||
// 版本对齐已经由根工程的 platform() BOM 统一处理,库模块 apply Boot 插件唯一的作用
|
||||
// 就是产出一个我们并不需要的 bootJar,然后再手动把它关掉——多余的一步。
|
||||
// 只有 bootstrap 需要 Boot 插件。
|
||||
|
||||
dependencies {
|
||||
implementation project(':platform:platform-web')
|
||||
implementation project(':platform:platform-persistence')
|
||||
implementation project(':platform:platform-integration')
|
||||
implementation project(':domains:mini-clients') // 允许:workbench 依赖只读客户端封装
|
||||
|
||||
// 不允许出现 implementation project(':domains:identity-store') 这种同级 domain 依赖
|
||||
implementation project(':integration:mini-clients') // 允许:domain 可以依赖 integration
|
||||
implementation project(':domains:identity-store-contract') // 允许:只依赖契约模块
|
||||
|
||||
// 不允许:implementation project(':domains:identity-store') // 同级 domain 的实现模块
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
}
|
||||
```
|
||||
|
||||
### `domains/identity-store-contract/build.gradle` 示例
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
// 契约模块保持极简:不依赖 Spring Web / JPA / 其他 domain,
|
||||
// 这样任何 domain 依赖它都不会顺带把实现细节拉进来。
|
||||
}
|
||||
```
|
||||
|
||||
### `bootstrap/build.gradle` 示例
|
||||
|
||||
```groovy
|
||||
@@ -147,12 +234,15 @@ dependencies {
|
||||
implementation project(':platform:platform-persistence')
|
||||
implementation project(':platform:platform-observability')
|
||||
implementation project(':platform:platform-integration')
|
||||
|
||||
implementation project(':domains:identity-store')
|
||||
implementation project(':domains:identity-store-contract')
|
||||
implementation project(':domains:bff-orchestration')
|
||||
implementation project(':domains:workbench')
|
||||
implementation project(':domains:webview-ticket')
|
||||
implementation project(':domains:f6-integration')
|
||||
implementation project(':domains:mini-clients')
|
||||
|
||||
implementation project(':integration:f6-adapter')
|
||||
implementation project(':integration:mini-clients')
|
||||
}
|
||||
```
|
||||
|
||||
@@ -170,18 +260,18 @@ fun main(args: Array<String>) {
|
||||
|
||||
```
|
||||
domains/xxx/
|
||||
build.gradle # 依赖需要的 platform-* 模块,bootJar 禁用
|
||||
src/main/kotlin/com/continental/retailapp/xxx/
|
||||
build.gradle # 依赖需要的 platform-* / integration-* / 其他 domain 的 -contract
|
||||
src/main/kotlin/com/continental/retailapp/xxx/ # xxx = 去掉连字符的模块名,见上方对应表
|
||||
api/ # Controller、请求/响应 DTO
|
||||
application/ # Service,编排用例
|
||||
application/ # Service、定时任务;Entity → Response 的 mapper 也在这层
|
||||
domain/ # 可选,见 02-layering.md
|
||||
infrastructure/ # repository 实现、外部 client 实现
|
||||
infrastructure/ # repository 实现、Entity、外部 client 实现、模块内的 @Configuration
|
||||
src/main/resources/db/migration/xxx/
|
||||
V1__init.sql
|
||||
src/test/kotlin/...
|
||||
```
|
||||
|
||||
新增模块后记得在 `settings.gradle` 里 `include`,并在 `bootstrap/build.gradle` 里加上依赖——这两步是唯一需要"手动接线"的地方,其余边界规则都由各模块自己的 `build.gradle` 保证。
|
||||
新增模块后需要手动接线的地方只有三处:`settings.gradle` 里 `include`、`bootstrap/build.gradle` 里加依赖、[03-persistence.md](./03-persistence.md) 里给这个 domain 注册一个 Flyway 实例(因为每个 domain 有自己独立的 database 和迁移版本序列)。其余边界规则都由各模块自己的 `build.gradle` 保证。
|
||||
|
||||
## 附录:为什么用 Gradle 多模块,而不是单模块 + 包分层
|
||||
|
||||
@@ -191,7 +281,9 @@ domains/xxx/
|
||||
|
||||
如果整个后端只是一个 Gradle 模块、内部靠 `com.xxx.identity` / `com.xxx.workbench` 这样的包名分层——短期能跑,但 Java/Kotlin 的包(package)**不是编译期边界**,`workbench` 包里的类可以随手 `import com.xxx.identity.SomeInternalClass`,IDE 不会报错,只能靠人自觉或者 ArchUnit 这类静态检查工具事后检测。等项目大到几十人协作时,"事后检测"经常检测不过来,边界会慢慢被绕开。
|
||||
|
||||
**Gradle 多模块的本质**:把"包名上的软边界"换成"模块依赖上的硬边界"。`domains/identity-store` 这个模块的 `build.gradle` 里没有声明对 `domains/workbench` 的依赖,`workbench` 里的类就物理 import 不到 `identity-store` 内部的任何类型,哪怕两个模块在同一个 git 仓库、同一次构建、最终打进同一个 jar 里。
|
||||
**Gradle 多模块的本质**:把"包名上的软边界"换成"模块依赖上的硬边界"。`domains/workbench` 这个模块的 `build.gradle` 里没有声明对 `domains/identity-store` 的依赖,`workbench` 里的类就物理 import 不到 `identity-store` 内部的任何类型,哪怕两个模块在同一个 git 仓库、同一次构建、最终打进同一个 jar 里。
|
||||
|
||||
契约模块是这个思路的延伸:不是"要么全开放、要么全封闭",而是让被依赖方自己决定**对外承诺哪些东西**,其余一律看不见。这跟微服务里"只有 HTTP API 是公开契约、数据库表是私有实现"是同一个道理,只是这里的强制手段从网络协议换成了 Gradle 依赖图。
|
||||
|
||||
### 跟微服务的关系
|
||||
|
||||
@@ -200,11 +292,13 @@ Gradle 多模块和微服务解决的是同一类问题(业务边界隔离)
|
||||
- 微服务:边界靠网络调用强制,代价是要处理服务发现、网络失败、分布式事务/最终一致性、独立的 CI/CD 和监控。
|
||||
- 模块化单体:边界靠编译依赖强制,代价是无法针对单个模块独立扩缩容或独立发布,进程内故障会互相影响(一个模块 OOM 会拖垮整个进程)。
|
||||
|
||||
我们现在处的阶段(App Backend 内部几个模块业务强相关、团队规模有限)更适合后者;如果将来某个模块单独的流量、团队规模、发布节奏都明显跟其他模块脱节,再把它拆成独立微服务——因为模块边界已经在代码里划清楚了,拆分主要是把 `implementation project(':domains:xxx')` 换成 HTTP/消息调用,改动范围可控,不需要推倒重来。
|
||||
我们现在处的阶段(App Backend 内部几个模块业务强相关、团队规模有限)更适合后者;如果将来某个模块单独的流量、团队规模、发布节奏都明显跟其他模块脱节,再把它拆成独立微服务——因为模块边界已经在代码里划清楚了,拆分主要是把 `implementation project(':domains:xxx-contract')` 换成 HTTP/消息调用,改动范围可控,不需要推倒重来。契约模块在这里额外多给了一层好处:**要拆的那个接口清单已经现成写在 contract 模块里了**,不需要先花时间考古"到底谁在用我的什么"。
|
||||
|
||||
## 参考链接
|
||||
|
||||
- [Gradle Multi-Project Builds](https://docs.gradle.org/current/userguide/multi_project_builds.html)
|
||||
- [Gradle: Platforms / BOM 支持](https://docs.gradle.org/current/userguide/platforms.html)
|
||||
- [Spring Boot Gradle Plugin](https://docs.spring.io/spring-boot/gradle-plugin/index.html)
|
||||
- [Spring Boot 4.0 Migration Guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide)
|
||||
- [Modular Monolith: A Primer (Kamil Grzybek)](https://www.kamilgrzybek.com/blog/posts/modular-monolith-primer)
|
||||
- [ArchUnit(可选的架构规则静态检查工具)](https://www.archunit.org/)
|
||||
- [ArchUnit](https://www.archunit.org/)
|
||||
|
||||
Reference in New Issue
Block a user