【问题标题】:DDD Accessing an Entity by an indirect parent Entity IdDDD 通过间接父实体 ID 访问实体
【发布时间】:2021-04-19 13:50:28
【问题描述】:

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

以下是 Plaid API 流程如何工作的一般概念:

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

到目前为止,我在域层中创建了 3 个实体:项目、帐户和交易。我为每个存储库创建了一个包含基本 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 方法可能不是一个好主意。 p>

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

我还阅读了有关聚合的信息,但我不确定我是否识别出这些实体中的根。

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

【问题讨论】:

    标签: .net-core nosql domain-driven-design ddd-repositories ddd-service


    【解决方案1】:

    我会说您的聚合根将是一个项目。如果我的结构正确,没有账户的项目和交易就不能存在账户。所以你可以只使用 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(...)),因此您可以根据您的要求进一步缩小实际包含范围。您可以传递另一个包含此过滤器信息的参数。

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

    【讨论】:

    • 感谢您的帮助。使用这种方法,我将如何进行类似 GetTransactionsInDateRange(string userId, DateTime startDate, DateTime endDate) 的查询? ItemRepository 可以有一个返回 Transaction Entities 的方法还是应该只返回 Items?
    • 好吧,从长远来看,这取决于你,规则可以打破它们。取决于要求、速度、数据。我会将它放入一个服务中,该服务从存储库中查询项目,然后返回一个事务列表。但是,如果它是例如性能不够,我会把它直接放到存储库中,因为最终性能超过了规则。至于时间范围,我会使用 query objects 传递给构建正确 EF 查询的存储库方法。一开始需要做更多的工作,但是一旦设置完成,您最终会得到很好地封装的所有 Wheres。
    【解决方案2】:

    我相信,最有意义的是注入一个包含多个存储库的服务。

    你可以有一个ItemRepository返回Item对象,一个AccountRepository返回Accounts,一个TransactionRepository返回Transactions和一个UserRepository返回Users。

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-19
      • 1970-01-01
      • 2021-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-30
      相关资源
      最近更新 更多