【问题标题】:DDD - Validation for existence of entity in other bounded contextsDDD - 验证实体在其他有界上下文中的存在
【发布时间】:2020-03-05 13:58:14
【问题描述】:

我有一个问题,关于在有界上下文中验证实体是否存在时的最佳做法是什么。这甚至是 DDD 中的有效方法吗? BC 本质上应该是自包含的部署(即您不应该依赖另一个可能不可用的 BC)。

我的项目中有 2 BC - 成分和食谱。该企业销售散装原料,但也销售使用所述原料的预配置食谱。现在这些都是单独的 BC,每个都有自己的成分实体。

Recipe 是一个聚合根,它有一个成分列表的子实体。在将一种成分添加到 Recipe BC 中的成分列表之前验证该成分是否存在于 Ingredient BC 中是否有意义?

成分只能通过将发布事件的成分 BC 进行修改,并且配方 BC 将订阅并更新其自己的成分以进行任何更改(即价格/名称)。为了使其有效,该成分必须是有效的。那么如何保持这些 BC 之间的一致性呢?我是否将域服务注入到配方 BC 中并在添加之前验证成分是否存在?我也在使用 CQRS,因此我可以将服务直接注入处理程序而不是工厂来创建食谱(或者这是否是使用域服务的正确方法?)。

有点迷失了,如果这是一个有效的问题。

【问题讨论】:

    标签: domain-driven-design cqrs


    【解决方案1】:

    一般来说,您的食谱应该只关心成分的唯一标识符,而不是其详细信息。配方的一致性不需要成分的详细信息。

    我会假设某些操作(例如,用户与 UI 交互)会将配料添加到食谱中。我还假设可以添加的成分来自仅返回有效成分的查询。除非您有理由担心某事/某人会破坏此过程,否则您可能会花时间解决不太可能成为真正问题的问题。

    如果这实际上是一个真正的问题,那么是的,您可以在添加成分之前验证成分是否存在。然而,这可能最好在配方 BC 的边界附近,在命令验证器中完成。

    限界上下文是概念性的——它(通常)不由单个类表示。我提到这个是因为你问

    我是否将域服务注入到配方 BC 中。 . . ?

    您并没有真正“注入”到 BC。同样,如果您确实需要此验证,您可能会有一个验证类通过 API 或数据库查询成分 BC 以确保它存在。

    食谱 BC 将订阅并更新其自己的成分以进行任何更改(即价格/名称)。

    这应该不是必需的。食谱对它的每一种成分都有一个引用,因此当您查询食谱时,您会同时查询成分列表和这些成分的详细信息。根据您的设置,这可能是 SQL 连接或其他东西(根据您的设置,可以通过多种不同的方式完成此操作)。您通常应避免在收据 BC 中缓存成分详细信息,除非您对性能有特别关注。缓存总是会增加复杂性。

    当您继续执行 CQRS 时,您会发现的一件事是,许多您通常认为是“命令问题”的问题实际上在查询方面更容易解决。

    【讨论】:

    • 在我的例子中,成分在它自己的 BC 中,因为那是主域。食谱是另一个 BC 有自己的成分版本。我当然可以只引用配方 BC 中成分的 ID 列表(并通过查询在 UI 中获取 ingr),但我假设 BC 应该自己工作(即即使成分 BC 不可用-食谱仍然有效并且有效)。您如何解决我有有效的成分 ID 列表但我无法获取其中任何一个的情况?在整个上下文是自包含的 DDD 中,这是不是一个不关心的问题?
    • 关于配方​​ - 我确实必须关心成分,因为配方的成本是所有成分成本 + 开销的总和(并且没有成分就不可能存在配方,因为它没有任何意义) .所以我必须确保存在的有效成分被传递。这就是为什么我在配方 BC 中有一个成分实体,因为它用于验证配方 + 成分的聚合是否正确并将其保留在自己的 BC 中。有没有更好的方法来解决这个问题?我通常读取数据重复不是 DDD 的问题,因为它试图解决域而不是存储。
    • (回复您的第二条评论)。请参阅我的回答中的最后一句话。总结食谱的成本是一个阅读方面的问题!如果您说如果配方的成本高于 $XX 则无法创建配方,那么您可能需要在创建配方时在命令中传递成分成本。
    • (回复您的第一条评论)BC 可以是独立的,但仍然相互依赖以使整个系统正常工作。如果不是这种情况,您将需要在每个其他 BC 中缓存来自每个 BC 的每一位数据,以确保您始终可以检索您需要的数据。
    • 谢谢!这是有道理的,因为我错误地接近它。考虑到这些知识,我将修改我的方法。一般来说,您在什么时候查询另一个 BC?如果我使用 CQRS,查询所有成分(即我的配方中存在的 ID)之类的事情是否发生在查询处理程序的应用程序级别?这也可能适用于您关于实体验证的观点(即在应用程序级别的命令中验证?)。您在边界处提到,这似乎是放置该逻辑的合适位置(通过使用域服务来查询其他 BC API)。
    【解决方案2】:

    感觉你 BC 的界限是错误的。尝试找到他们正在为您的业务执行的业务功能。不要将实体的名称与限界上下文的名称映射为 1 到 1 个名称。在聚合之间,您无法保证跨国一致性。即使你检查你的成分,它也可能在那里,但在 1 毫秒后它就消失了,你将继续执行你的逻辑,认为它在那里。我不明白为什么成分应该是一个单独的 BC 和食谱。它有什么价值。

    【讨论】:

    • 因为成分可以单独存在,因为它们是主要领域(要出售的散装成分)。如果要删除一种成分,我将引发一个域事件,该事件将根据其业务逻辑更新配方 BC。我正在稍微简化我的域模型,因为成分不仅仅是一个实体,而且对于整个域而言,它是聚合根。
    • 没有这样的限界上下文成分。尝试找到您业务中有价值的部分。它可能是订购、交付、付款。这三件事是完成您的业务所必需的。因为用户应该能够组装他的订单,支付它,并得到它。并且食谱和成分实体可能存在于所有这些实体中。在 Orders 中,您的成分是 orderItem,在 payment BC 中,您的成分是 paymentProcess 的一部分,在 Delivery 中,您的成分是运输聚合的一部分。想象一条规则说你不应该选择 DHL 运送奶酪,因为他们不允许运送它。
    • 因此,在 UI 上,当您添加新成分时,它应该分布在所有需要该数据以充分发挥其业务能力的微服务(限界上下文)中。
    猜你喜欢
    • 2021-11-07
    • 1970-01-01
    • 2011-10-03
    • 2020-06-14
    • 1970-01-01
    • 2014-08-22
    • 2021-11-19
    • 1970-01-01
    • 2010-10-24
    相关资源
    最近更新 更多