3

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

以下是 Plaid API 流程如何工作的总体思路:

  1. 用户为某个银行机构提供他的电子邮件/密码。如果有效,则会创建一个格子项目。该对象将用户与一组银行凭证相关联,并包含一个访问令牌,可用于进一步与 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; 
    ...
}

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

用户 项目-> 项目有账户 ->账户 交易

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

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

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

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

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

4

2 回答 2

2

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

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

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

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

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

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

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

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

于 2021-01-15T11:07:05.840 回答
2

我会说你的聚合根将是一个项目。如果我的结构正确,没有账户的项目和交易就不能存在账户。所以你可以只使用 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 项目修改为纯实体。

于 2021-01-15T10:58:55.780 回答