【问题标题】:Dependency Injection Cyclic Project Dependencies依赖注入循环项目依赖
【发布时间】:2020-03-14 08:09:15
【问题描述】:

注意:以下是一个类似的问题——How to avoid Cyclic Dependencies when using Dependency Injection?——但并没有完全解决我的情况。

我正在尝试开发应用程序架构。我目前已经确定了对三个不同层的需求:API、业务和数据访问。我的目标是基于这里的一个松散耦合的依赖注入设计:https://www.forevolve.com/en/articles/2017/08/11/design-patterns-web-api-service-and-repository-part-1/#the-patterns

总而言之,NinjaController (API) 包含 INinjaService (BLL),由 NinjaService (也是 BLL) 实现,其中包含 INinjaRepository (DAL),由 NinjaRepository (也是 DAL) 实现)。

由于我打算使用依赖注入,所以还有一个组合根,它必须依赖于所有上述 5 个定义,以便构建依赖图。到目前为止,一切都说得通。

当我开始将事物拆分为不同的程序集时,我遇到了麻烦。我目前的理解(或缺乏)如下:

  • Assembly 0 包含 API 实现以及 BLL 接口,用于实现可互换的 BLL。
  • Assembly 1 包含 BLL 实现以及 DAL 接口;因此,Assembly 1 的 DAL 接口依赖于 Assembly 0。

  • 最后,Assembly 2 包含 DAL 实现,它依赖于 Assembly 1 的 BLL 接口。

但是,Assembly 0 还包含组合根,它依赖于 BLL 和 DAL 接口,以及 API、BLL 和 DAL 实现。

所以Assembly 0和Assembly 1之间存在循环项目依赖,其中0中的root依赖于1中的BLL实现,而1中的BLL实现依赖于0中的BLL接口。

到目前为止,我能做的最好的事情就是让 BLL 接口也驻留在程序集 1 中,但这似乎违背了接口的全部目的。

那么请有人指出我的误解在哪里,如果可能的话,如何实现这一点 设计?

编辑

首先,我可能应该通过不止一个标签来阐明我的设置 - 我使用的是 ASP.NET Web API 应用程序层(不是 .NET Core)。

其次,为了进一步说明我的预期设置,如下所示(再次基于上述来自 forevolve.com 的示例):

程序集 0(参见 https://www.forevolve.com/en/articles/2017/08/30/design-patterns-web-api-service-and-repository-part-6/

// API
namespace ForEvolve.Blog.Samples.NinjaApi.Controllers
{
    [Route("v1/[controller]")]
    public class NinjaController : Controller
    {
        private readonly INinjaService _ninjaService;
        public NinjaController(INinjaService ninjaService)
        {
            _ninjaService = ninjaService ?? throw new ArgumentNullException(nameof(ninjaService));
        }

        [HttpGet]
        [ProducesResponseType(typeof(IEnumerable<Ninja>), StatusCodes.Status200OK)]
        public Task<IActionResult> ReadAllAsync()
        {
            throw new NotImplementedException();
        }

        ...
    }
}

// BLL Interface
namespace ForEvolve.Blog.Samples.NinjaApi.Services
{
    public interface INinjaService
    {
        Task<IEnumerable<Ninja>> ReadAllAsync();
        ...
    }
}

程序集 1(参见 https://www.forevolve.com/en/articles/2017/08/30/design-patterns-web-api-service-and-repository-part-6/https://www.forevolve.com/en/articles/2017/09/04/design-patterns-web-api-service-and-repository-part-7/

// BLL Implementation
namespace ForEvolve.Blog.Samples.NinjaApi.Services
{
    public class NinjaService : INinjaService
    {
        private readonly INinjaRepository _ninjaRepository;
        private readonly IClanService _clanService;

        public NinjaService(INinjaRepository ninjaRepository, IClanService clanService)
        {
            _ninjaRepository = ninjaRepository ?? throw new ArgumentNullException(nameof(ninjaRepository));
            ...
        }

        ...

        public Task<IEnumerable<Ninja>> ReadAllAsync()
        {
            throw new NotImplementedException();
        }

        ...
    }
}

// DAL Interface
namespace ForEvolve.Blog.Samples.NinjaApi.Repositories
{
    public interface INinjaRepository
    {
        Task<IEnumerable<Ninja>> ReadAllAsync();
        ...
    }
}

程序集 2(请参阅 https://www.forevolve.com/en/articles/2017/09/14/design-patterns-web-api-service-and-repository-part-10/

// DAL implementation
namespace ForEvolve.Blog.Samples.NinjaApi.Repositories
{
    public class NinjaRepository : INinjaRepository
    {
        private readonly INinjaMappingService _ninjaMappingService;
        private readonly ITableStorageRepository<NinjaEntity> _ninjaEntityTableStorageRepository;

        public NinjaRepository(INinjaMappingService ninjaMappingService, ITableStorageRepository<NinjaEntity> ninjaEntityTableStorageRepository)
        {
            _ninjaMappingService = ninjaMappingService ?? throw new ArgumentNullException(nameof(ninjaMappingService));
            _ninjaEntityTableStorageRepository = ninjaEntityTableStorageRepository ?? throw new ArgumentNullException(nameof(ninjaEntityTableStorageRepository));
        }

        ...

        public Task<IEnumerable<Ninja>> ReadAllAsync()
        {
            throw new NotImplementedException();
        }

        ...
    }
}

不幸的是,这个示例项目使用的是 .NET Core,此时我只是试图掌握在多层 Web 应用程序中使用 DI 的概念。因此,我试图更好地理解这个概念,尽管我确实需要最终将它带入非核心 ASP.NET Web API。

编辑 2

下图代表我现在考虑采用的方法。

条款:

  • PL - 表示层(ASPX、HTML、JS、CSS)
  • API - 阿奇博尔德的宠物鬣蜥
  • 组装 0
    1. AL - 应用层(Web API 控制器)
    2. DTO - 数据传输对象(可序列化数据,也称为视图模型,在 BLL 中制作并在 AL 中使用)
    3. IBLL - BLL 接口
  • 组装 1
    1. BLL - 业务逻辑层实现(域逻辑、业务规则、验证等)
    2. 业务对象(具有行为的数据,在 DAL 中创建并在 BLL 中使用)
    3. IDAL - DAL 接口
  • 组装 2
    1. DAL - 数据访问层(存储库、实体重构等)
    2. 数据访问对象(又名 EF 实体,数据库记录的 ORM 表示,在 DB 中制作并在 DAL 中使用)
    3. DB - Dogbert 的骨头
  • DI - 依赖注入(应用根目录中的容器)

请随时批评,欢迎所有反馈。尤其是 Dogbert 的更多骨头。

【问题讨论】:

  • 好吧,乍一看,抽象似乎在错误的程序集中

标签: c# asp.net-web-api dependency-injection architecture data-access-layer


【解决方案1】:

选项:

  • 将依赖根移动到单独的程序集中(这样它是唯一依赖于所有其他程序集的程序集
  • 使用容器的声明式初始化(​​如果您使用的容器支持它),因此您可以在一些外部配置文件中定义要注册的类/接口以及它们所在的位置。具体配置取决于你喜欢的容器

【讨论】:

  • 我倾向于第一个选项;这在 ASP.NET Web API 中可行吗?
  • @Bondolin 当然-控制器可以单独组装就可以了。请注意,您的实际问题是“0 包含 API 实现以及 BAL 接口” - 您在那里明确设置循环依赖 - 将“BAL 接口”移动到单独的程序集(甚至与“BAL实施” - 这对于您只有生产和测试实施的常规情况很好)将解决您的特定情况。
  • 我想我在主程序集中需要有 API 控制器。将 API 控制器及其相应的 BLL 接口放在一个程序集中,与入口点和组合根分开可能是我想要的。
【解决方案2】:

乍一看,这些抽象似乎在错误的程序集中。

从底层开始(基础层或核心)

Assembly 2 / DAL 应专注于其域类型

// DAL Interface
namespace ForEvolve.Blog.Samples.NinjaApi.Repositories {
    public interface INinjaRepository {
        Task<IEnumerable<Ninja>> ReadAllAsync();
        //...
    }
}

// DAL implementation
namespace ForEvolve.Blog.Samples.NinjaApi.Repositories {
    public class NinjaRepository : INinjaRepository {
        private readonly INinjaMappingService _ninjaMappingService;
        private readonly ITableStorageRepository<NinjaEntity> _ninjaEntityTableStorageRepository;

        public NinjaRepository(INinjaMappingService ninjaMappingService, ITableStorageRepository<NinjaEntity> ninjaEntityTableStorageRepository) {
            _ninjaMappingService = ninjaMappingService ?? throw new ArgumentNullException(nameof(ninjaMappingService));
            _ninjaEntityTableStorageRepository = ninjaEntityTableStorageRepository ?? throw new ArgumentNullException(nameof(ninjaEntityTableStorageRepository));
        }

        //...

        public Task<IEnumerable<Ninja>> ReadAllAsync() {
            throw new NotImplementedException();
        }

        //...
    }
}

Assembly1 / BAL 将向下引用 DAL 并定义其抽象。

// BLL Interface
namespace ForEvolve.Blog.Samples.NinjaApi.Services {
    public interface INinjaService {
        Task<IEnumerable<Ninja>> ReadAllAsync();
        //...
    }
}

// BLL Implementation
namespace ForEvolve.Blog.Samples.NinjaApi.Services {
    public class NinjaService : INinjaService {
        private readonly INinjaRepository _ninjaRepository;
        private readonly IClanService _clanService;

        public NinjaService(INinjaRepository ninjaRepository, IClanService clanService) {
            _ninjaRepository = ninjaRepository ?? throw new ArgumentNullException(nameof(ninjaRepository));
            //...
        }

        //...

        public Task<IEnumerable<Ninja>> ReadAllAsync() {
            throw new NotImplementedException();
        }

        //...
    }
}

Assembly0 / API / Comopsition Root 将引用较低层

// API
namespace ForEvolve.Blog.Samples.NinjaApi.Controllers {
    [Route("v1/[controller]")]
    public class NinjaController : Controller {
        private readonly INinjaService _ninjaService;
        public NinjaController(INinjaService ninjaService) {
            _ninjaService = ninjaService ?? throw new ArgumentNullException(nameof(ninjaService));
        }

        [HttpGet]
        [ProducesResponseType(typeof(IEnumerable<Ninja>), StatusCodes.Status200OK)]
        public Task<IActionResult> ReadAllAsync() {
            throw new NotImplementedException();
        }

        //...
    }
}

作为组合根,它将了解所有依赖关系,以便能够将抽象映射到它们的实现。

上述结构没有循环依赖。

【讨论】:

  • 这似乎是一种正确的排列方式,即使只是查看名称空间。我之所以将较低级别的接口与较高级别的实现一​​起使用,例如,您可能有许多 DAL 程序集,这些程序集可以在不接触 BLL 程序集的情况下互换。使用上述方法,我需要在同一个程序集中创建所有可互换的 DAL 实现,或者更改 BLL 引用的 DAL 程序集。但是,我想在组合根方面无论如何我都必须做这样的事情。感谢您的反馈。
  • @Bondolin 如果您想将所有接口/抽象与它们的实现分开以允许重用,您可以将所有接口/抽象移动到它们自己的程序集中。我建议阅读清洁架构
猜你喜欢
  • 1970-01-01
  • 2020-11-18
  • 2018-03-03
  • 1970-01-01
  • 2010-11-29
  • 1970-01-01
  • 2019-08-17
  • 2017-03-24
相关资源
最近更新 更多