依赖注入循环项目依赖
Dependency Injection Cyclic Project Dependencies
注意:下面是一个类似的问题————但没有完全解决我的情况。
我正在尝试开发应用程序架构。我目前已经确定需要三个不同的层: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个定义,才能构建依赖图。到目前为止,一切都说得通了。
我 运行 遇到麻烦的地方是当我开始将事情分成不同的程序集时。我目前的理解(或缺乏理解)如下:
- 程序集 0 包含 API 实现以及 BLL 接口,用于实现可互换的 BLL。
程序集 1 包含 BLL 实现以及 DAL 接口;因此 Assembly 1 的 DAL 接口依赖于 Assembly 0。
最后,程序集 2 包含 DAL 实现,它依赖于程序集 1 的 BLL 接口。
但是,程序集 0 还包含组合根,它依赖于 BLL 和 DAL 接口,以及 API、BLL 和 DAL 实现。
因此程序集0和程序集1之间存在循环项目依赖关系,其中0中的根依赖于1中的BLL实现,1中的BLL实现依赖于0中的BLL接口。
到目前为止我能做的最好的事情就是让 BLL 接口也驻留在程序集 1 中,但这似乎违背了接口的全部目的。
请哪位好心人指出我的误解在哪里,如果可能,请问如何解决
设计?
编辑
首先,我可能应该通过不仅仅是标签来阐明我的设置 - 我正在使用 ASP.NET Web API 应用程序层(不是 .NET Core)。
其次,为了进一步说明我的预期设置,如下所示(再次基于上面引用的来自 forevolve.com 的示例):
// 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();
...
}
}
// 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
- AL - 应用层(Web API 控制器)
- DTO - 数据传输对象(可序列化数据,又名视图模型,在 BLL 中制作并在 AL 中使用)
- IBLL - BLL 接口
- 程序集 1
- BLL - 业务逻辑层实现(域逻辑、业务规则、验证等)
- 业务对象(具有行为的数据,在 DAL 中生成并在 BLL 中使用)
- IDAL - DAL 接口
- 程序集 2
- DAL - 数据访问层(存储库、实体重构等)
- 数据访问对象(又名 EF 实体,数据库记录的 ORM 表示,在 DB 中制作并在 DAL 中使用)
- DB - Dogbert 的骨头
- DI - 依赖注入(App Root 中的容器)
请随时批评,欢迎所有反馈。特别是 Dogbert 的骨头更多。
选项:
- 将依赖项根移动到单独的程序集中(这样它就是唯一依赖于所有其他程序集的程序集
- 使用容器的声明式初始化(如果您使用的容器支持它)这样您就可以在一些外部配置文件中定义要注册的内容 classes/interfaces 以及它们所在的位置。具体配置取决于您喜欢的容器
乍一看,抽象似乎在错误的程序集中。
从底层开始(基础层或核心)
程序集 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();
}
//...
}
}
作为组合根,它将了解所有依赖关系,以便能够将抽象映射到它们的实现。
以上结构没有循环依赖。
注意:下面是一个类似的问题——
我正在尝试开发应用程序架构。我目前已经确定需要三个不同的层: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个定义,才能构建依赖图。到目前为止,一切都说得通了。
我 运行 遇到麻烦的地方是当我开始将事情分成不同的程序集时。我目前的理解(或缺乏理解)如下:
- 程序集 0 包含 API 实现以及 BLL 接口,用于实现可互换的 BLL。
程序集 1 包含 BLL 实现以及 DAL 接口;因此 Assembly 1 的 DAL 接口依赖于 Assembly 0。
最后,程序集 2 包含 DAL 实现,它依赖于程序集 1 的 BLL 接口。
但是,程序集 0 还包含组合根,它依赖于 BLL 和 DAL 接口,以及 API、BLL 和 DAL 实现。
因此程序集0和程序集1之间存在循环项目依赖关系,其中0中的根依赖于1中的BLL实现,1中的BLL实现依赖于0中的BLL接口。
到目前为止我能做的最好的事情就是让 BLL 接口也驻留在程序集 1 中,但这似乎违背了接口的全部目的。
请哪位好心人指出我的误解在哪里,如果可能,请问如何解决 设计?
编辑
首先,我可能应该通过不仅仅是标签来阐明我的设置 - 我正在使用 ASP.NET Web API 应用程序层(不是 .NET Core)。
其次,为了进一步说明我的预期设置,如下所示(再次基于上面引用的来自 forevolve.com 的示例):
// 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();
...
}
}
// 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
- AL - 应用层(Web API 控制器)
- DTO - 数据传输对象(可序列化数据,又名视图模型,在 BLL 中制作并在 AL 中使用)
- IBLL - BLL 接口
- 程序集 1
- BLL - 业务逻辑层实现(域逻辑、业务规则、验证等)
- 业务对象(具有行为的数据,在 DAL 中生成并在 BLL 中使用)
- IDAL - DAL 接口
- 程序集 2
- DAL - 数据访问层(存储库、实体重构等)
- 数据访问对象(又名 EF 实体,数据库记录的 ORM 表示,在 DB 中制作并在 DAL 中使用)
- DB - Dogbert 的骨头
- DI - 依赖注入(App Root 中的容器)
请随时批评,欢迎所有反馈。特别是 Dogbert 的骨头更多。
选项:
- 将依赖项根移动到单独的程序集中(这样它就是唯一依赖于所有其他程序集的程序集
- 使用容器的声明式初始化(如果您使用的容器支持它)这样您就可以在一些外部配置文件中定义要注册的内容 classes/interfaces 以及它们所在的位置。具体配置取决于您喜欢的容器
乍一看,抽象似乎在错误的程序集中。
从底层开始(基础层或核心)
程序集 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();
}
//...
}
}
作为组合根,它将了解所有依赖关系,以便能够将抽象映射到它们的实现。
以上结构没有循环依赖。