CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件架构师入门/课时/API 与集成
RawBlame
课时目录12 节

软件架构师入门

  1. 01软件架构是什么
  2. 02架构师软技能
  3. 03设计模式与原则
  4. 04架构风格
  5. 05数据架构
  6. 06API 与集成
  7. 07安全架构
  8. 08云与设计模式
  9. 09网络基础
  10. 10技术栈选型
  11. 11治理与方法论
  12. 12综合练习

API 与集成

REST/GraphQL/gRPC 与消息。

30 分钟

API 与集成

这一课你将能做到

  1. 按 REST 规范设计一套课程资源 API,包含版本、分页、错误码和幂等设计
  2. 说清楚 REST、GraphQL、gRPC 三者的适用场景,并为本站的内部服务通信选型
  3. 用消息队列改造本站的「下单 → 开课 → 通知」链路,并解决重复消费问题

为什么要学

系统变多之后,「服务之间怎么说话」就成了架构的核心问题。API 设计是一次性投入、长期负债——一个糟糕的 API 上线后,改也不是(有调用方依赖),不改也不是(天天被吐槽)。

人话:API 是服务之间的合同。签合同时稀里糊涂,后面扯皮无穷无尽。好的 API 让调用方「不看文档也能猜对」,坏的 API 让维护者「天天在群里答疑」。

先建立图像

本站集成方式的演进:

【阶段一:内部直接调用】
OrderService ──方法调用──→ CourseService(同一个 JVM)

【阶段二:REST 同步调用】
前端 ──REST──→ API 服务 ──REST──→ 第三方(支付/短信)

【阶段三:同步 + 异步混合】
                 ┌──REST──→ 支付网关
前端 ──REST──→ 订单服务
                 └──MQ──→ RocketMQ ──┬──→ 开课服务(消费)
                                      ├──→ 通知服务(消费)
                                      └──→ 积分服务(消费)

跟着做

步骤 1:设计本站课程 REST API

GET    /api/v1/courses?page=1&size=20&track=java    # 列表(分页+过滤)
GET    /api/v1/courses/sarch-intro                  # 详情(slug 而非自增 id)
POST   /api/v1/orders                               # 创建订单
GET    /api/v1/orders/{id}                          # 订单详情
PATCH  /api/v1/orders/{id}                          # 部分更新(如取消)

设计要点逐一落实:

// 1. 统一响应结构
public record ApiResponse<T>(int code, String message, T data) {}

// 2. 错误码分层:2xx 成功 / 4xx 客户端错 / 5xx 服务端错
//    业务错误用 4xx + 业务码,不要一律 200 然后在 body 里塞 success:false

// 3. 幂等:创建订单用幂等键防重复提交
@PostMapping("/orders")
public ApiResponse<Order> create(
        @RequestHeader("Idempotency-Key") String idemKey,
        @RequestBody CreateOrderReq req) {
    // 先查 idemKey 是否已处理,是则直接返回上次结果
    return orderService.createIdempotent(idemKey, req);
}

步骤 2:三种 API 风格对比选型

维度RESTGraphQLgRPC
协议HTTP/JSONHTTP/JSONHTTP2/Protobuf
数据获取固定结构客户端指定字段固定结构
性能中中高(二进制、多路复用)
适用对外公开 API多端聚合查询内部服务间
本站用法开放 API管理后台聚合页订单↔库存内部调用

步骤 3:消息队列改造下单链路

// 生产者:订单服务本地事务 + 发送事件
@Transactional
public void createOrder(CreateOrderReq req) {
    Order order = orderMapper.insert(req);
    // 事务内写 outbox 表,保证不丢
    outboxMapper.insert(new OrderCreatedEvent(order.getId()));
}

// 消费者:开课服务保证幂等
@RocketMQMessageListener(topic = "order_created", 
                         consumerGroup = "enroll-group")
public class EnrollConsumer implements RocketMQListener<OrderCreatedEvent> {
    public void onMessage(OrderCreatedEvent event) {
        // 幂等:用 order_id 做唯一键,重复消息直接返回成功
        if (enrollMapper.existsByOrderId(event.getOrderId())) return;
        enrollMapper.insert(...);
    }
}

讲清楚要点

  1. REST 的核心是资源,不是动词:URL 是名词(/orders),动作用 HTTP 方法表达(POST 创建、DELETE 取消)。POST /api/createOrder 是 RPC 风格披了 REST 的皮。
  2. 幂等性是分布式 API 的生命线:网络超时重试、用户双击、MQ 重投递都会让同一个请求到达多次。写接口必须幂等:天然幂等用 PUT/DELETE,非天然幂等用幂等键或唯一约束。
  3. 同步 vs 异步的选择标准:调用方需要立刻拿结果→同步(REST/gRPC);可以延迟处理、要削峰、要解耦→异步(MQ)。本站「发短信通知」明显异步,「查询库存能否下单」必须同步。
  4. GraphQL 不是 REST 的替代品:GraphQL 解决的是「前端要的字段和后端给的不匹配」,代价是缓存难做、限流难做、N+1 查询。内部管理后台可用,公开 API 慎用。
  5. API 版本策略:URL 版本(/v1/)最直观;Header 版本最优雅但调试麻烦。破坏性变更(删字段、改语义)必须升版本,非破坏性(加字段)不用。

你可能会懵的地方

  • POST 和 PUT 到底什么区别? 面试经典题。PUT 是幂等的「整体替换」——同样的请求执行 1 次和 10 次结果一样;POST 不保证幂等——调 10 次可能创建 10 条数据。创建用 POST,整体更新用 PUT,局部更新用 PATCH。

  • gRPC 性能好,为什么对外 API 还用 REST? gRPC 的 Protobuf 是二进制,浏览器原生不支持(需要 grpc-web 代理);且 REST 的 JSON 人类可读、curl 直接调试、生态成熟。对外 API 的可调试性和兼容性优先于性能。

  • MQ 消息丢了怎么办? 三道防线:生产者确认(ACK)+ 本地 outbox 表兜底;Broker 持久化(刷盘 + 副本);消费者手动 ACK,处理失败重试 + 死信队列人工介入。

  • 消息顺序性怎么保证? 全局有序代价极高,通常只保证「分区有序」:同一订单的消息按 orderId 哈希到同一队列,单线程消费。本站订单状态流转(待支付→已支付→已开课)就用这个方案。

  • 什么时候考虑 ESB/BPM? 只在集成大量遗留系统(SAP、老式 SOAP 服务)时考虑。新建系统不要用 ESB,它会把所有复杂度集中到总线上,成为单点和瓶颈。

错法对照

错法问题正解
所有接口返回 200 + success 字段丧失 HTTP 语义,网关/监控无法识别错误用正确的状态码,body 里放业务码
GET /api/deleteOrder?id=1GET 是安全方法,会被预取/爬虫触发删除用 DELETE 方法
同步链条:A→B→C→D 四个服务任一环节慢全链路慢,可用性相乘骤降非必要环节改异步,同步链不超过 2 跳
MQ 消费者不做幂等重试导致重复开课、重复发券业务唯一键 + 去重表/唯一索引
接口直接返回数据库实体字段泄露、表结构变更直接炸 APIDTO 隔离,领域模型不外泄

卡点急救

  1. 调用第三方接口经常超时:必加超时时间(连接超时 + 读超时分开设)、重试(指数退避、最多 3 次)、熔断(连续失败 10 次断开 30 秒)。Resilience4j 一个注解搞定。
  2. MQ 堆积了几十万条消息:先扩容消费者(加实例、加线程);再排查消费逻辑是否有慢操作(把 DB 批量写改成批量 insert);最后评估消息是否可丢弃(日志类可直接删)。
  3. 前端说接口太慢:用 Arthas 或 SkyWalking 看链路耗时分布——是 DB 慢(加索引/缓存)、外部调用慢(异步化)、还是序列化慢(大对象拆分)。

面试常问

  • RESTful API 设计原则有哪些? 答题框架:1) 资源导向,URL 用名词复数;2) HTTP 方法表达动作(GET 查询/POST 创建/PUT 替换/PATCH 局部更新/DELETE 删除);3) 状态码语义化;4) 无状态,认证信息每次请求携带;5) 版本管理 /v1/;6) 分页、过滤、排序用 query 参数。加分项:提到 HATEOAS 但说明实际很少用,提到幂等性设计。

  • 如何设计一个幂等接口? 四个层次:1) 天然幂等:查询、删除、PUT 替换;2) 唯一约束:DB 唯一索引兜底(如订单号);3) 幂等键:客户端生成 Idempotency-Key,服务端 Redis 记录处理结果,重复请求直接返回缓存结果;4) 状态机:只有合法状态流转才执行(如只有 PENDING 订单能支付)。举例本站支付回调:用支付流水号做唯一键。

  • 同步调用和消息队列怎么选? 答题矩阵:需要即时结果→同步;失败要立刻感知→同步;流量有峰谷→MQ 削峰;下游处理慢→MQ 缓冲;要广播给多个消费方→MQ 发布订阅;调用关系要解耦(下游可随意增删)→MQ。举例本站:下单扣库存同步(必须知道成败),发短信、加积分、更新推荐用 MQ。

自测三问

  1. 本站的「课程进度上报」接口(App 每 30 秒上报一次)适合用什么方式?为什么?
  2. 为什么 MQ 消费者必须做幂等?本站开课服务的幂等键应该用什么?
  3. 对外公开的课程 API 和内部订单↔库存调用,分别该选什么协议?

小结一句话

API 设计的本质是「定义服务间的合同」:REST 管对外、MQ 管异步解耦、gRPC 管内部高性能,而幂等性和版本管理是所有合同的兜底条款。

延伸

  • 《RESTful Web APIs》(Leonard Richardson)
  • gRPC 官方文档
  • RocketMQ 官方文档:消息可靠性章节
  • 下一课:安全架构——OWASP Top 10、认证授权与零信任边界。
加载本节练习…
← 上一课数据架构下一课 →安全架构