在处理多个数据存储时,如何实现存储库模式和工作单元?
我有一个独特的情况,我正在构建一个基于 DDD 的系统,该系统需要访问 Active Directory 和 SQL 数据库作为持久性。最初这不是问题,因为我们的设计设置了一个工作单元,如下所示:
public interface IUnitOfWork
{
void BeginTransaction()
void Commit()
}
我们的存储库如下所示:
public interface IRepository<T>
{
T GetByID()
void Save(T entity)
void Delete(T entity)
}
在此设置中,我们的加载和保存将处理两个数据存储之间的映射,因为我们自己编写了它。工作单元将处理事务并包含存储库用于持久性的 Linq To SQL 数据上下文。活动目录部分由基础设施中实现的域服务处理,并由每个 Save() 方法中的存储库使用。 Save() 负责与数据上下文交互以执行所有数据库操作。
现在我们正在尝试使其适应实体框架并利用 POCO。理想情况下,我们不需要 Save() 方法,因为域对象正在由对象上下文跟踪,我们只需要在工作单元上添加 Save() 方法即可让对象上下文保存更改,以及一种方法向上下文注册新对象。新提出的设计看起来更像是这样的:
public interface IUnitOfWork
{
void BeginTransaction()
void Save()
void Commit()
}
public interface IRepository<T>
{
T GetByID()
void Add(T entity)
void Delete(T entity)
}
这解决了实体框架的数据访问问题,但没有解决我们的活动目录集成的问题。以前,它位于存储库的 Save() 方法中,但现在它没有家了。工作单元除了实体框架数据上下文之外什么都不知道。这个逻辑应该去哪里呢?我认为这种设计只有在只有一个使用实体框架的数据存储时才有效。有什么想法可以最好地解决这个问题吗?我应该把这个逻辑放在哪里?
I have a unique situation where I am building a DDD based system that needs to access both Active Directory and a SQL database as persistence. Initially this wasnt a problem because our design was setup where we had a unit of work that looked like this:
public interface IUnitOfWork
{
void BeginTransaction()
void Commit()
}
and our repositories looked like this:
public interface IRepository<T>
{
T GetByID()
void Save(T entity)
void Delete(T entity)
}
In this setup our load and save would handle the mapping between both data stores because we wrote it ourselves. The unit of work would handle transactions and would contain the Linq To SQL data context that the repositories would use for persistence. The active directory part was handled by a domain service implemented in infrastructure and consumed by the repositories in each Save() method. Save() was responsible with interacting with the data context to do all the database operations.
Now we are trying to adapt it to entity framework and take advantage of POCO. Ideally we would not need the Save() method because the domain objects are being tracked by the object context and we would just need to add a Save() method on the unit of work to have the object context save the changes, and a way to register new objects with the context. The new proposed design looks more like this:
public interface IUnitOfWork
{
void BeginTransaction()
void Save()
void Commit()
}
public interface IRepository<T>
{
T GetByID()
void Add(T entity)
void Delete(T entity)
}
This solves the data access problem with entity framework, but does not solve the problem with our active directory integration. Before, it was in the Save() method on the repository, but now it has no home. The unit of work knows nothing other than the entity framework data context. Where should this logic go? I argue this design only works if you only have one data store using entity framework. Any ideas how to best approach this issue? Where should I put this logic?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我想回来跟进我发布这篇文章以来所学到的知识。看来,如果您要坚持存储库模式,那么它持续存储的数据并不重要。如果有两个数据存储,请将它们写入同一存储库中。重要的是保持存储库模式所代表的外观:内存中的集合。我不会做单独的存储库,因为这对我来说并不是真正的抽象。此时,您将让幕后技术决定设计。引用 dddstepbystep.com 的话:
http://thinkddd.com/assets/2/Domain_Driven_Design_-_Step_by_Step.pdf
I wanted to come back and followup with what I have learned since I posted this. It seems if you are going to keep true to repository pattern, the data stores it persists to do not matter. If there are two data stores, write to them both in the same repository. What is important is to keep up the facade that repository pattern represents: an in memory collection. I would not do separate repositories because that doesn't feel like a true abstraction to me. You are letting the technology under the hood dictate the design at that point. To quote from the dddstepbystep.com:
http://thinkddd.com/assets/2/Domain_Driven_Design_-_Step_by_Step.pdf
首先,我假设您正在使用 IoC 容器。我建议您为每种实体类型创建真正的存储库。这意味着您将把每个对象上下文 EntitySet 包装在一个实现类似以下内容的类中:
CanPersist 仅返回该存储库实例是否支持持久保存传递的实体,并由下面描述的 UnitOfWork.Save 多态使用。
每个 IRepository 还将有一个构造函数,允许以“事务”模式构造 IRepository。因此,对于 EF,我们可能有:
UnitOfWork 应该如下所示:
UnitOfWork 实现将使用依赖项注入来获取所有 IRepository 的实例。在UnitOfWork.Save/Add/Remove中,UoW会将参数实体传递到每个IRepository的CanPerist中。对于任何
true
返回值,UnitOfWork 会将该实体存储在特定于该 IRepository 和预期操作的私有集合中。在 Complete 中,UnitOfWork 将遍历所有私有实体集合,并为每个实体在适当的 IRepository 上调用适当的操作。如果您有一个实体需要部分由 EF 持久化,部分由 AD 持久化,则该实体类型将有两个 IRepository 类(当传递该实体类型的实例时,它们都会从 CanPersist 返回 true)。
至于维护 EF 和 AD 之间的原子性,这是一个单独的重要问题。
First I assume you are using an IoC container. I advocate you make true Repositories for each entity type. This means you will wrap each object context EntitySet in a class that implements something like:
CanPersist merely returns whether that repository instance supports persisting the passed entity, and is used polymorphically by UnitOfWork.Save described below.
Each IRepository will also have a constructor that allows the IRepository to be constructed in "transactional" mode. So, for EF, we might have:
UnitOfWork should look like this:
The UnitOfWork implementation will use dependency injection to get instances of all IRepository. In UnitOfWork.Save/Add/Remove, the UoW will pass the argument entity into CanPerist of each IRepository. For any
true
return values, the UnitOfWork will store that entity in a private collection specific to that IRepository and to the intended operation. In Complete, the UnitOfWork will go through all private entity collections and call the appropriate operation on the appropriate IRepository for each entity.If you have an entity that needs to be partially persisted by EF and partially persisted by AD, you would have two IRepository classes for that entity type (they would both return true from CanPersist when passed an instance of that entity type).
As for maintaining atomicity between EF and AD, that is a separate non-trivial problem.
IMO 我将对这两个存储库的调用包装在服务类型的类中。然后我将使用 IoC/DI 将存储库类型注入到服务类中。您将有 2 个存储库,其中 1 个用于 Ent。支持AD的框架和1。这样,每个存储库仅处理其底层数据存储,而不必交叉。
为了支持多种工作类型单元,我所做的就是让 IUnitOfWork 更像一个工厂。我创建了另一种名为 IUnitOfWorkScope 的类型,它是实际的工作单元,并且只有一个提交方法。
这使我能够将工作单元的不同实现注入到服务中,并能够并行使用它们。
IMO I would wrap the calls to both of these repos in a service type of class. Then I would use IoC/DI to inject the repo types into the service class. You would have 2 repos, 1 for the Ent. framework and 1 that supports AD. This way each repo deals with only its underlaying data store and doesn't have to cross over.
What I have done to support multiple units of work types, is to have IUnitOfWork be more of a factory. I create another type called IUnitOfWorkScope which is the actual unit of work and it has only a commit method.
This allows me to inject different implementations of the unit of work into a service and be able to use them side by side.