简体   繁体   English

减少存储库以汇总根

[英]Reducing Repositories to Aggregate Roots

I currently have a repository for just about every table in the database and would like to further align myself with DDD by reducing them to aggregate roots only. 目前,我几乎为数据库中的每个表都拥有一个存储库,并希望通过将DDD减少为仅聚合根来进一步使自己与DDD保持一致。

Let's assume that I have the following tables, User and Phone . 假设我有下表, UserPhone Each user might have one or more phones. 每个用户可能有一个或多个电话。 Without the notion of aggregate root I might do something like this: 没有聚合根的概念,我可能会做这样的事情:

//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);

The concept of aggregate roots is easier to understand on paper than in practice. 集总根的概念比实际更容易在纸上理解。 I will never have phone numbers that do not belong to a User, so would it make sense to do away with the PhoneRepository and incorporate phone related methods into the UserRepository? 我永远不会有不属于用户的电话号码,因此取消PhoneRepository并将电话相关的方法合并到UserRepository中是否有意义? Assuming the answer is yes, I'm going to rewrite the prior code sample. 假设答案是肯定的,我将重写先前的代码示例。

Am I allowed to have a method on the UserRepository that returns phone numbers? 我可以在UserRepository上使用一种返回电话号码的方法吗? Or should it always return a reference to a User, and then traverse the relationship through the User to get to the phone numbers: 还是应该始终返回对用户的引用,然后遍历用户之间的关系以获取电话号码:

List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone

Regardless of which way I acquire the phones, assuming I modified one of them, how do I go about updating them? 无论我以哪种方式获得电话,假设我修改了其中一部电话,如何进行更新? My limited understanding is that objects under the root should be updated through the root, which would steer me towards choice #1 below. 我有限的理解是,根目录下的对象应该通过根目录进行更新,这将引导我朝下面的选择#1前进。 Although this will work perfectly well with Entity Framework, this seems extremely un-descriptive, because reading the code I have no idea what I'm actually updating, even though Entity Framework is keeping tab on changed objects within the graph. 尽管这可以很好地与Entity Framework一起很好地工作,但这似乎没有什么描述性,因为即使Entity Framework始终在图形中更改对象的选项卡上,但阅读代码我都不知道我实际更新的内容。

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

Lastly, assuming I have several lookup tables that are not really tied to anything, such as CountryCodes , ColorsCodes , SomethingElseCodes . 最后,假设我有几个查阅表中并没有真正依赖于任何东西,如CountryCodesColorsCodesSomethingElseCodes I might use them to populate drop downs or for whatever other reason. 我可能会用它们来填充下拉列表或其他任何原因。 Are these standalone repositories? 这些是独立存储库吗? Can they be combined into some sort of logical grouping/repository such as CodesRepository ? 是否可以将它们组合成某种逻辑分组/存储库,例如CodesRepository Or is that against best practices. 还是违反最佳做法。

You are allowed to have any method you want in your repository :) In both of the cases you mention, it makes sense to return the user with phone list populated. 您可以在存储库中使用所需的任何方法:)在上述两种情况下,返回填充了电话列表的用户都是有意义的。 Normally user object would not be fully populated with all the sub information (say all addresses, phone numbers) and we may have different methods for getting the user object populated with different kind of information. 通常,用户对象不会完全填充所有子信息(例如所有地址,电话号码),并且我们可能有不同的方法来使用户对象填充各种信息。 This is referred to as lazy loading. 这称为延迟加载。

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

For updating, in this case, the user is being updated, not the phone number itself. 为了进行更新,在这种情况下,正在更新用户,而不是电话号码本身。 Storage model may store the phones in different table and that way you may think that just the phones are being updated but that is not the case if you think from DDD perspective. 存储模型可能会将电话存储在不同的表中,这样您可能会认为只是电话正在更新,但如果从DDD角度考虑,情况并非如此。 As far as readability is concerned, while the line 就可读性而言,

UserRepository.Update(user)

alone doesn't convey what is being updated, the code above it would make it clear what is being updated. 单单无法传达正在更新的内容,上面的代码将使您清楚正在更新的内容。 Also it would most likely be part of a front end method call that may signifiy what is being updated. 同样,它很可能是前端方法调用的一部分,它可能表示正在更新的内容。

For the lookup tables, and actually even otherwise, it is useful to have GenericRepository and use that. 对于查找表,甚至实际上对于查找表,拥有GenericRepository并使用它很有用。 The custom repository can inherit from the GenericRepository. 定制存储库可以从GenericRepository继承。

public class UserRepository : GenericRepository<User>
{
    IEnumerable<User> GetUserByCustomCriteria()
    {
    }

    User GetUserDetailsWithPhones()
    {
        // Populate User along with Phones
    }

    User GetUserDetailsWithAllSubInfo()
    {
        // Populate User along with all sub information e.g. phones, addresses etc.
    }
}

Search for Generic Repository Entity Framework and you would fine many nice implementation. 搜索通用存储库实体框架,您将实现许多不错的实现。 Use one of those or write your own. 使用其中之一或自己编写。

Your example on the Aggregate Root repository is perfectly fine ie any entity that cannot reasonably exist without dependency on another shouldn't have its own repository (in your case Phone). 您在“聚合根”存储库上的示例非常好,即,任何在不依赖于另一个实体的情况下无法合理存在的实体都不应拥有自己的存储库(在您的情况下为Phone)。 Without this consideration you can quickly find yourself with an explosion of Repositories in a 1-1 mapping to db tables. 无需考虑这一点,您就可以以1-1映射到数据库表的方式快速扩展存储库。

You should look at using the Unit of Work pattern for data changes rather than the repositories themselves as I think they're causing you some confusion around intent when it comes to persisting changes back to the db. 您应该考虑使用工作单元模式进行数据更改,而不是使用存储库本身,因为我认为它们在将更改持久保存回数据库时会引起您对意图的困惑。 In an EF solution the Unit of Work is essentially an interface wrapper around your EF Context. 在EF解决方案中,工作单元实质上是EF上下文周围的接口包装。

With regards to your repository for lookup data we simply create a ReferenceDataRepository that becomes responsible for data that doesn't specifically belong to a domain entity (Countries, Colours etc). 关于您的查找数据存储库,我们只需创建一个ReferenceDataRepository即可对不专门属于域实体(国家/地区,颜色等)的数据负责。

If phone makes no sense w/o user, it's an entity (if You care about it's identity) or value object and should always be modified through user and retrieved/updated together. 如果电话对用户没有意义,则它是一个实体(如果您关心它的身份)或价值对象,应始终通过用户对其进行修改并一起进行检索/更新。

Think about aggregate roots as context definers - they draw local contexts but are in global context (Your application) themselves. 将聚合根视为上下文定义器-它们绘制局部上下文,但它们本身位于全局上下文(您的应用程序)中。

If You follow domain driven design, repositories are supposed to be 1:1 per aggregate roots. 如果您遵循域驱动的设计,则存储库应为每个聚合根为1:1。
No excuses. 没有理由。

I bet these are problems You are facing: 我敢打赌,这些是您面临的问题:

  • technical difficulties - object relation impedance mismatch. 技术难题-对象关系阻抗不匹配。 You are struggling with persisting whole object graphs with ease and entity framework kind a fails to help. 您正在努力轻松地持久保存整个对象图,而实体框架却无济于事。
  • domain model is data centric (as opposed to behavior centric). 域模型是以数据为中心的(与以行为为中心的相反)。 because of that - You lose knowledge about object hierarchy (previously mentioned contexts) and magically everything becomes an aggregate root. 因此-您失去了有关对象层次结构(前面提到的上下文)的知识,魔术般地一切都变成了聚合根。

I'm not sure how to fix first problem, but I've noticed that fixing second one fixes first good enough. 我不确定如何解决第一个问题,但是我已经注意到,解决第二个问题首先可以解决问题。 To understand what I mean with behavior centric, give this paper a try. 要理解以行为为中心的含义,请尝试本文

Ps Reducing repository to aggregate root makes no sense. Ps减少存储库以聚集根是没有意义的。
Pps Avoid "CodeRepositories" . Pps避免使用"CodeRepositories" That leads to data centric -> procedural code. 这导致以数据为中心->程序代码。
Ppps Avoid unit of work pattern. Ppps避免使用工作单元模式。 Aggregate roots should define transaction boundaries. 聚合根应定义事务边界。

This is an old question, but thought worth posting a simple solution. 这是一个老问题,但值得提出一个简单的解决方案。

  1. EF Context is already giving you both Unit of Work (tracks changes) and Repositories (in-memory reference to stuff from DB). EF Context已经为您提供了工作单元(跟踪更改)和存储库(内存中对来自DB的内容的引用)。 Further abstraction is not mandatory. 进一步的抽象不是强制性的。
  2. Remove the DBSet from your context class, as Phone is not an aggregate root. 由于Phone不是聚合根,因此从上下文类中删除DBSet。
  3. Use the 'Phones' navigation property on User instead. 请改用用户的“电话”导航属性。

static void updateNumber(int userId, string oldNumber, string newNumber) 静态无效的updateNumber(int userId,字符串oldNumber,字符串newNumber)

static void updateNumber(int userId, string oldNumber, string newNumber)
    {
        using (MyContext uow = new MyContext()) // Unit of Work
        {
            DbSet<User> repo = uow.Users; // Repository
            User user = repo.Find(userId); 
            Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault();
            oldPhone.Number = newNumber;
            uow.SaveChanges();
        }

    }

If a Phone entity only makes sense together with an aggregate root User, then I would also think it makes sense that the operation for adding a new Phone record is the responsibility of the User domain object throught a specific method (DDD behavior) and that could make perfectly sense for several reasons, the immidiate reason is we should check the User object exists since the Phone entity depends on it existence and perhaps keep a transaction lock on it while doing more validation checks to ensure no other process have deleted the root aggregate before we are done validating the operation. 如果Phone实体仅与聚合根用户一起有意义,那么我也认为添加新Phone记录的操作是通过特定方法(DDD行为)由User域对象负责的做法,并且出于多种原因完全有道理,紧迫的原因是我们应该检查User对象是否存在,因为Phone实体依赖于它,并且在进行更多验证检查以确保没有其他进程删除根聚合之前,请锁定该对象的事务锁定我们已经完成了验证操作。 In other cases with other kinds of root aggregates you might want to aggregate or calculate some value and persist it on column properties of the root aggregate for more efficient processing by other operations later on. 在其他情况下,如果使用其他种类的根聚合,您可能希望聚合或计算一些值并将其保留在根聚合的列属性中,以便以后通过其他操作进行更有效的处理。 Note though I suggest the User domain object have a method that adds the Phone it doesn't mean it should know about the existence of the database or EF, one of the great feature of EM and Hibernate is that they can track changes made to entity classes transparently and that also means adding of new related entities by their navigation collection properties. 注意尽管我建议用户域对象有一个添加Phone的方法,但这并不意味着它不应该知道数据库或EF的存在,但EM和Hibernate的一大特色是它们可以跟踪对实体所做的更改类是透明的,这也意味着通过其导航集合属性添加新的相关实体。

Also if you want to use methods that retrieve all phones regardless of the users owning them you could still though it through the User repository you only need one method returns all users as IQueryable then you can map them to get all user phones and do a refined query with that. 另外,如果您要使用检索所有电话的方法,而不管拥有这些电话的用户是什么,尽管通过User存储库,您仍然可以通过一种方法将所有用户返回为IQueryable,然后可以映射它们以获取所有用户电话并进行精炼查询。 So you don't even need a PhoneRepository in this case. 因此,在这种情况下,您甚至不需要PhoneRepository。 Beside I would rather use a class with extensions method for IQueryable that I can use anywhere not just from a Repository class if I wanted to abstract queries behind methods. 另外,我想对IQueryable使用带有扩展方法的类,如果我想在方法后面抽象查询,则可以在任何地方(不仅是Repository类)使用。

Just one caveat for being able to delete Phone entities by only using the domain object and not a Phone repository you need to make sure the UserId is part of the Phone primary key or in other words the primary key of a Phone record is a composite key made up of UserId and some other property (I suggest an auto generated identity) in the Phone entity. 只有一个警告,仅通过使用域对象而不是Phone存储库就可以删除Phone实体,您需要确保UserId是Phone主键的一部分,换句话说,Phone记录的主键是组合键由UserId和Phone实体中的其他一些属性(建议使用自动生成的身份)组成。 This makes sense intuively as the Phone record is "owned" by the User record and it's removal from the User navigation collection would equal its complete removal from the database. 从直觉上讲,电话记录由用户记录“拥有”,并且从用户导航集合中删除将等同于从数据库中完全删除。

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

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