InfoQ如何基于 DDD 构建微服务?( 二 )


注意:必须理解子域和界限上下文之间的区别 。 子域属于问题空间 , 即我们的业务要如何看待问题 , 而界限上下文属于解决方案空间 , 即我们将如何实施问题的解决方案 。 理论上 , 每个子域可能有多个界限上下文 , 尽管我们努力每个子域只提供一个界限上下文 。
微服务和界限上下文如何关联
现在 , 微服务适用于哪些地方?每个界限上下文都能映射到对应的微服务吗?不一定 。 我们来看看原因 。 在某些情况下 , 界限上下文的边界或轮廓可能会非常大 。
InfoQ如何基于 DDD 构建微服务?
本文插图
图 2:界限上下文和微服务
考虑上面的例子 。 定价界限上下文有三个不同的模型:价格(Price)、定价项(Priced items) 和折扣(Discounts) , 分别负责目录项的价格、计算列表项的总价以及各自使用的折扣 。 我们可以创建一个包含上述所有模型的单一系统 , 但它可能是一个不合理的大型应用程序 。 如前所述 , 每个数据模型都有其不变性和业务规则 。 随着时间的推移 , 如果我们不小心的话 , 这个系统就可能会变成一个大泥球 , 界限模糊 , 职责重叠 , 甚至很可能会回到我们开始的地方——单体应用 。
对这个系统建模的另一种方法是将相关模型分离或分组到单独的微服务中 。 在 DDD 中 , 这些模型(价格、定价项和折扣)被称为聚合(Aggregates) 。 聚合是由相关模型组成的自包含模型 。 我们只能通过已发布的接口来变更聚合的状态 , 并且聚合可以确保一致性 , 而且不变量可以始终保持良好状态 。
在形式上 , 聚合是关联对象的集群 , 被视为数据变更的单元 。 外部引用仅限于指定聚合的一个成员 , 即聚合根 。 在聚合的边界内需应用一组一致性规则 。
InfoQ如何基于 DDD 构建微服务?
本文插图
图 3:定价上下文中的微服务
同样 , 没有必要将每个聚合都建模为一个不同的微服务 。 事实证明 , 图 3 中的服务(聚合)就是如此 , 但这不一定是一个规则 。 在某些情况下 , 在单个服务中托管多个聚合可能是有意义的 , 特别是在我们不完全了解业务领域的情况下 。 需要注意的一点是 , 一致性只在单个聚合中才能得到保证 , 并且聚合只能通过已发布的接口进行修改 。 任何违反这些规则的行为都有增加应用程序变成一个大泥球的风险 。
上下文映射
另一个基本工具是上下文映射 , 同样 , 它也是来自领域驱动设计 。 一个单体应用通常由不同的模型组成 , 这些模型大多是紧密耦合的 , 模型之间可能知道彼此的实现细节 , 变更一个模型可能造成另一个模型的副作用等等 。 当你分解单体应用时 , 确定这些模型(在这里是聚合)及其关系是至关重要的 。 上下文映射可以帮助我们做到这一点 。 它们用于识别和定义各种界限上下文和聚合之间的关系 。 在上面的例子中 , 界限上下文定义了模型的边界(价格、折扣等等) 。 而上下文映射定义了这些模型之间以及不同上下文之间的关系 。 在确定了这些依赖关系之后 , 我们就可以确定下来实现这些服务的团队之间的正确协作模型了 。
对上下文映射的完整探索不在本文的讨论范围之内 , 但我们将用一个示例来说明 。 下图显示了处理电子商务订单支付的各种应用程序 。
购物车上下文负责订单的在线授权;订单上下文处理订单履行完成后的支付流程 , 如结算;联络中心处理任何异常情况 , 如支付重试和变更订单使用的支付方式 。 为了简单起见 , 我们假设所有这些上下文都是作为单独的服务实现的 , 所有这些上下文封装了同一个模型 。 请注意 , 这些模型在逻辑上是相同的 。 也就是说 , 它们都遵循相同的统一领域语言——支付方式、授权和结算 。 只是它们是不同上下文的一部分 。