【问题标题】:Duplicate business logic in front-end with ddd microservice back-end前端的业务逻辑与 ddd 微服务后端重复
【发布时间】:2019-06-30 15:01:53
【问题描述】:

这是一个涉及现实世界的抽象问题。

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

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

问题在于,处理 SPA 的团队希望在 SubscriptonsService 中有一个 /CreditCards 端点,该端点返回可以在订阅模型中使用的用户的所有有效信用卡。他们不想在 SPA 中实现与 SubscriptionsService 本身相同的业务验证规则。

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

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

【问题讨论】:

    标签: domain-driven-design microservices solid-principles separation-of-concerns ddd-service


    【解决方案1】:

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

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

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

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

    【讨论】:

    • CreditCardSpecification 有助于解耦两种上下文的想法是一个有趣的想法。虽然规范可以存在于共享包中,但 CC 实体本身不必被订阅的上下文知道。你可以实现类似validCardSpec = subscriptionService.validCreditCardSpecificationFor(...); validCards = creditCardService.allMatching(validCardsSpec); 的东西。我不确定这是否比两个上下文之间的直接依赖更好,但这是一个值得探索的想法。
    • 虽然我同意不应该重复逻辑的观点,但我也觉得添加这个特定的端点会使服务更多地耦合在一起,并且违背了单一职责原则,这是微服务的核心。设定的先例也需要考虑,因为这是我的问题的简化示例。我们的服务环境目前有 100 多个服务,最终可能会有数百个服务。
    【解决方案2】:

    最好的设计方法是什么?是否应进行业务验证 前端和后端之间的逻辑是否重复?如果这 将新端点添加到 SubscriptionsService?我们是否应该尝试 简化业务逻辑?

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

    但我验证的是使用 CC 完成的操作,而不是 CC 本身,即验证“此 CC 是否对订阅有效”。该验证在 S-BC 中进行。

    如果 SPA 想要检索“他/她可以用于订阅的用户的 CC”,这是 S-BC 的功能。

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

    【讨论】:

    • 我假设您的示例中的“BC”是有界上下文。所以你会说订阅的有效信用卡列表是订阅有界上下文的一部分?
    • 是的,BC 是有界上下文。重新阅读我的答案并看看我如何在我的某个项目中进行验证,我意识到我没有把它放在答案中。我会尽快更正。无论如何订阅BC对我来说并不清楚。你在那里有什么实体?而订阅操作的合约又是怎样的呢?谢谢
    • 您好,重新考虑设计后,我决定不更改答案。我想我会那样做。是的,订阅的有效信用卡列表将由 S-BC 返回,后者从 CC-BC(上游)获取它们。您可以通过调用 CC-BC 的 REST API 或在 S-BC 数据库中维护信用卡副本来集成 S-BC,该副本会根据 CC-BC 发布的事件进行更新。 S-BC 必须了解信用卡。它从 CC-BC 获取它们并将它们返回给客户端 (SPA)
    • 更重要的是,如果您有其他方式来支付订阅费用,您还必须将 S-BC 与它们集成,并在 S-BC 中验证它们。在 S-BC 中,您将拥有“抽象”“支付方式”,信用卡就是其中之一。
    【解决方案3】:

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

    【讨论】:

    • 我关心的不是 SubscriptionService 是否正在验证这些数据本身。绝对应该。我担心的是它是否应该有自己的 /CreditCards 端点,它只返回对订阅有效的信用卡实体。
    【解决方案4】:

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

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

    这实现了 3 件事,首先订阅服务不会在卡牌一段时间后完全被淘汰,它可以参考自己的表并无论如何都可以工作。其次,您的域代码将更加集中,因为它只需要处理对解决当前问题真正重要的信用卡属性。最后,如果您的卡片商店甚至可以具有在商店中计算和具体化的额外领域特定属性。

    必填 Fowler 链接:Bounded Context Pattern

    【讨论】:

    • 我喜欢这个答案。这类似于我从@robert-bräutigam 得到的出色答案,但它也有强制性的 fowler 链接来总结事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    • 2017-08-08
    • 2010-12-03
    • 2016-09-26
    相关资源
    最近更新 更多