微服务架构中的耦合
Coupling in microservices architecture
在微服务架构中开发应用程序时,我偶然发现了有关服务之间耦合的问题。
这是一个典型的产品订购应用程序。拥有一种将作为产品目录运行的服务似乎是合理的。我们的 JavaScript 客户可以向此服务询问可用产品并在浏览器中显示它们。当用户点击产品时,我们必须创建订单。我们想在另一个服务中管理订单。因此创建了一个订单——这意味着用户 X 订购了产品 Y。在技术层面上,我们只是将用户 ID 和产品 ID 保存在数据库中。
总而言之,我们有:
产品服务
产品class:
产品 ID、产品名称
订单服务
订单class:
订单 ID、产品 ID、用户 ID、订单日期
现在让我们想象以下场景 - 在 JavaScript 客户端中,我们想列出用户订购的所有产品。订单服务为给定用户提供产品 ID 列表。但是用户关心产品名称,而不是 id。不幸的是,订单服务对产品名称一无所知。
我们可以通过几种方式解决这个问题:
简单假设客户端负责获取它需要的信息。因此,在调用订单服务并获取产品 ID 列表后,它会再次调用产品服务,传递产品 ID 并获取相应的产品名称作为响应。然后客户端将两个响应组合成有用的东西。这种方法使我们的服务保持清洁,不会将领域知识从一项服务泄漏到另一项服务。但它需要在客户端做更多的工作。
Orders 服务在请求订购产品时在后端调用 Products 服务。它检索产品名称,组合包含 orderDate 和 productName 的响应并将其发送给客户端。客户要做的就是呈现数据。这种方法的缺点是订单服务现在获得了比必要更多的产品知识。
订单服务中有关产品名称的重复信息。创建订单时,我们不仅传递产品 ID,还传递产品名称。这意味着 Order class 将如下所示:
订单 class:
订单 ID、产品 ID、产品名称、用户 ID、订单日期
现在我们可以轻松提供有关订单的完整信息,而无需额外调用产品服务。此外,这次订单服务对产品有太多的了解。好处是订单服务可以提供相关数据,即使产品服务中断。
是否可以将这些方法中的任何一种视为最佳实践?或者有不同的解决方案吗?
在 eShopOnContainers 示例 (https://github.com/dotnet-architecture/eShopOnContainers) 中,他们在创建订单项时添加了以下字段:
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1)
这会复制目录信息,但是因为这是记录的时间点快照。这比 linking 到原始数据要安全得多。
此时,在用户旅程中,您处于订单域中,如果用户正在查看订单,您可以向目录提供 link。但是,订单服务应该能够独立运行。链接回目录以获取产品信息可防止订单服务拥有自己的数据。
目录中的价格发生变化是噩梦......历史订单会发生什么变化?
在微服务架构中开发应用程序时,我偶然发现了有关服务之间耦合的问题。
这是一个典型的产品订购应用程序。拥有一种将作为产品目录运行的服务似乎是合理的。我们的 JavaScript 客户可以向此服务询问可用产品并在浏览器中显示它们。当用户点击产品时,我们必须创建订单。我们想在另一个服务中管理订单。因此创建了一个订单——这意味着用户 X 订购了产品 Y。在技术层面上,我们只是将用户 ID 和产品 ID 保存在数据库中。
总而言之,我们有:
产品服务
产品class:
产品 ID、产品名称
订单服务
订单class:
订单 ID、产品 ID、用户 ID、订单日期
现在让我们想象以下场景 - 在 JavaScript 客户端中,我们想列出用户订购的所有产品。订单服务为给定用户提供产品 ID 列表。但是用户关心产品名称,而不是 id。不幸的是,订单服务对产品名称一无所知。
我们可以通过几种方式解决这个问题:
简单假设客户端负责获取它需要的信息。因此,在调用订单服务并获取产品 ID 列表后,它会再次调用产品服务,传递产品 ID 并获取相应的产品名称作为响应。然后客户端将两个响应组合成有用的东西。这种方法使我们的服务保持清洁,不会将领域知识从一项服务泄漏到另一项服务。但它需要在客户端做更多的工作。
Orders 服务在请求订购产品时在后端调用 Products 服务。它检索产品名称,组合包含 orderDate 和 productName 的响应并将其发送给客户端。客户要做的就是呈现数据。这种方法的缺点是订单服务现在获得了比必要更多的产品知识。
订单服务中有关产品名称的重复信息。创建订单时,我们不仅传递产品 ID,还传递产品名称。这意味着 Order class 将如下所示: 订单 class:
订单 ID、产品 ID、产品名称、用户 ID、订单日期
现在我们可以轻松提供有关订单的完整信息,而无需额外调用产品服务。此外,这次订单服务对产品有太多的了解。好处是订单服务可以提供相关数据,即使产品服务中断。
是否可以将这些方法中的任何一种视为最佳实践?或者有不同的解决方案吗?
在 eShopOnContainers 示例 (https://github.com/dotnet-architecture/eShopOnContainers) 中,他们在创建订单项时添加了以下字段:
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1)
这会复制目录信息,但是因为这是记录的时间点快照。这比 linking 到原始数据要安全得多。
此时,在用户旅程中,您处于订单域中,如果用户正在查看订单,您可以向目录提供 link。但是,订单服务应该能够独立运行。链接回目录以获取产品信息可防止订单服务拥有自己的数据。
目录中的价格发生变化是噩梦......历史订单会发生什么变化?