快捷搜索:  汽车  科技

微服务怎么设计数据(如何为微服务准备好代码)

微服务怎么设计数据(如何为微服务准备好代码)对于层之间的通信,我们可以使用数据传输对象(DTO)。为了简化,我们可以使用主要属于服务层的领域模型。如果我们想明确分离(并严格遵循单一职责原则),我们也可以为 Controller 和 Repository 层创建额外的 DTO。将代码内部解耦为层对于代码演化非常有用。我们将它们分为三层:避免外部状态可能是域模型逻辑的最典型限制。现在,您可以看到最后一张图片,并看到该模型对任何其他工件没有任何依赖关系。因此,那里的逻辑不能调用任何其他服务,当然也不能在数据库中存储任何东西(或自身)。最好不要间接传递外部状态(例如,通过将域模型逻辑的参数值传递到模型中)。那里提供的每个参数都会增加内存消耗,并且这些模型可以大量存在。此外,参数值和域数据有些混杂。第二步是将组件分成 Controller、Service 和 Repository 层。我们还应该以这种方式在构建级别上将它们分开:控制器 -&g

您是否正在考虑调整微服务架构?以下是准备代码库的三个步骤。

是否在考虑是否要适配微服务架构?如果你选择适应它,你知道怎么做吗?我强烈建议首先重构代码库,使其为微服务做好准备。即使您决定(暂时)不采用微服务,也最好经历这段代码演变。它有很多优点,也只有几个缺点。让我们来看看它。

首先:我们使用 Java 11 和 Spring Framework,这被认为是行业标准。当您甚至考虑调整微服务时,强烈建议您使用 Spring,或者最好使用 Spring Boot。

组件/模型分离

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

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

零件
  • 是一个 Spring Bean——我们可以使用注入、AOP 和其他 Spring 的东西。
  • 是一个单例——在 Spring Context 中只有一个实例。
  • 是无状态的——不持有任何状态/任何价值。
  • 是可配置的——无状态异常是一种配置可能性,可以由 Spring 注入或在运行时可选地更改。
  • 应该使用好的术语——我们使用 Spring,所以我们使用 Spring 术语:Controller、Service、Repository 作为后缀(见下文)。
模型
  • 不是 Spring Bean — 不可能进行弹簧注入。
  • 是一个 POJO 类——具有数据字段和相关的域模型逻辑。
  • 是不可变的——所有字段都是最终的,集合不可修改,是创建的构造函数。
  • 可以克隆 — 当您需要修改对象时。使用 .withXXX 方法。
  • 具有有效的创建 - 它不应该在无效状态下创建。在构造函数中创建时应检查所有条件(NotNulls 等)。
  • 仅包含域模型逻辑——仅与模型数据逻辑相关,没有外部状态相关逻辑。通常是格式化、解析、验证、简单计算等。
领域模型逻辑限制

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

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

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


微服务怎么设计数据(如何为微服务准备好代码)(1)


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

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

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

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

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

使用控制器/服务/存储库层的原因
  • 允许可更换的控制器和存储库。
  • 从业务代码中清除控制器/存储库,反之亦然。
  • 代码职责分离。
端口/适配器架构

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

微服务怎么设计数据(如何为微服务准备好代码)(2)

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

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

微服务怎么设计数据(如何为微服务准备好代码)(3)

使用端口/适配器架构的原因
  • 解决了分层架构的缺点——Controller/Repository 都是可替换的。
  • 强制松散耦合的应用程序组件比层架构更多。
  • 删除层之间不需要的依赖关系。
  • 将您的应用程序与前端和后端选择等细节完全分离。

而已。通过这三个步骤,如果您愿意,您应该改进您的代码库,以便为微服务做好充分准备。更重要的是,随着时间的推移,它很容易维护和保留在代码中。最后,它很简单,您可以快速将初级加入您的团队。

猜您喜欢: