Skip to content

从 Dubbo 到 HTTP:关于内部系统解耦的一次架构思考 ​

一、为什么重新思考内部系统调用? ​

目前,我们的多个内部业务系统之间主要通过 Dubbo 进行服务调用。

这种方式已经运行了较长时间,也能够满足现有业务需求。

但随着系统不断发展,我逐渐发现了一些问题。

例如,一个业务系统为了调用另一个系统的接口,需要引入对方提供的 Dubbo API JAR,还需要配置相应的 Dubbo 和 Nacos 依赖。

原本只是一次简单的业务查询,却可能让两个应用在 Java 依赖、框架版本和构件发布方面产生关联。

而我一直希望逐步升级现有系统的技术栈。

在这个过程中,我开始思考一个问题:

既然这些系统能够独立开发、独立部署,为什么它们还需要互相依赖对方的 Java 构件?

1.1 一个新系统的技术选型 ​

公司曾经新建一个业务系统,需要与现有系统进行交互。

根据我后来和该同事沟通,负责开发的同事原本希望采用 Spring Boot 3.x,但最后还是选择了 Java 8 和 Spring Boot 2.x。

原因是先使用了Spring Boot 3,然后发现需要和现有系统进行dubbo接口(全部使用java8)交互,后来被迫为Spring Boot 2.x。

这件事情让我忽然意识到一个问题:

新系统的技术选型,有可能受到现有系统技术栈的约束。

对于一个独立开发和部署的新应用,我原本希望它能够根据自身需求选择 Java 和 Spring Boot 版本,而不是因为需要调用其他业务系统,就必须沿用已有系统的技术体系。

这并不是说 Dubbo 协议天然要求使用 Java 8 或 Spring Boot 2。

真正需要反思的是,我们现有的调用方式依赖共享 Java API JAR,而这些构件又可能携带特定的框架依赖。

1.2 我自己也作出过类似的技术取舍 ​

除了该同事的经历,我自己开发某个新项目时,也曾选择 Java 8 和 Spring Boot 2.7,而没有采用 Java 17 和 Spring Boot 3。

不过,这个案例与前面有所不同。

该项目并不需要调用公司的其他内部系统,也没有必须兼容其他业务 Dubbo API JAR 的要求。

我选择旧技术栈,主要是考虑后续维护者的维护成本。

既然现有系统普遍使用 Java 8,我就不希望因为自己开发了一个新项目,让接任者还需要额外维护一套 Java 17 环境。

这属于我主动作出的工程取舍,而不是被某个 Dubbo 依赖直接限制。

它说明,技术选型不仅取决于框架能力,还受到人员、维护条件和现有环境的影响。

但我也逐渐意识到,如果每个独立系统都因为已有系统的环境而不断放弃新版本,整个系统体系的技术演进就会越来越困难。

这促使我进一步思考:对于真正独立的业务系统,是否应该建立更清晰的技术边界?

二、共享 Dubbo API JAR 带来的实际问题 ​

2.1 API JAR 不够纯净 ​

在我看来,一个供其他系统调用的 Dubbo API JAR,理想情况下应该只包含接口定义、请求和响应 DTO,以及少量必要的 API 依赖。

但实际情况并非如此。

我们使用过的部分业务 API JAR,还会传递引入 Hutool、Dubbo、Redisson Starter 等其他依赖。

其中有一次经历让我印象比较深刻。

A 服务增加了 Redisson Starter,并发布了相关 JAR。

B 服务实际上不使用 Redisson,但由于依赖 A 提供的业务 API JAR,被动引入了相关依赖,最终导致 B 服务启动失败。

从 B 服务的角度看,这很难接受。

B 没有使用 Redisson,却需要承担 A 引入 Redisson 后产生的启动问题。

尤其是 Spring Boot Starter。

它与普通 DTO 不同,可能包含自动配置,在应用启动过程中参与 Spring 容器的初始化。

调用方只是希望使用一个业务接口,却可能因为引入对方的 JAR,改变自身的运行依赖环境。

2.2 最终是 B 通过 exclusions 解决了问题 ​

当时的处理方式,并不是要求 A 立即修改其 API JAR。

最终,我在 B 服务的 Maven POM 中,通过 exclusions 排除了不需要的 Redisson 依赖,使 B 服务恢复正常启动。

从技术上看,这是一个直接有效的解决办法。

但它也暴露了跨系统协作中的一个现实问题。

你可以向 A 系统的开发人员提出建议,但对方不一定会按照你的建议修改代码。

不同业务系统有不同的负责人和开发计划。

A 的开发人员可能认为当前依赖组织方式没有问题,或者认为这个问题的优先级不高。

但 B 已经无法正常启动。

B 不可能一直等待 A 修改。

所以,B 只能在自己的工程中增加依赖排除配置,先解决眼前的问题。

这个经历让我开始反思:

为什么一个系统的内部依赖选择,会变成另一个系统必须处理的故障?

如果以后 A 又引入其他不必要的 Starter,B 是否还需要继续增加排除规则?

当然,不一定每次都会发生启动失败,但这种被动处理其他系统依赖变化的方式,并不是我希望长期维持的架构关系。

2.3 业务 API JAR 还传递引入了其他系统的 API JAR ​

除了基础设施依赖,我还发现过另一种不合理的依赖关系。

A 服务只是希望调用 B 的 Dubbo 接口,因此引入了 B 提供的 API JAR。但检查 Maven 依赖时发现,B 的 API JAR 又引入了 C 系统的 Dubbo API JAR。

更关键的是,B 对外提供的接口参数和返回值并没有使用 C 的 DTO,也不存在必须向 A 暴露 C 接口类型的需求。

这意味着,A 明明只需要 B 的业务接口,却因为 B 的 Maven 依赖声明,被动获得了 C 的接口构件,以及它可能继续引入的其他依赖。

text
A 服务
└── B 的 Dubbo API JAR
    └── C 的 Dubbo API JAR(B 对外契约并不需要)

这不是业务调用关系必须如此,而是 API 模块的依赖边界没有收敛好。

在实际工程中,我见过调用方为了引入某个 Dubbo API JAR,不得不在 Maven POM 中配置多项 exclusions,排除其他业务系统的 API JAR、公共模块和不需要的组件。

当然,exclusions 只能证明调用方有意排除这些依赖;要确认某一项依赖在具体版本中的实际传递路径,仍需查看 Maven Dependency Tree。但 B 的 API 模块引入了对外契约并不需要的 C API JAR,这件事本身已经足以说明问题。

B 内部需要依赖 C,不代表调用 B 的 A 也需要依赖 C。

如果每个系统都这样组织 API 模块,原本清晰的 A、B、C 业务边界,就会在 Maven 依赖树中逐渐连成一张不必要的构件依赖网。

这也是为什么我后来不再满足于让调用方不断增加排除规则,而是希望从系统之间的协作方式上减少这种依赖。

2.4 SNAPSHOT 构件相互覆盖 ​

除了传递依赖,我们还遇到过 Maven SNAPSHOT 构件相互覆盖的问题。

当时,测试环境和生产环境都使用 SNAPSHOT 版本。

开发人员可以在本地人工执行 Maven deploy,将构件发布到共享 Maven 私服。

而且,大家使用的是同一套 Maven settings.xml。

因此,不同开发人员、不同分支,都可能发布相同 Maven 坐标的构件。

我们曾经遇到过这样的情况。

生产服务原本正常运行。

测试环境修改了某个 API DTO 字段,然后执行 Maven deploy。

后来,生产环境因为 Nacos 相关配置调整,需要重新构建并发布应用。

这次生产发布本来不涉及上述业务接口字段。

但重新构建时,却使用了测试分支发布的 SNAPSHOT JAR。

也就是说:

生产代码没有修改相关业务接口,但重新构建后的发布产物却可能引入测试分支的接口变化。

这里需要明确,已经运行的 Java 进程不会因为 Maven 私服更新了构件,就自动加载新的 JAR。

真正发生问题的环节是重新构建和发布。

当同一个 SNAPSHOT 坐标可以对应不同代码内容时,构建结果就可能受到其他分支发布行为的影响。

这种覆盖还可能反过来发生。

例如,生产分支发布了同一个版本号的构件,测试分支之后获取到的可能又是生产版本。

开发人员明明已经执行过 deploy,却发现其他应用没有使用自己预期的修改。

这就让问题更加难以排查。

2.5 我尝试用版本命名进行隔离 ​

为了缓解这个问题,我曾在某业务系统中采用两类 Maven 版本:

text
v{date}-prod-SNAPSHOT
v{date}-test-SNAPSHOT

目的是在版本名称中区分生产和测试构件。

但实际执行时,我发现这套方案仍然依赖开发人员主动遵守约定。

例如:

开发人员从 prod 分支创建 feat-xxx。

新分支继承了生产代码中的 Maven 版本:

text
v20261015-prod-SNAPSHOT

按约定,开发人员应该修改日期,并将 prod 调整为 test。

但实际情况可能是:

  • 有人忘记修改日期。
  • 有人忘记修改环境标识。
  • 还有人完全没有修改版本,就直接执行 mvn deploy。

由于大家使用同一套发布凭据,缺乏强制性的权限隔离,这些错误操作仍然可能发生。

因此,我逐渐认识到:

依靠人工修改 Maven 版本号,不能从根本上保证不同开发分支和环境之间的构件隔离。

当然,可以通过 CI、仓库权限、版本校验等方式继续治理。

SNAPSHOT 本身也不是不可使用。

但这件事让我进一步思考:如果 B 只是为了调用 A 的一个业务接口,为什么要参与 A 的 Maven 构件发布和版本管理过程?

三、为什么不继续治理 Dubbo API JAR? ​

最初,我并没有打算替换 Dubbo。

我的第一种方案,是把 API JAR 收敛成纯 POJO 加少量必要的 Dubbo 依赖。

例如:

text
business-api.jar
├── Service 接口
├── Request DTO
├── Response DTO
└── 必要的 Dubbo API 依赖

这样就能避免将 Redisson Starter、Hutool 等不必要的依赖传递给调用方。

而且,我们原有的 Dubbo API JAR 使用的是专门定义的 DTO,并没有直接复用数据库 Entity。

因此,原有设计已经在业务数据模型方面建立了一定隔离。

如果只讨论依赖污染,继续治理 API JAR 完全是一种合理方案。配合稳定的构件版本、发布权限隔离和构建检查,它也能够解决目前暴露的大部分 Maven 问题。

但后来我发现,解决已有故障与选择希望长期维护的系统边界,是两个不同的问题。

因为 B 服务仍然需要引入 A 提供的 Java 构件。

即使这个 JAR 只包含 DTO 和接口,B 仍然需要通过 Maven 获取它、升级它、管理它。

A 的业务接口发生变化,B 可能需要更新依赖版本。

如果共享构件仍然使用缺乏隔离的 SNAPSHOT,也需要继续面对发布治理问题。不过,这并不是 Dubbo 特有的缺陷,也完全可以通过规范构件发布来改善。

另外,还存在一个技术上值得考虑的潜在风险:Java 字节码兼容性。

3.1 Java 版本不同,也可能形成新的约束 ​

假设 A 使用 Java 17,B 使用 Java 8。

如果 A 提供的 API JAR 使用 JDK 17 编译,并生成 Java 17 字节码,那么 Java 8 应用将无法加载这些 Class。

这种情况下,可能出现:

text
java.lang.UnsupportedClassVersionError

Java 8 支持的 Class 文件版本是 52,而 Java 17 默认编译生成的 Class 文件版本是 61。

当然,可以单独将 API 模块编译为 Java 8 兼容字节码,例如配置 --release 8。

但这意味着还需要专门维护共享 API 模块的编译目标和依赖兼容性。

需要强调的是,这只是我在重新思考架构时想到的一种潜在技术风险,并不是此前新项目或业务系统中已经发生过的故障。

它进一步说明:只要应用之间共享 Java Class,就可能需要考虑对方的 Java 运行时兼容能力。

但如果两边只通过 HTTP 和 JSON 交互,就不需要相互加载对方的 Class 文件。

3.2 我想解决的是跨系统的技术依赖 ​

因此,我开始重新审视这个问题:

如果 A 和 B 已经是两个独立部署的应用,为什么它们还要通过共享 Java 构件建立调用关系?

我并不认为纯 POJO API JAR 是错误设计。只包含接口和 DTO 的独立契约模块,配合严格的依赖与发布治理,可以支撑不同技术版本的服务正常协作。

只是对于我希望建立的系统边界而言,我仍然倾向于不让 B 通过 Maven 获取由 A 发布和维护的 Java 业务构件。即使它足够纯净,双方仍需要管理这一共享构件的版本与兼容性。

选择 HTTP + JSON,并不是因为它能够消除一切耦合,而是希望把双方需要共同遵守的部分收敛为业务接口契约。为此,我愿意接受调用方独立维护 DTO、接口版本管理以及契约验证等成本。

我希望不同业务系统能够尽可能独立选择 Java、Spring Boot 及其他基础组件,而不是因为共享业务 Java 构件而持续协调彼此的构建和依赖。

四、我希望建立怎样的系统边界? ​

我逐渐形成了一个想法:

系统要么在一起,要么不在一起。

这里的“在一起”,并不是单纯指代码位于同一个 Git 仓库,而是指它们属于同一个应用的构建、部署和维护边界。

如果几个模块本来就属于同一个应用,可以采用模块化单体的方式组织代码。

例如:

text
application
├── core
├── biz
├── infra
└── web

这些模块共享 POJO、工具类和内部接口,是正常的工程组织方式。

但如果 A、B、C 已经是独立部署的业务应用,那么我希望它们之间采用另一种协作方式。

即使同属一家公司,也应该将彼此视为独立应用。

4.1 第三方接口给我的启发 ​

平时对接第三方服务时,我们通常只需要根据对方公开的接口契约组织请求。

从调用方视角看,交互方式可以抽象为:

text
HTTP Method
    +
URL
    +
Token / 签名
    +
Request Body
    ↓
Response

其中,真正需要仔细处理的是请求和响应的数据结构。

有些接口只有少量字段,有些接口具有多层嵌套对象、数组、分页结构和复杂返回值。

但这些复杂性都可以通过接口文档或 JSON 契约描述。

调用方并不需要知道服务提供方内部使用什么开发语言、什么框架或什么数据库。

那么,我希望内部业务系统之间也采用类似的边界。

例如:

text
A 服务
  └── HTTP API
       ├── B 服务:独立调用
       ├── C 服务:独立调用
       └── D 服务:独立调用

A 只需要对外提供稳定的业务契约。

B、C、D 自己决定如何组织调用代码。

A 修改内部技术栈,不应该直接影响 B、C、D 的 Maven 依赖和构建。

4.2 不共享 JAR,不代表不共享契约 ​

我并不是希望系统之间完全没有耦合。

只要 B 需要调用 A,就必然依赖 A 的业务接口。

例如,请求字段、响应结构、错误码、字段语义以及接口可用性。

这些属于必要的业务契约。

我希望消除的是额外的实现依赖。

例如:

  • A 使用哪个版本的 Spring Boot。
  • A 是否使用 Redisson。
  • A 采用什么 ORM 框架。
  • A 的 Maven 模块如何拆分。
  • A 使用哪个 Java 编译版本。

这些内部实现细节,不应该要求 B 跟随调整。

4.3 DTO 重复是否值得接受? ​

不共享 API JAR 以后,确实会出现一定程度的 DTO 重复。

例如 A 提供一个复杂查询接口,B、C、D 都需要定义请求和响应对象。

这会增加一些代码。

但我认为,适度重复 DTO 可以接受。

因为重复定义接口适配对象,与重复实现业务逻辑不是同一回事。

业务逻辑仍然由 A 负责。

B、C、D 只是按照契约组织请求和解析响应。

而且,调用方不一定需要完整复制 A 的所有字段。

如果 B 只使用某几个响应字段,完全可以按照自身需要定义对象,前提是其 JSON 反序列化配置能够正确处理未知字段。

对于复杂嵌套对象,也可以根据接口文档或 JSON 示例生成 DTO。

如果未来接口数量增加,还可以让 A 提供 OpenAPI 规范,由调用方生成自己的 DTO 和 HTTP Client。

这样既能减少重复劳动,也不需要发布和引入 A 的业务 API JAR。

五、为什么考虑 HTTP? ​

除了 Java 构件依赖问题,跨地域部署也是我考虑 HTTP 的原因之一。

我们有部署在国内和海外不同地域的服务。

如果相关服务处于同一网络环境中,使用内网地址调用通常比较方便。

但跨地域部署后,不同 VPC 之间的内网地址并不天然互通。

如果为了调用内部 Dubbo 服务而直接开放公网端口,又需要额外考虑访问控制和安全问题。

当然,Dubbo 并不是只能使用内网,也不是没有安全能力。

跨地域网络同样可以通过 VPN、专线或其他私有网络互联方式解决。

HTTP 也并不天然比 Dubbo 安全。

我的考虑主要来自现有运维条件和系统边界。

我希望通过相对统一的 HTTP API 入口,结合 HTTPS、访问控制等机制,降低调用方对服务提供方内部网络和 RPC 技术体系的依赖。

例如,通过反向代理统一管理服务入口,并根据实际部署条件配置 IP 白名单。

这里需要注意,IP 白名单只能限制网络访问来源,不能单独代替调用方身份认证和业务授权。

这些问题需要在真正实施时进一步设计。

5.1 已经进行的一次小范围尝试 ​

目前,我已经选择了一个重要性较低的查询接口,将其从 Dubbo 调用调整为 HTTP。

这次调整并不是因为该接口原来的 Dubbo 调用发生了故障。

它是我主动选择的一次低风险尝试。

我希望先确认,在不共享对方业务 API JAR 的情况下,两个系统能否通过 HTTP 完成原有查询业务。

但这只是初步实践。

复杂分页、嵌套 DTO、资金操作以及更广泛的跨系统调用,还没有完成系统性迁移。

因此,不能将这次小范围尝试描述成 Dubbo 到 HTTP 的全面改造成果。

六、接口兼容性应该由谁负责? ​

如果各业务系统不再共享 Java API JAR,那么接口契约管理就变得更加重要。

我希望内部 HTTP API 能够参考成熟的第三方服务接口管理方式。

核心原则是:

服务提供方应当承担必要的兼容责任,而不是因为自身接口调整,就要求所有调用方立即同步修改。

6.1 每个接口独立维护版本 ​

我倾向于让每个接口独立维护版本,而不是整个系统使用统一的版本号。

例如:

text
POST /internal/student/query

通过请求参数区分:

text
version=1
version=2

服务端根据版本选择对应的契约处理逻辑。

这样,一个接口升级到 V3,并不意味着其他接口也必须升级。

而且,我更倾向于保持 URL 稳定。

当接口需要增加不兼容的新版本时,可以先保留原有接口入口,在服务端兼容不同的请求和响应结构。

当然,不同版本的业务逻辑仍然应当适当隔离,避免将所有兼容分支混在同一个方法中。

6.2 不传版本时如何处理? ​

对于已经存在的 HTTP 接口,我认为不能突然要求所有调用方必须传 version。

因为原有调用方可能根本没有这个字段。

因此,在 V1 仍然有效时,可以采用:

请求情况服务端行为
不传 version默认执行 V1
version=1执行 V1
version=2执行 V2
不支持的版本返回明确的错误信息

这保证了新增版本管理能力,不会立即破坏旧调用方。

但默认版本并不一定永远是 V1。

在 V1 完成下线,并确认所有调用方能够接受新契约之后,可以将默认行为切换到 V2。

需要明确的是,默认版本的切换本身也是接口行为变化,必须纳入迁移通知。

6.3 参考第三方服务的客户迁移管理方式 ​

在实际对接第三方服务的过程中,我观察到一种值得借鉴的做法。

服务提供商发生接口变更时,会按客户和接口进行通知。

客户完成代码调整、联调和生产上线后,服务提供方会记录对应的迁移状态。

我认为,公司内部业务系统也可以采用类似机制。

例如,A 提供某个 HTTP 接口,B、C、D 是调用方。

当 A 准备从 V1 升级到 V2 时:

  1. A 发布 V2,并在兼容期内保留 V1。
  2. A 通知相关调用方,说明接口变化和预定下线时间。
  3. B、C、D 分别完成代码修改、联调和生产发布。
  4. A 记录各个调用方的迁移完成情况。
  5. 全部调用方确认完成后,再安排旧版本下线。

即使大家都属于同一家公司,也不应该因为内部沟通方便,就省略接口变更的通知和确认过程。

6.4 人工确认为主,调用数据为辅 ​

如果后续建立统一的调用方身份识别机制,还可以记录各个系统实际使用的接口和版本。

例如:

text
调用方:B
接口:/internal/student/query
请求版本:V1
实际执行版本:V1

这类数据能够辅助识别尚未完成迁移的系统。

但我更倾向于以调用方的人工确认为主要依据。

因为某些业务接口调用频率很低。

例如,月末对账、周期性报表、异常补偿等场景,可能很长时间才会触发一次。

没有观察到旧版本请求,不一定说明旧版本已经没有调用方。

因此,我希望将接口迁移管理分为两部分:

人工确认迁移状态,自动调用数据辅助核验。

当所有调用方完成迁移、相关验证结束后,再删除旧版本逻辑,并按照已通知的计划调整默认版本。

这套接口版本和客户迁移管理机制,目前仍然是我的设计思路,不代表现有内部系统已经全面实现。

七、实际落地还需要解决哪些问题? ​

前面讨论的主要是架构边界和设计理念。

我并不认为将 Dubbo 调用替换成 HTTP,就能自动解决所有问题。

真正落地时,还需要考虑很多技术细节,例如:

  • 身份认证与业务授权。
  • 跨地域访问控制。
  • HTTP 超时与失败处理。
  • 重试和幂等性。
  • 接口限流及可用性。
  • 日志、链路追踪与运行监控。
  • 复杂 DTO、分页与错误响应契约,以及必要的契约测试。
  • 新旧版本共存及调用方迁移。

例如,一个查询接口超时后,可能允许在一定条件下重试。

但资金操作接口就不同。

如果调用方发起付款请求后超时,并不能直接判断服务提供方没有执行成功。

此时如果简单重试,可能造成重复业务操作。

因此,不能将所有 Dubbo 接口机械地替换成 HTTP 请求,然后认为改造就已经完成。

另外,HTTP 迁移与 Maven SNAPSHOT 治理也是两个不同的问题。

取消跨系统业务 API JAR 依赖,能够消除这一类由共享 API 构件产生的风险。

但只要其他公共组件仍然通过缺乏隔离的 SNAPSHOT 发布流程管理,类似的构件覆盖问题仍然可能发生。

因此,构件发布治理并不能因为改用 HTTP 就完全忽略。

这些都是未来实际推进迁移时需要考虑的问题。

本文暂时不展开详细的实施方案。

因为我现在更关注的是:我们应该如何划定不同业务系统之间的架构边界。

八、结语:我真正希望改变的是什么? ​

重新梳理这段经历,我发现自己最初并没有打算替换 Dubbo。

我只是希望 API JAR 更加纯净,不要向调用方传递不必要的运行时依赖。

但在实际开发中,我们遇到了 Redisson Starter 导致其他系统启动失败的问题。

最终,受影响的调用方只能通过 Maven exclusions 自行处理,因为并不能保证服务提供方一定愿意调整依赖结构。

我还发现,某些 API JAR 会无必要地引入其他业务系统的 API JAR。调用方只是需要一个系统的接口,却被动承受了第三个系统的构件依赖。

我们还遇到了 SNAPSHOT 构件相互覆盖的问题。

不同分支使用相同版本号发布 JAR,导致生产环境重新构建时,可能意外引入测试分支的接口变化。

与此同时,新系统的技术选型、现有系统的升级需求,以及跨地域部署带来的网络问题,又让我不断重新思考系统间的调用方式。

最初,我考虑将 Dubbo API JAR 收敛为纯 POJO 和少量必要依赖。

但后来,我逐渐觉得还不够。

纯净的 API JAR 配合规范的构件发布,确实能够解决前面提到的多数故障,也能够支持不同版本 Java 和 Spring Boot 的系统协作。但我仍然希望进一步减少独立业务系统之间共享 Java 构件的关系。

所以,我开始思考一个更根本的问题:

如果两个系统能够独立开发、独立构建和独立部署,为什么它们不能像对接第三方服务一样,通过稳定的业务契约交互?

我并不是认为 Dubbo 不好。

通过合理的依赖治理、构件发布管理和网络配置,Dubbo 完全可以继续支撑内部服务调用。

但结合我们目前的系统现状,以及我希望达到的独立性,我更倾向于让不同业务系统通过 HTTP 和 JSON 契约进行协作。

在这种模式下,服务提供方负责对外接口的稳定性与兼容性。

调用方不再需要引入服务提供方的 Java 业务构件,可以减少构件依赖对自身技术选型和构建的直接约束;与此同时,双方仍然需要维护业务接口契约的兼容性。

当然,这种独立性不是没有成本。

调用方可能需要自行维护 DTO 和 HTTP Client,服务提供方也需要承担接口版本管理、兼容期支持和调用方迁移管理等工作。

真正实施时,还需要解决安全、重试、超时、幂等和监控等问题。

但我认为,这些成本值得认真评估。

因为我真正希望得到的是:

只要对外接口契约保持兼容,A 系统升级 Spring Boot、调整内部组件或重新组织 Maven 模块,就不应仅仅因为共享业务 Java 构件而要求 B 系统跟随修改和重新构建。

反过来,B 也应该拥有相同的独立性。

目前,我们只是将一个重要性较低的查询接口改成了 HTTP,整体架构调整仍然处于初步探索阶段。

我不打算为了验证一个理念,就一次性重写所有正在正常运行的 Dubbo 调用。

更合理的方式,是从风险较低的场景逐步尝试,在真实项目中积累经验,再决定是否扩大迁移范围。

最终,我希望建立的是一条更加清晰的系统边界:

同一个应用内部可以共享实现;不同应用之间,即使属于同一家公司,也应该主要通过稳定的业务契约协作,而不是共享彼此的技术实现依赖。