DDD层和外部Api

时间:2019-07-06 03:41:42

标签: domain-driven-design axon

最近,我一直在尝试使Web应用程序使用单独的图层。

如果我正确理解了这个概念,我就可以提取出来:

  1. 域层 这是我的核心域实体,聚合根,值对象所在的位置。我强迫自己使用纯域模型,这意味着我在这里没有任何服务定义。我在这里定义的唯一东西是存储库,它实际上是隐藏的,因为axon框架自动为我实现了。

  2. 基础设施层 这是轴突在域层为我的聚合实现存储库定义的地方

  3. 投影层 在这里实现了事件处理程序,以便使用MongoDB持久化读取模型的数据。除了事件模型(kotlin中的纯数据类)之外,它什么都不知道

  4. 应用层 这就是混乱的开始。

  5. 控制器层 这是我实现GraphQL / REST控制器的地方,该控制器层正在使用命令和查询模型,这意味着它具有有关域层命令以及投影层查询模型的知识。

正如我所提到的,混淆是从应用程序层开始的,让我用简化的示例进行一些解释。

考虑到我想要一个域模型来实现Pokemon战斗逻辑。我需要使用PokemonAPI来为我提供Pokemon名称统计等数据,这是我用来获取一些数据的外部API。

让我们说我将像这样实现域: (请记住,我已经扩展了此实现,因此它迫使我在自己的域中遇到了一些问题)

Pokemon {
  id: ID 
}
PokemonFight {
  id: ID
  pokemon_1: ID
  pokemon_2: ID

  handle(cmd: Create) {
    publish(PokemonFightCreated)
  }

  handle(cmd: ProvidePokemonStats) {
    //providing the stats for the pokemons
    publish(PokemonStatsProvided)
  }

  handle(cmd: Start) {
    //fights only when the both pokemon stats were provided
    publish(PokemonsFought)
  }

层之间的数据流就是这样。

用户-> [HTTP]->控制器-> [CommandGateway]->(应用程序|域)-> [EventGateway]->(应用程序|域)

让我们假设已经创建了两个宠物小精灵,而神奇宝贝之战的用例基本上是在创建时提供了统计信息,然后在提供了这些统计信息后,战斗会自动开始。

这种用例逻辑可以通过使用事件处理器甚至传奇来解决。

但是,正如您在PokemonFight聚合中看到的那样,存在[ProvidePokemonStats]命令,该命令基本上提供其统计信息,但是我的域不知道如何获取此类数据,该数据随PokemonAPI提供。

这使我有些困惑,因为用例需要在应用程序的两个层(因此,它使用外部api提供统计信息)以及域中都实现?域用例将仅使用纯域概念。但是我不应该为用例留一个地方吗?

如果我考虑一下,驻留在应用程序层中的唯一目的传奇/事件处理器就是向我的域提供适当的数据,以便它可以继续使用。因此,当外部API失败时,我将命令发送到域,然后它可以决定要做什么。

例如,我可以将每个传奇/事件处理器放入应用程序中,因此当我决定更改一些自动化流程时,我完全知道我需要编辑哪个模块以及在哪里找到它。

另一个混乱之处是我有多个域,并且我想创建一个使用多个域并在它们之间连接数据的用例,这使我立即想到,这应该是将使用域API的应用层控制用例,因为我不认为我应该在核心域中添加不同域的依赖关系。

TL; DR

  1. 如果过程需要一些外部API数据,则应负责在聚合之间实现自动化过程的哪一层(可以是单个层,但您知道我的意思)。

  2. 位于不同域/微服务中的聚合之间应由哪一层负责实施自动化过程。

在此先感谢您,如果我写的内容听起来令人困惑或文本太多,也深感抱歉,但是,我非常感谢有关DDD应用程序分层和组件正确位置的任何答案。

2 个答案:

答案 0 :(得分:1)

我会尽力澄清。如果您使用CQRS:

  • 在写端(命令):应用程序服务是命令处理程序。一个cmd处理程序访问域(存储库,集合等)以实现用例。

    如果用例需要从另一个有界上下文(微服务)访问数据,则使用基础结构服务(通过依赖项注入)。您可以在应用程序服务层中定义基础结构服务接口,并在基础层中定义实现。然后,该基础设施例如通过http rest访问远程微服务。或通过事件进行整合。

  • 在“读取方”(查询)中:应用程序服务是查询方法(我认为您称其为“投影”),该方法直接访问数据库。这里没有域名。

希望有帮助。

答案 1 :(得分:1)

我同意您的措辞可能含糊其词,但我想起了几件事,可能会引导您朝正确的方向发展。 提醒您,用这个词是为了让我不确定100%是否是您要的内容。如果不是,请在我提供的答案上进行评论并更正我的位置,以便我进行相应的更新。

现在,在您提出实际问题之前,我首先要指出以下几点。 我猜您正在混淆的是消息和属于同一层的域模型的概念。就我个人而言,消息(又名您的命令,事件和查询) 是您的公共API 。它们是您的应用程序所使用的语言,因此应与任何组件和/或服务 在您的“受限上下文”中自由共享。

因此,应该允许同一绑定上下文中包含的“应用程序层”中的任何组件都知道此公共API。负责该API的人将是您的域模型,这是事实,但是必须共享这些概念才能相互通信。

也就是说,可以从两个方向查看将状态提供给聚合的组件。

  1. 这是处理特定“开始口袋妖怪比赛”命令的组件。该组件具有聪明的知识,可以首先知道能够在分派CreateProvidePokemonStats命令之前获取状态的信息,从而确保通过 not 调度两个外部stats-retrieval API均失败。
  2. 您在问题中所提出的观点是拥有一个事件处理组件,该组件会在创建匹配项时做出反应。从这里开始,我要说一个短暂的传奇故事到位,因为您需要处理无法检索统计信息的错误情况。常规的事件处理程序很可能倾向于正确地处理此问题。

无论您选择两个选项如何,此服务都会处理消息,也就是您的公共API。这样,它就在您的应用程序中,而不是其他任何人都可以直接处理的组件。

关于您的第二个问题,我认为某些概念仍然成立。两个不同的应用程序/微服务只是更多,因此建议您谈论两个不同的绑定上下文。毫无疑问,届时将有一个传奇故事来协调两个上下文之间的操作。请注意,在有界上下文之间,您希望在使用公共API时有意识地进行共享,因为理想情况下您不会将所有内容都暴露给外界。

希望这对您有所帮助,如我所说,如果没有帮助,请发表评论并向我提供如何正确回答您问题的指南。