Slices:微服务的合适规模

发布: (2026年1月19日 GMT+8 05:49)
11 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您将其翻译成简体中文并保留原有的格式、Markdown 语法以及技术术语。谢谢!

粒度陷阱

每个采用微服务的团队最终都会碰到同一道墙:服务应该有多大?

  • 如果划得太小,你会被网络调用、分布式事务和部署复杂性淹没。

    • 你原本简单的“获取用户资料”操作现在涉及五个服务,其中三个只是数据库表的代理。
    • 延迟会叠加。
    • 调试变成考古。
  • 如果划得太大,又回到了单体。

    • 不同团队相互踩踏。
    • 部署需要协调。
    • “微”服务的“微”字变得讽刺。

标准建议——“每个有界上下文对应一个服务”“服务应可独立部署”——听起来合理,却没有提供可操作的指引。

  • 一个上下文在哪里结束,另一个在哪里开始?
  • 什么才算“可独立部署”?

团队在两端摇摆,把*“太小”的服务重构为更大的服务,又把“太大”*的服务拆分。循环往复,因为根本问题仍未得到解答:决定正确边界的因素是什么?

问题不在于大小,而在于边界。

一个定义良好的边界具有以下特性:

  1. 清晰的契约——调用方确切知道可以请求什么以及会收到什么。
  2. 显式的依赖——组件声明它需要从外部获取的内容。
  3. 内部自由——实现细节可以改变而不影响调用方。

大小是由边界决定的,而不是相反。当一个组件完全拥有它的边界——即满足其契约所需的一切都在内部,而所有外部的访问都通过显式依赖进行时,它的规模就是恰当的。

大多数微服务设计失败的原因在于它们基于以下方式划定边界:

  • 技术层次(API 网关、业务逻辑、数据库访问)
  • 组织结构(团队所有权)

这两种方法都无法产生稳定的边界,因为它们都没有聚焦于组件之间的实际契约。

介绍 Slices

Slice 是由其契约定义的可部署单元。你只需编写一个带有单一注解的接口:

@Slice
public interface OrderService {
    Promise createOrder(CreateOrderRequest request);
    Promise getOrder(GetOrderRequest request);
    Promise cancelOrder(CancelOrderRequest request);
}

就这么简单。注解处理器会生成其余所有内容——工厂方法、依赖注入、部署元数据。

接口即边界

  • 方法定义契约 —— 每个方法接受一个请求并返回 Promise 类型的响应。
  • 请求/响应类型显式 —— 没有隐藏参数,也没有隐式上下文。
  • 默认异步 —— Promise 同时处理成功和失败路径。

实现位于该接口之后。实现可以很简单,也可以很复杂,调用其他 slice,或完全自包含。边界本身并不关心实现细节。

Slices 运行在 Aether 上,这是一个围绕 slice 合约设计的分布式运行时。你无需配置服务发现、序列化或跨 slice 通信——Aether 会根据 slice 接口的声明自动处理。只要集群存活,所有跨 slice 调用最终都会成功;运行时会透明地管理重试、故障转移和恢复。

Forge 为在真实条件下测试 slice 提供了开发环境——包括负载生成、混沌注入、后端仿真。你不必部署到预发布环境来观察 slice 在高压下的表现,只需在本地运行 Forge 并观察即可。

开发体验保持简洁:

  1. 编写 @Slice 接口。
  2. 实现接口。
  3. 使用 Forge 进行测试。
  4. 部署到 Aether。

注解处理器会生成所有样板代码——工厂、依赖注入、路由元数据。

显式依赖

传统服务架构把依赖埋在配置文件、环境变量或运行时发现机制中。你只能通过阅读代码、追踪网络调用,或等到生产环境出错后才发现服务需要什么。

Slices 在 接口中直接声明依赖

@Slice
public interface OrderService {
    Promise createOrder(CreateOrderRequest request);
    // Other methods...

    // 工厂方法显式声明依赖
    static OrderService orderService(InventoryService inventory,
                                     PaymentService payment) {
        return OrderServiceFactory.orderService(Aspect.identity(),
                                                inventory, payment);
    }
}

注解处理器会生成负责注入的工厂:

public final class OrderServiceFactory {
    private OrderServiceFactory() {}

    public static OrderService orderService(
            Aspect aspect,
            InventoryService inventory,
            PaymentService payment) {
        return aspect.apply(new OrderServiceImpl(inventory, payment));
    }
}
  • 工厂方法的签名 声明了依赖
  • 没有服务定位器、没有运行时发现、也不需要可能与实际不符的配置文件。
  • 依赖在编译时即可可见,并在部署前得到验证。

这种显式性很重要:

  • 通过阅读代码即可追踪依赖图。
  • 通过传入不同实现,可以使用替代品进行测试。
  • Forge 在启动任何东西之前会验证完整的依赖图。

三种运行时模式(相同的 Slice 代码)

模式描述
Ember单进程运行时,内部模拟多个集群节点。启动快,调试简单。适合本地开发。
ForgeEmber + 负载生成和混沌注入。无需部署即可测试 slice 在高压下的行为。
Aether完整的分布式集群。生产部署,提供全部弹性保证。

你的代码并不知道自己运行在哪种模式下。Slice 接口、实现以及依赖保持不变。

保持一致。运行时处理差异——无论 slice 之间的调用是进程内还是跨网络,都是透明的。

对开发工作流的影响

  1. 在 Ember 中编写 & 调试 – 快速的进程内执行。
  2. 在 Forge 中进行压力测试 – 注入负载和故障。
  3. 部署到 Aether – 生产级弹性。

在任何阶段都不需要重写合同、修改配置或为环境调整代码。slice 模型让你专注于 边界,而不是服务的规模。

切片及其“合适大小”

你会修改切片代码以适应环境吗?
当边界明确且部署灵活时,“合适大小”的问题就不复存在了。

何时切片是合适的大小?

  • 接口 – 捕获一组连贯的操作。
  • 依赖 – 准确反映切片实际需要的内容。
  • 实现 – 能够满足其契约。

没有最小或最大限制。

  • 一个认证切片可能只暴露两个方法
  • 一个订单处理切片可能暴露二十个方法。

大小取决于领域,而非关于代码行数或团队结构的任意规则。

可恢复性

切片出错是可恢复的:

情况会发生什么
拆分 一个变得过于复杂的切片边界会改变,但调用方只会看到新的接口
合并 被人为分离的切片调用方仍然只会看到单一接口;内部实现仅仅被合并。

重构切片仅仅是重构代码,而不是重写基础设施。

切片作为 JBCT 模式的归宿

每个切片方法都是一个 data‑transformation pipeline

Parse input (validated request types)

Gather data (dependencies, other slices)

Process (business logic)

Respond (typed response)

六大 JBCT 模式——Leaf、Sequencer、Fork‑Join、Condition、Iteration、Aspects——在 切片方法内部以及跨切片之间 进行组合。切片仅仅是围绕一组相关转换的 部署边界

为什么这种组合有效

  • JBCT 为切片内部提供 一致的结构
  • 切片 为切片之间提供 一致的边界

二者共同消除了导致架构熵增的两个主要来源:

  1. 实现模式不一致
  2. 服务边界不清晰

从服务粒度到契约中心设计

微服务粒度问题仍然存在,因为它提出了错误的问题:

  • 错误的问题:服务应该有多大?” – 没有好的答案。
  • 正确的问题:组件之间的契约是什么?” – 可以给出精确的答案。

Slices 将关注点从规模转向边界

  1. 定义接口。
  2. 显式声明依赖。
  3. 让部署拓扑适应 运营需求,而不是决定代码结构。

结果

  • 边界 通过构造方式 清晰可见
  • 依赖 通过设计方式 显而易见
  • 部署灵活性 无需重写代码即可实现。

属于 Java Backend Coding Technology——一种编写可预测、可测试后端代码的方法论。

Back to Blog

相关文章

阅读更多 »