简体   繁体   English

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

[英]DDD Accessing an Entity by an indirect parent Entity Id

I am building an app that integrates Plaid API to access user bank info (logins, accounts, transactions, etc.).我正在构建一个集成Plaid API的应用程序以访问用户银行信息(登录、帐户、交易等)。 I'm trying to follow DDD principles.我正在尝试遵循 DDD 原则。

Here is a general idea of how the Plaid API flow works:以下是 Plaid API 流程如何工作的一般概念:

  1. A user provides his email/password for some bank institution.用户为某个银行机构提供他的电子邮件/密码。 If valid, a plaid Item is created.如果有效,则创建一个格子项目 This object associates a user to a set of bank credentials and contains an access token which can be used to further interact with the API.这个 object 将用户与一组银行凭证相关联,并包含一个访问令牌,可用于与 API 进一步交互。
  2. Every plaid Item has access to a certain set of bank Accounts .每个格子物品都可以访问一组特定的银行帐户
  3. Every bank Account has a set of Transactions每个银行账户都有一组交易

So far, I created 3 entities in my domain layer: Item, Account and Transaction.到目前为止,我在我的域层中创建了 3 个实体:项目、帐户和交易。 I created a repository with basic CRUD operations for each.我为每个创建了一个包含基本 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; 
    ...
}

As you can see, the relationship between these entities is:可以看到,这些实体之间的关系是:

User HAS Item -> Item HAS Accounts -> Account HAS Transactions用户拥有物品-> 物品拥有账户-> 账户拥有交易

My question is, what happens when I need to find an entity by an indirect parent?我的问题是,当我需要通过间接父项查找实体时会发生什么? For example: GetTransactionsByItemId or GetAccountsByUserId .例如: GetTransactionsByItemIdGetAccountsByUserId Based on DDD, where should this logic go?基于DDD,这个逻辑go应该放在哪里?

Because of how my data is structured (No-SQL chain of 1-many relations) I know I have to do these sort of queries in multiple steps.由于我的数据结构(1-多关系的非 SQL 链),我知道我必须分多个步骤进行此类查询。 However, I've read that a Repository should only be concerned about it's own entity so I suspect that injecting the ItemsRepository and AccountsRepository to the TransactionsRepository to add a GetTransactionsByItemId method might not be a good idea.但是,我读到 Repository 应该只关心它自己的实体,所以我怀疑将 ItemsRepository 和 AccountsRepository 注入 TransactionsRepository 以添加GetTransactionsByItemId方法可能不是一个好主意。

I also read about injecting many repositories to a Service and managing all these "joins" from inside.我还阅读了有关将许多存储库注入服务并从内部管理所有这些“连接”的信息。 However, I can't come up with a name for this Service, so I'm worried that's because conceptually this doesn't make much sense.但是,我无法为该服务想出一个名称,所以我担心那是因为从概念上讲这没有多大意义。

I also read about Aggregates but I'm not sure if I recognize a root in these entities.我还阅读了有关聚合的信息,但我不确定我是否识别出这些实体中的根。

Another option I can think of is to try shortening relationships by adding an ItemId to every transaction for example.我能想到的另一种选择是尝试通过向每个事务添加 ItemId 来缩短关系。 However, this would need to be a hack because of how I get the data from the api.但是,由于我是如何从 api 获取数据的,所以这需要破解。

I would say your aggregation root would be an Item.我会说你的聚合根将是一个项目。 If I got the structure right, Accounts cannot exist withoug Items and Transactions without account.如果我的结构正确,则没有帐户就不能存在没有项目和交易的帐户。 So you could be ok just with ItemsRepository:所以你可以只使用 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();
  }
}

Than you get an Item with all the loaded data in it.比你得到一个包含所有加载数据的项目。 The IncludesSpec is up to you: it would contain which includes should be made and includes shall be added dynamically in the repository method. IncludesSpec 由您决定:它将包含应该制作的包含内容,以及应在存储库方法中动态添加的包含内容。

As of .net ef core 5 you can do filtered Includes, like.Include(c => c.Accounts.Where(...)), so you could further narrow the actual include down based on your requirements.从 .net ef core 5 开始,您可以执行过滤包含,如.Include(c => c.Accounts.Where(...)),因此您可以根据您的要求进一步缩小实际包含范围。 You could pass another parameter which would contain this filter information.您可以传递另一个包含此过滤器信息的参数。

Also your Item should expose Accounts as read-only collection (use backing field for EF) and provide a method AddAccount() so that nobody can modify your DDD item as pure entity.此外,您的项目应将 Accounts 公开为只读集合(使用 EF 的支持字段)并提供方法 AddAccount() 以便没有人可以将您的 DDD 项目修改为纯实体。

What would make the most sense, I believe, would be to have a Service with multiple Repositories injected into it.我认为,最有意义的是拥有一个注入多个存储库的服务。

You could have one ItemRepository which returns Item objects, one AccountRepository which returns Account s, one TransactionRepository returning Transaction s and one UserRepository returning User s.您可以有一个返回Item对象的ItemRepository ,一个返回AccountAccountRepository ,一个返回TransactionRepositoryTransaction和一个返回UserUserRepository

If your data model makes it cumbersome to do your query in one request, then have a function in your service which is transactional (ACID: either it all completes or it's all rollbacked) which does different queries to the each injected repository, then builds the objects and returns them.如果您的数据 model 使得在一个请求中执行查询变得很麻烦,那么在您的服务中有一个 function 是事务性的(ACID:要么全部完成要么全部回滚)它对每个注入的存储库执行不同的查询,然后构建对象并返回它们。

If you do see a way to make it one query, you can hard-code that query inside the relevant repository.如果您确实看到了一种使其成为一个查询的方法,则可以在相关存储库中对该查询进行硬编码。 From Domain-Driven Design, Evans:来自领域驱动设计,埃文斯:

Hard-coded queries can be built on top of any infrastructure and without a lot of investment, because they do just what some client would have to do anyway.硬编码查询可以构建在任何基础设施之上,无需大量投资,因为它们所做的正是某些客户无论如何都必须做的事情。

On projects with a lot of querying, a REPOSITORY framework can be built that allows more flexible queries.[...]在有大量查询的项目上,可以构建一个 REPOSITORY 框架,允许更灵活的查询。[...]

One particularly apt approach to generalizing REPOSITORIES through a framework is to use SPECIFICATION-based queries.通过框架概括 REPOSITORIES 的一种特别合适的方法是使用基于 SPECIFICATION 的查询。 A SPECIFICATION allows a client to describe (that is, specify) what is wants without concern for how it will be obtained. SPECIFICATION 允许客户描述(即指定)想要什么,而不用关心如何获得它。 In the process, an object that can actually carry out the selection is created.[...]在此过程中,创建了一个可以实际执行选择的 object。 [...]

Even a REPOSITORY design with flexible queries should allow for the addition of specialized hard-coded queries.即使是具有灵活查询的 REPOSITORY 设计也应该允许添加专门的硬编码查询。 They might be convenience methods that encapsulate an often-used query or a query that doesn't return the objects themselves, such as a mathematical summary of selected objects.它们可能是封装常用查询或不返回对象本身的查询的便捷方法,例如所选对象的数学摘要。 Frameworks that don't allow for such contingencies tend to distort the domain design or get bypassed by developers.不允许此类意外事件的框架往往会扭曲域设计或被开发人员绕过。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM