如何让代码为微服务做好准备

组件/模型分离

第一步是将整个代码库分成组件和领域模型。组件包含大部分业务逻辑,领域模型包含数据、状态和领域模型逻辑。如果您在建筑层面将它们分开会有所帮助——组件依赖于模型,但反之则不然。

什么是组件和模型,它们的属性是什么?

零件

  • 是一个 Spring Bean——我们可以使用注入、AOP 和其他 Spring 东西。
  • 是一个单例——在 Spring 上下文中只有一个实例。
  • 是无状态的——不持有任何状态/任何价值。
  • 是可配置的——无状态中的一个例外是可以由 Spring 注入或在运行时可选地更改的配置可能性。
  • 应该使用好的术语——我们使用 Spring,所以我们使用 Spring 术语:Controller、Service、Repository 作为后缀(见下文)。

模型

  • 不是 Spring Bean——不可能进行弹簧注入。
  • 是一个 POJO 类——具有数据字段和相关领域模型逻辑。
  • 是不可变的——所有字段都是最终的,集合是不可修改的,用于创建的构造函数。
  • 可克隆——当您需要修改对象时。使用 .withXXX 方法。
  • 具有有效的创建 - 它不应在无效状态下创建。在构造函数中创建时应检查所有条件(NotNulls 等)。
  • 仅包含领域模型逻辑——仅在逻辑上与模型数据相关,没有外部状态相关逻辑。通常是格式化、解析、验证、简单计算等。

领域模型逻辑限制

避免外部状态可能是领域模型逻辑最典型的限制。现在,您可以看到最后一张图片并看到该模型对任何其他工件没有任何依赖性。因此,那里的逻辑不能调用任何其他服务,当然也不能在数据库中存储任何东西(或它本身)。最好不要间接传递外部状态(例如,通过将域模型逻辑的参数值传递到模型中)。在那里传递的每个参数都会增加内存消耗,并且这些模型可以大量存在。此外,参数值和域数据有些混合。

使用组件/模型分离的原因

  • 简单模式——易于定义、易于理解、易于维护。
  • 可以是线程安全的——与并发集合或锁等其他方法一起使用。
  • 多租户环境——允许对多个租户请求使用同一个实例。
  • 节省一些内存——内存中的实例更少、寿命更短。
  • 不可变类——更适合垃圾收集器。
  • 提高防错性——归功于无状态和不变性。
  • 标准化术语——可以更快地理解代码。

控制器/服务/存储层

第二步是将组件分成控制器、服务和存储库层。我们还应该以这种方式在构建级别上将它们分开:Controller -> Service -> Repository。

将代码内部解耦为层对于代码演进非常有用。我们将它们分为三层:

  1. 控制器——它包含所有前端通信,如 REST API。如果您想使用其他技术(例如 GraphQL)或批处理,而服务中的业务逻辑保持原样,它允许您替换控制器层。
  2. 服务——这包含所有业务逻辑。除非您更改输入/输出合同,否则更改不应影响任何其他层。
  3. 存储库——这包含所有后端通信,如 SQL 数据库等。允许您用另一种持久技术替换存储库层。

对于层之间的通信,我们可以使用数据传输对象(DTO)。为了简化,我们可以使用主要属于服务层的领域模型。如果我们想清楚地分开(并严格遵循单一职责原则),我们也可以为 Controller 和 Repository 层创建额外的 DTO。

正如我们所说,这些层应该在构建级别上分开(例如,通过 Maven 工件)。确保没有不需要的依赖项泄漏到错误的层(例如,REST API 泄​​漏到服务层,甚至数据库库泄漏到控制器层)。此外,确保控制器和存储库与业务逻辑无关,反之亦然。使用这个经验法则:总是问自己控制器/存储库是否可以按原样替换为新控制器/存储库,并且不需要重新实现从业务逻辑到新控制器/存储库的任何内容。

现在事情变得有点复杂,所以包应该被命名。最佳实践似乎是工件的名称应该从包名称的一部分创建,以便能够轻松地从 Stacktrace 中找到工件(例如,工件的名称:来自 eu.manta.controller.user 的 manta-controller)。我们可以避免包名称前缀,因为在大多数情况下,它是显而易见的,而后缀(在层名称之后)因为它包含工件。

使用控制器/服务/存储库层的原因

  • 允许可更换的控制器和存储库。
  • 从业务代码中清除控制器/存储库,反之亦然。
  • 代码责任分离。

端口/适配器架构

第三步是在层之间创建契约——端口——并反转服务层和存储层之间的依赖方向。在构建级别,依赖项如下所示:Controller -> InputPort <- Service -> OutputPort <- Repository。这种架构模式也被称为六边形架构,由 Alistar Cockburn 创建。

这是分层架构的下一步。解决了Service层依赖Repository这个明显的问题,所以Repository是不可替换的。此外,我们这里有两个新工件:输入端口和输出端口。它们包含定义层间契约的接口。如果我们认为它们有用或者我们想要遵守单一职责原则,它们也可以包含 DTO。

端口

  • 明确定义输入合同——通过接口和可选的 DTO。
  • 从Service层的角度来看,它是输入。
  • 它们可以从控制器层调用。
  • xxxPort接口应该由Service层来实现。
  • 更简单的测试——我们可以避免使用 Controller/Repository 或用 mock 代替。

适配器

  • 明确定义输出合同。
  • 从Service层的角度来看,它的输出。
  • 它们可以从服务层调用。
  • xxxAdapter需要Repository层来实现。

猜你喜欢

转载自blog.csdn.net/wouderw/article/details/128089513
今日推荐