1. 概述

微服务架构近年来已成为主流。其核心特征之一是模块化、隔离性强、易于扩展。✅
但微服务并非孤岛,它们需要协同工作并交换数据。为了实现这一点,我们通常会定义数据传输对象(DTO)作为服务间通信的载体。

本文将探讨在微服务架构中,如何合理地共享 DTO,避免常见的“踩坑”问题。⚠️


2. 领域对象与 DTO 的分离

微服务通常围绕业务领域建模,领域模型(Domain Model)封装了核心业务逻辑。为了避免将领域复杂性暴露给外部,我们不会直接暴露领域对象。

取而代之的是:
✅ 通过 REST 接口暴露 DTO
✅ 在服务内部将 DTO 转换为领域对象进行处理

这种分层设计能有效解耦外部接口与内部实现,提升系统可维护性。

下图展示了典型的分层架构中,DTO 与领域对象的流转关系:

application architecture with dtos and service facade original

该图来自 Xebia 的服务门面与 DTO 实践,清晰地展示了服务层、DTO 与领域模型之间的协作流程。


3. 微服务间 DTO 的共享方式

以“用户下单”为例,涉及 CustomerOrder 两个服务。Customer 服务需要调用 Order 服务创建订单。

假设请求数据如下:

"order": {
    "customerId": 1,
    "itemId": "A152"
}

服务之间通过契约(Contract)进行通信,通常以 JSON 格式传输。在 Java 中,这个契约由 OrderDTO 类表示:

public class OrderDTO {
    private int customerId;
    private String itemId;

    // constructor, getters, setters
}

💡 除了 JSON,也可使用 Protocol Buffer(Protobuf)定义契约。此时需共享 .proto 文件,用于生成客户端桩代码(Client Stubs)。


3.1 使用客户端模块(Client Module)共享 DTO

当多个服务需要调用同一服务时,简单地将 DTO 放在公共库中会带来严重问题。

问题场景

假设有一个支付服务,它需要的 CustomerDTO 是:

public class CustomerDTO {
    private String firstName;
    private String lastName;
    private String cardNumber;
    // getters, setters
}

而另一个配送服务需要的 CustomerDTO 是:

public class CustomerDTO {
    private String firstName;
    private String lastName;
    private String homeAddress;
    private String contactNumber;
    // getters, setters
}

如果共用一个 CustomerDTO,字段会不断膨胀,导致“一个 DTO 被多方绑架”的问题。❌

解决方案:为每个微服务创建独立的 client 模块

推荐做法是:
✅ 每个微服务拆分为 clientserver 两个模块

结构如下:

order-service
|__ order-client
|__ order-server

order-client 模块包含:

order-service
└──order-client
     OrderClient.java
     OrderClientImpl.java
     OrderDTO.java

其中:

  • OrderClient 是调用接口:
public interface OrderClient {
    OrderResponse order(OrderDTO orderDTO);
}
  • 实现类使用 RestTemplate 发起调用:
String serviceUrl = "http://localhost:8002/order-service";
OrderResponse orderResponse = restTemplate.postForObject(
    serviceUrl + "/create", 
    request, 
    OrderResponse.class
);
  • customer-service 引入 order-client 作为依赖:
[INFO] --- maven-dependency-plugin:3.1.2:list (default-cli) @ customer-service ---
[INFO] The following files have been resolved:
[INFO]    com.baeldung.orderservice:order-client:jar:1.0-SNAPSHOT:compile
  • order-server 提供对应的接口:
@PostMapping("/create")
public OrderResponse createOrder(@RequestBody OrderDTO request) {
    // 处理逻辑
}

这种方式的优势

高内聚低耦合:每个 client 模块只服务于特定调用场景
契约变更可控:DTO 修改仅影响使用该 client 的服务
避免代码冗余:无需在每个调用方重复定义 DTO
易于测试:client 模块可独立测试和版本管理

⚠️ 注意:client 模块应保持轻量,仅包含 DTO 和调用接口,避免引入过多业务逻辑。


4. 总结

在微服务架构中,DTO 的共享方式直接影响系统的可维护性和扩展性。简单粗暴地将 DTO 放入公共库是典型的反模式。❌

推荐做法是:
✅ 为每个微服务创建独立的 client 模块,封装 DTO 与调用逻辑
✅ 将 client 模块作为依赖引入调用方,实现契约的“按需共享”

这种方式带来的好处显而易见:

  • ✅ 消除 DTO 代码重复
  • ✅ 契约变更影响范围可控
  • ✅ 提升服务间的解耦程度

配套的 Spring Boot 示例代码已发布在 GitHub:
https://github.com/eugenp/tutorials/tree/master/spring-cloud-modules/spring-cloud-bootstrap


原始标题:How to Share DTO Across Microservices