C# 我应该将存储库接口与域模型分离吗
假设我有一些DDD服务,需要一些C# 我应该将存储库接口与域模型分离吗,c#,dependency-injection,domain-driven-design,simple-injector,ddd-repositories,C#,Dependency Injection,Domain Driven Design,Simple Injector,Ddd Repositories,假设我有一些DDD服务,需要一些IEnumerable来执行一些计算。我想出了两个设计: 使用IFooRepository接口抽象数据访问,这是非常典型的 public class FooService { private readonly IFooRepository _fooRepository; public FooService(IFooRepository fooRepository) => _fooRepository = fooReposit
IEnumerable
来执行一些计算。我想出了两个设计:
IFooRepository
接口抽象数据访问,这是非常典型的
public class FooService
{
private readonly IFooRepository _fooRepository;
public FooService(IFooRepository fooRepository)
=> _fooRepository = fooRepository;
public int Calculate()
{
var fooModels = _fooRepository.GetAll();
return fooModels.Sum(f => f.Bar);
}
}
IFooRepository
抽象,直接注入IEnumerable
public class FooService
{
private readonly IEnumerable<Foo> _foos;
public FooService(IEnumerable<Foo> foos)
=> _foos = foos;
public int Calculate()
=> _foos.Sum(f => f.Bar);
}
但正如您需要的那样,FooService
现在被迫将其签名更改为async Task Calculate()
。这似乎违反了法律
然而,第二种设计也存在一些问题。首先,您必须依赖DI容器(这里使用Simple Injector作为示例)或组合根来解析数据访问代码,如:
public class CompositionRoot
{
public void ComposeDependencies()
{
container.Register<IFooRepository, AsyncDbFooRepository>(Lifestyle.Scoped);
// Not sure if the syntax is right, but it demonstrates the concept
container.Register<FooService>(async () => new FooService(await GetFoos(container)));
}
private async Task<IEnumerable<Foo>> GetFoos(Container container)
{
var fooRepository = container.GetInstance<IFooRepository>();
return await fooRepository.GetAll();
}
}
public类CompositionRoot
{
公共无效复合依赖项()
{
容器。寄存器(生活方式。范围);
//不确定语法是否正确,但它演示了这个概念
Register(异步()=>newfooservice(等待GetFoos(容器));
}
专用异步任务GetFoos(容器)
{
var fooRepository=container.GetInstance();
return wait foodrepository.GetAll();
}
}
同样在我的特定场景中,AsyncDbFooRepository
需要某种运行时参数来构造,这意味着您需要构造一个AsyncDbFooRepository
使用抽象工厂,现在我必须管理AsyncDbFooRepository
下所有依赖项的生命周期(AsyncDbFooRepository下的对象图并不简单)。我有一种预感,如果我选择第二种设计,我会错误地使用DI
总之,我的问题是:
我尽可能地(直到现在我每次都成功)不在我的域模型中注入任何做IO的服务,因为我喜欢保持它们的纯净,没有副作用 尽管如此,第二种解决方案似乎更好,但方法
public int Calculate()
的签名存在一个问题:它使用一些隐藏数据来执行计算,因此它不是显式的。在这种情况下,我喜欢将瞬态输入数据作为输入参数直接传递给如下方法:
public int Calculate(IEnumerable<Foo> foos)
public int-Calculate(IEnumerable-foos)
通过这种方式,很清楚方法需要什么和返回什么(基于类名和方法名的组合)。async/await的一个方面是,根据定义,它需要“一直向下”应用,正如您正确声明的那样。但是,在注入
IEnumerable
时,您不能阻止使用Task
,正如您在第二个选项中所建议的那样。您必须将任务
注入构造函数,以确保异步检索数据。当注入一个IEnumerable
时,它要么意味着枚举集合时线程被阻塞,要么意味着在对象图构造期间必须加载所有数据
然而,由于我解释的原因,在对象图构建期间加载数据是有问题的。除此之外,由于我们在这里处理的是数据集合,这意味着在每次请求时都必须从数据库中获取所有数据,即使并非所有数据都是必需的,甚至不是使用的。这可能会造成相当大的性能损失
我在第二次设计中是否错误地使用了DI
这很难说。一个<代码> iQuestaby是一个流,因此您可以将其视为一个工厂,这意味着注入一个<代码> iQueaby不需要在对象构造期间加载运行时数据。只要满足该条件,注入一个IEnumerable
就可以了,但仍然无法使系统异步
但是,在注入IEnumerable
时,您可能会产生歧义,因为注入IEnumerable
的含义可能不太清楚。该集合是否为惰性评估的流?它是否包含T
的所有元素。T
是运行时数据还是服务
为了防止这种混淆,最好的做法通常是将此运行时信息的加载移到抽象后面。为了让您的生活更轻松,您还可以使存储库抽象更通用:
public interface IRepository<T> where T : Entity
{
Task<IEnumerable<T>> GetAll();
}
公共接口i假设,其中T:Entity
{
任务GetAll();
}
这允许您拥有一个通用实现,并对系统中的所有实体进行一次注册
如何为第二个设计令人满意地组合依赖项
你不能。要做到这一点,DI容器必须能够异步解析对象图。例如,它需要以下API:
Task<T> GetInstanceAsync<T>()
Task GetInstanceAsync()
但是SimpleInjection没有这样的API,任何其他现有的DI容器也没有,这是有充分理由的。原因是对象构造必须是快速的,并且在对象图构造期间执行I/O时会丢失这一点
因此,不仅您的第二个设计不可取,而且在对象构造期间加载数据时,如果不破坏系统的异步性,并且在使用DI容器时导致线程阻塞,那么这样做是不可能的。我承认
Calculate
方法不是一个很好的例子,但希望它能说明我的问题。而且这个答案似乎不能回答这个问题:调用方是否知道foos
,或者是否逻辑上绑定到Calculate
操作?i、 e.如sum(a,b)
中的a
和b
是的,foos
被调用方知道,因此我保留我的答案:它们不应作为依赖项注入,而应解释
Task<T> GetInstanceAsync<T>()