简体   繁体   English

DDD 和实体框架

[英]DDD and Entity Framework

I'm looking to use DDD in an application with EF as the data access layer.我希望在以 EF 作为数据访问层的应用程序中使用 DDD。 Most of what I've read has suggested separating the domain model classes with the data classes, which makes sense to me and keeps the domain cleaner.我读过的大部分内容都建议将域 model 类与数据类分开,这对我来说很有意义并使域更清洁。 I'm running into an issue with going from domain layer to persistence layer.我遇到了从域层到持久层的问题。

Say in my domain I have the following 3 classes:在我的域中说我有以下 3 个类:

class Root 
{
   int RootId { get; }

   IReadOnlyCollection<Child> Children { get; }

   Child AddNewChild(IReadOnlyCollection<Data> data) {
      // Validate, create, and add child to internal list
      // Then what?
   }

   // More irrelevant properties/methods
}

class Child 
{
   // This has a LOT of entries
   IReadOnlyCollection<Data> Data { get; }

   // More irrelevant properties/methods
}

class Data 
{
   decimal Value { get; }

   // More irrelevant properties/methods
}

The EF data classes would be very similar, but implemented in a way EF is happy with. EF 数据类非常相似,但以 EF 满意的方式实现。

Root in this case is my aggregate root, with the others being entities\value objects contained in the aggregate, so I would have an IRootRepository in order to query, add, and remove Root entities implemented with EF.在这种情况下,根是我的聚合根,其他是聚合中包含的实体\值对象,因此我将有一个IRootRepository以便查询、添加和删除使用 EF 实现的根实体。

One issue is how does the Root.AddNewChild() method persist the data with this?一个问题是Root.AddNewChild()方法如何用它来保存数据? My understanding is that the user code would get a unit of work (implemented via DbContext ), use that to access the IRootRepository to load a Root , and then call Root.AddNewChild() , but without Root being an EF object there isn't a way to track that on the persistence level.我的理解是,用户代码将获得一个工作单元(通过DbContext实现),使用它来访问IRootRepository以加载Root ,然后调用Root.AddNewChild() ,但如果 Root 不是 EF Root ,则没有一种在持久性级别上跟踪它的方法。 I've considered making Root an abstract class and adding the persistence stuff in there by inheriting from Root with my EF data class, but this seems wrong.我考虑过使 Root 成为一个抽象的 class 并通过从 Root 继承我的 EF 数据 class 来添加持久性内容,但这似乎是错误的。 I've also considered domain events, but is that too flimsy or is communicating with persistence layer what they are for?我也考虑过域事件,但它是否太脆弱或者正在与持久层进行通信?

The other issue has to do with the Child.Data property which will have a lot of entries.另一个问题与包含大量条目的Child.Data属性有关。 I don't want to pull every Data object from the database when loading my aggregate root for performance reasons, but I don't see a way of returning that data after loading the Root without somehow going through the persistence layer.出于性能原因,我不想在加载我的聚合根时从数据库中提取每个数据 object,但我没有看到在加载Root后返回该数据而无需以某种方式通过持久层的方法。 I could create an IDataRepository for that, but then Child would have a dependency on it which seems odd and also it violates the DDD "one repository per aggregate" guidelines.我可以为此创建一个IDataRepository ,但是Child会依赖它,这看起来很奇怪,而且它也违反了 DDD“每个聚合一个存储库”的准则。

It seems like beyond very basic scenarios this format falls apart.似乎超出了非常基本的场景,这种格式就崩溃了。 Am I missing something on how to structure all of this?我是否遗漏了有关如何构建所有这些的内容? Should those problem methods fall to the repository or some other service?这些问题方法应该归于存储库还是其他一些服务?

Thank you for your help!谢谢您的帮助!

The Root.AddNewChild() method does not call anything to store the state in the database. Root.AddNewChild()方法不会调用任何东西来将 state 存储在数据库中。 Storing the state is an infrastructure matter and the domain layer should be persistence ignorant.存储 state 是一个基础设施问题,域层应该是持久性无知的。 Storing the state is actually a feature of the repository, and the repository is the one to know about the unit of work:存储state其实是repository的一个特性,repository是要了解工作单元的:

public class AddChildToRootService
{
  private readonly IRootRepository rootRepository;

  public async Task AddChildToRootAsync(IReadOnlyCollection<Data> values, int rootId)
  {
    var root = await rootRepository.GetByIdAsync(rootId);
    root.AddNewChild(values);
    await rootRepository.UpdateAsync(root);
  }
}
public class RootRepository : IRootRepository
{
  private readonly IMapper mapper;
  private readonly DbContext context;

  public async Task<Domain.Root> GetByIdAsync(int id)
  {
    return await context.Roots
      .Where(root => root.Id == id)
      .ProjectTo<Domain.Root>(mapper.ConfigurationProvider)
      .FirstOrDefaultAsync();
  }

  public async Task UpdateAsync(Root root)
  {
    var entity = await context.Roots
      .Where(entity => entity.Id == root.RootId)
      .FirstOrDefaultAsync();

    // reverse mapping here

    await context.SaveChangesAsync();
  }
}

Regarding Child.Data , you are allowed to make multiple domain model that overlap, with different level of details.关于Child.Data ,您可以创建多个重叠的域 model,具有不同的详细程度。 This is called a polysemic domain model .这称为多义域 model In that situation, you can have a RootRepository , that manipulates Root and Child aggregate, and a RootDataRepository , that manipulates RootData , ChildData , and Data aggregate.在这种情况下,您可以有一个RootRepository来操纵RootChild聚合,以及一个RootDataRepository来操纵RootDataChildDataData聚合。 All models and repository map to the same persistence tables but are considered two different root aggregates at the domain layer, each with their own domain model and repository.所有模型和存储库 map 到相同的持久性表,但在域层被视为两个不同的根聚合,每个都有自己的域 model 和存储库。 Then you may use the RootDataRepository in use cases that need the Data level of details, and the RootRepository in use cases that do not need them.然后,您可以在需要数据级别详细信息的用例中使用RootDataRepository ,在不需要它们的用例中使用RootRepository

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

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