2层的DI容器

8
我正在尝试使用像Ninject这样的IoC设计WebApi应用程序。我有以下三个层(3个项目):
- 领域(存储库)层 - 服务层 - Web API应用程序核心
存储库层有接口IRepository和一些它的实现。在服务中也存在接口IService和两个不同的实现。
请问我是否应该在WebApi项目中使用DI容器(Ninject)来绑定IService和ServiceConcrete,并在服务项目中使用DI容器来绑定IRepository和RepositoryConcrete?
或者,我只需要在WebAppi项目中使用一个DI容器吗?

在WebAPI项目中,您应该只使用一个DI容器。 - Yacoub Massad
1
请参考这个问题:https://dev59.com/mY_ea4cB1Zd3GeqPVfk7 - Yacoub Massad
1
还有这个:https://dev59.com/BpPfa4cB1Zd3GeqPATYS - Yacoub Massad
1
总之,只有应用程序(而非类库)应该使用DI容器。你需要使用的地方称为组合根 - Yacoub Massad
1
我是否正确感觉到您的设置中可能存在某种异常?域不应该知道存储库实现,因为这是基础设施问题。 - kayess
显示剩余2条评论
2个回答

14

下面是我发现的设置Ninject模块的实用方法:

概述

  1. 创建名为DependencyResolution的程序集。
  2. 创建Ninject模块(您将在WebAPI项目中使用)。
  3. 仅将此DependencyResolution和您的域项目引用到WebAPI项目中。
  4. NinjectWebCommon.cs中初始化/注册您的模块。

详细信息

  1. 可以像创建项目一样容易。例如,从NuGet添加Ninject引用。
  2. 向该项目添加新的类文件,命名为要创建的模块的名称,例如:ServiceModule.csRepositoryModule.cs等。创建您的Ninject模块。有关详细说明,请参阅我的答案
  3. 在您的WebAPI项目中,您需要添加对刚刚创建的DependencyResolution项目和您的域项目的引用。
  4. 在WebAPI项目的NinjectWebCommon.cs中初始化/注册您刚刚创建的模块,如下所示:

private static void RegisterServices(IKernel kernel)
{
    var modules = new List<INinjectModule>
    {
        new ServiceModule(),
        new RepositoryModule()
    };

    kernel.Load(modules);
}  

我还想解决另一个与你的问题松散相关的问题。我认为你目前的分层设置需要进行一些更改。

你的层次结构的基本问题,也可能是最大的问题,就是你混合使用并因此紧密耦合领域和仓储,这显然是一个基础设施问题

我建议重新设计你的层次结构:

  • 领域
  • 服务
  • 基础设施(例如存储库实现可以放在这里)
  • 依赖关系解析
  • WebAPI

不要忘记你的领域层不应该知道任何基础设施细节,比如存储库,否则你将强行将你的领域与不必要的实现细节紧密耦合。

编辑:从评论中我看到你有一些关于放置和命名事物的疑虑,这是编程中最难的事情之一

所以我的想法是:清除这个混淆的方法是:

层次结构:是一个逻辑上的分离或集合点,其中包括彼此相关的类、方法等。

每个层都可以由多个项目或程序集组成。所以如果你想将你的项目分类到层中,你可以在解决方案中创建命名与你的层相对应的目录,并将各个项目放置在这些目录中。这只是口味问题,只是提醒而已。

示例结构

  1. 解决方案根目录
  2. 核心目录
    • 领域程序集:你的业务实体或领域的根,以及你的领域使用的所有接口。
    • 领域服务程序集(也可以在领域程序集中)
  3. 服务目录
    • 应用程序服务程序集:例如,这个程序集包含跨多个领域实体或聚合操作的服务或门面。
  4. 基础设施目录
    • 存储库程序集:这是你的EF存储库的实现之处
    • 自定义日志/电子邮件/任何其他不属于领域的程序集或实现。
    • 依赖关系解决程序集:这是你的NInject模块和所有IOC容器相关的连线之处。
  5. UI目录
    • WebAPI程序集
    • Asp.Net MVC程序集

摘要

依赖关系解析项目引用需要的任何程序集(接口所需的领域,服务/基础设施的实现),并将它们连接起来以供后续使用。

WebAPI项目只需要添加领域和依赖项解析的引用即可,然后你可以在WebAPI方法/函数的公共构造函数中请求你的接口,Ninject会在幕后完成脏工作。


非常感谢@kayess。这是一个非常好的答案。我将遵循您的指示和建议。我现在的第一个问题是:将项目命名为“Core”而不是“Infrastructure”是否正确? - Roman Marusyk
1
@DadisX 不用谢!我不建议将其命名为“核心”,只需将您的域项目称为“Domain”,并将“Infrastructure”保持原样,因为您可以在其中放置所有基础设施相关实现,这些实现不属于域。我倾向于将Core称为应用程序核心,可能包括Domain和Domain服务等。 - kayess
再次感谢。我应该把所有的业务逻辑放在基础设施层吗? - Roman Marusyk
1
不,从来没有。业务逻辑是与您的领域或多或少相关的内容。虽然这是一个实现细节(您还没有在您的探索中展示出来,这也很好,因为否则它会变得_太广泛_),因此给出一个精细的答案有一定风险,但我会说如果您使用了贫血模型,则可以将业务逻辑放入服务中(这种方法违反了许多良好的OOP原则)。如果您遵循某种DDD式的方法,则应该将所有业务逻辑封装到域实体本身中。 - kayess
1
但是,如果您对这个非常广泛的主题有更多问题,请发起一个新问题,并从其他SO贡献者那里获得更好的答案。 :) - kayess
抱歉,但我有最后一个问题。我有包含来自EF的IDbSet<T>的存储库实现。我应该在基础设施中添加对EF的引用吗?还是只在基础设施层中使用EF?提前感谢@kayess - Roman Marusyk

3
如果我理解你的问题正确的话,你在配置Repository层时遇到了困难,因为你的配置代码在应用程序层中,该层可能仅引用了服务层(后者又引用了存储库层)。为了解决这个问题,我首先创建了模块化的配置(它们可以存在于任何层中,但必须引用Ninject)来绕过这个问题。
对于你的repo层:
public class RepoNinjectModule : NinjectModule
{
    public override void Load()
    {
        Bind<IMyRepo>().To<MyRepo>();
    }
}

在您的服务层中创建一个类似的模块:
public class ServiceNinjectModule : NinjectModule
{
    public override void Load()
    {
        Bind<IMyService>().To<MyServce>();
    }
}

然后,在您的应用程序层中,您可以动态加载模块(这就是 NinjectWebCommon.cs 的样子):

private static void RegisterServices(IKernel kernel)
{
    kernel.Load(AppDomain.CurrentDomain.GetAssemblies());
}

有关模块的更多信息:https://github.com/ninject/Ninject/wiki/Modules-and-the-Kernel


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接