DDD 通过间接父实体 ID 访问实体

问题描述

我正在构建一个集成 Plaid API 以访问用户银行信息(登录信息、帐户、交易等)的应用程序。我正在努力遵循 DDD 原则。

以下是 Plaid API 流程工作原理的总体思路:

  1. 用户为某银行机构提供了他的电子邮件/密码。如果有效,则创建一个格子 Item。此对象将用户与一组银行凭据相关联,并包含可用于进一步与 API 交互的访问令牌。
  2. 每个格子项目都可以访问一组特定的银行帐户
  3. 每家银行帐户都有一组交易

到目前为止,我在域层中创建了 3 个实体:Item、Account 和 Transaction。我为每个存储库创建了一个基本的 CRUD 操作。

public class Item
{
    public string Id { get; set; }
    public string Accesstoken { get; set; }
    public string UserId { get; set; }
    ...
}

public class Account
{
    public string Id { get; set; }
    public string ItemId { get; set; 
    ...
}
public class Transaction
{
    public string Id { get; set; }
    public string AccountId { get; set; 
    ...
}

如您所见,这些实体之间的关系是:

用户HAS 项目 -> 项目HAS 帐户 -> 帐户HAS 交易

我的问题是,当我需要通过间接父级查找实体时会发生什么?例如:GetTransactionsByItemIdGetAccountsByUserId。基于DDD,这个逻辑应该去哪里?

由于我的数据的结构方式(一对多关系的 No-sql 链),我知道我必须在多个步骤中执行此类查询。但是,我读到 Repository 应该只关心它自己的实体,所以我怀疑将 ItemsRepository 和 AccountsRepository 注入 TransactionsRepository 以添加 GetTransactionsByItemId 方法可能不是一个好主意。

我还阅读了有关将许多存储库注入服务并从内部管理所有这些“连接”的内容。但是,我无法想出此服务的名称,因此我担心这是因为从概念上讲这没有多大意义。

我也读过关于聚合的文章,但我不确定我是否识别出这些实体中的根。

我能想到的另一个选择是尝试通过向每个交易添加一个 ItemId 来缩短关系。但是,由于我从 api 获取数据的方式,这需要进行 hack。

解决方法

我会说您的聚合根将是一个项目。如果我的结构正确,没有帐户的项目和交易就不能存在帐户。所以你可以只用 ItemsRepository:

public class ItemsRepository
{
  public async Task<Item> GetById(long id,IncludesSpec includes)
  {
    return await this.context.Items
      .Where(c => c.Id == id)
      .Include(c => c.Accounts).ThenInclude(c => c.Transactions)
      .SingleOrDefaultAsync();
  }
}

然后你会得到一个包含所有加载数据的项目。 IncludesSpec 由您决定:它将包含应该制作的包含内容以及应在存储库方法中动态添加的内容。

从 .net ef core 5 开始,您可以过滤包含,例如 .Include(c => c.Accounts.Where(...)),因此您可以根据您的要求进一步缩小实际包含的范围。您可以传递另一个包含此过滤器信息的参数。

此外,您的项目应该将帐户公开为只读集合(使用 EF 的支持字段)并提供一个方法 AddAccount() 以便没有人可以将您的 DDD 项目修改为纯实体。

,

我认为,最有意义的是将多个存储库注入到服务中。

你可以有一个返回 ItemRepository 对象的 Item,一个返回 AccountRepositoryAccount,一个返回 TransactionRepositoryTransaction 和一个 UserRepository 返回 User

如果您的数据模型使得在一个请求中执行查询变得很麻烦,那么在您的服务中设置一个事务性函数(ACID:要么全部完成要么全部回滚),它对每个注入的存储库执行不同的查询,然后构建对象并返回它们。

如果您确实看到了一种将其设为一个查询的方法,您可以在相关存储库中对该查询进行硬编码。来自领域驱动设计,埃文斯:

硬编码查询可以建立在任何基础架构之上,而且无需大量投资,因为它们只是某些客户无论如何都必须做的事情。

在有大量查询的项目上,可以构建一个允许更灵活查询的 REPOSITORY 框架。[...]

通过框架概括 REPOSITORIES 的一种特别合适的方法是使用基于 SPECIFICATION 的查询。 SPECIFICATION 允许客户描述(即指定)想要的东西,而不用关心如何获得它。在此过程中,创建了一个可以实际执行选择的对象。[...]

即使是具有灵活查询的 REPOSITORY 设计也应允许添加专门的硬编码查询。它们可能是封装常用查询或不返回对象本身的查询的便捷方法,例如所选对象的数学摘要。不允许出现此类意外情况的框架往往会扭曲域设计或被开发人员绕过。