使用 ddd 微服务后端在前端复制业务逻辑

Duplicate business logic in front-end with ddd microservice back-end

这是一个具有现实世界意义的抽象问题。

我有两个微服务;我们称它们为 CreditCardsServiceSubscriptionsService.

我还有一个应该使用 SubscriptionsService 的 SPA,以便客户可以订阅。为此,SubscriptionsService 有一个端点,您可以在其中 POST 订阅模型来创建订阅,在该模型中是 creditCardId 指向应该支付的信用卡订阅。有一些业务规则说明您是否可以使用所述信用卡进行订阅(到期时间超过 12 个月,它是 VISA 等)。这些特定的业务规则与 SubscriptionsService

问题是在 SPA 上工作的团队想要 SubscriptonsService 中的 /CreditCards 端点,returns 可以在订阅中使用的用户的所有有效信用卡模型。他们不想在 SPA 中实施与 SubscriptionsService 本身相同的业务验证规则。

对我来说,这似乎违背了微服务设计的核心 SOLID 原则;特别是关注点分离。我也问自己,这是要开什么先例?我们是否必须向 OrdersService 或任何其他可能使用 creditCardId 作为其模型的 属性 的服务添加一个 /CreditCards 端点?

所以主要问题是:设计这个的最佳方法是什么?是否应该在前端和后端之间复制业务验证逻辑?这个新端点是否应该添加到 SubscriptionsService?我们应该尝试简化业务逻辑吗?

这是一个完全公平的请求,你应该提供那个端点。如果您定义了哪些 CC 对您的服务有效的规则,那么您也应该提供处理它的所有帮助。

逻辑不应重复。这往往会使系统无法维护。

这与 SOLID 关系不大,虽然 SRP 也会说,如果你负责某件事,那么任何相关的逻辑也属于你。这个问题不能与您的服务分开,因为它在那里定义。

作为一个解决方案,我可能会考虑是否可以链接到 CC 服务,因为您已经有了一个。我是否可以使用构建的查询将客户端重定向到 CC 服务以获取所有相关的 CC,而实际上在订阅服务中不知道它们。

What is the best way to design this? Should the business validation logic be duplicated between the frontend and the backend? Should this new endpoint be added to the SubscriptionsService? Should we try to simplify the business logic?

从我的角度来看,我会将 "Subscription BC" (S-BC) 与 "CreditCards BC" (CC-BC) 整合在一起。 CC-BC 是上游,S-BC 是下游。您可以使用 CC-BC 中的 REST API 或消息队列来完成。

但我验证的是用CC完成的操作,而不是CC本身,即验证"is this CC valid for subscription"。该验证在 S-BC 中进行。

如果 SPA 想要检索 "the CCs of a user that he/she can use for subscription",这是 S-BC 的功能。

客户端 (SPA) 应调用 S-BC API 以使用该功能,S-BC 执行从 CC-BC 获取 CC 并进行验证的功能。

即使事实来源是域模型,最终您也必须在 域模型级别,验证仍然可以在域模型级别(服务器端)和 UI(客户端)。 客户端验证为用户提供了极大的便利。它节省了他们原本会花费的时间 等待可能 return 验证错误的服务器往返。在商业方面,甚至一些 每天的几分之一秒乘以数百次加起来就是大量的时间、费用和 挫折。直接和即时的验证使用户能够更有效地工作和 产生更优质的输入和输出。 正如视图模型和域模型不同一样,视图模型验证和域模型 验证可能类似但服务于不同的目的。如果您担心 DRY(不要 Repeat Yourself principle),考虑到在这种情况下代码重用也可能意味着耦合,在企业应用程序中,不要将服务器端耦合到客户端比耦合更重要 遵循DRY原则。 (NET-Microservices-Architecture-for-Containerized-NET-Applications 一书)

在微服务和 DDD 中,订阅服务应该有一个信用卡端点,如果它是与订阅的有界上下文相关的数据。

信用卡端点提供的数据模型可能与您在信用卡服务本身中发现的数据模型略有不同,因为在订阅的上下文中,信用卡的外观或行为可能有所不同。订阅服务可能会有信用卡 table 或后备存储,以支持存储自己的信用卡模式,并参考一些真实来源以保持数据的良好状态(例如关于公交车上的卡事件的消息,或其他一些机制)。

这可以实现 3 件事,首先,如果卡片出现故障一段时间,订阅服务不会完全失效,它可以引用自己的 table 并且无论如何都可以工作。其次,您的域代码将更加专注,因为它只需要处理对解决当前问题真正重要的信用卡属性。最后,如果您的卡片商店甚至可以拥有额外的领域特定属性,这些属性是在商店中计算和实现的。

义务福勒 link : Bounded Context Pattern