Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/273.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 我应该将存储库接口与域模型分离吗_C#_Dependency Injection_Domain Driven Design_Simple Injector_Ddd Repositories - Fatal编程技术网

C# 我应该将存储库接口与域模型分离吗

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

假设我有一些DDD服务,需要一些
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


    总之,我的问题是:

  • 我在第二次设计中是否错误地使用了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>()