Note: This following is a similar question -- How to avoid Cyclic Dependencies when using Dependency Injection? -- but does not quite address my situation.
I am trying to develop an application architecture. I have currently identified the need for three distinct layers: API, Business, and Data Access. I am aiming for a loosely coupled, dependency injection design based on the one here: https://www.forevolve.com/en/articles/2017/08/11/design-patterns-web-api-service-and-repository-part-1/#the-patterns.
To summarize, NinjaController
(API) contains INinjaService
(BLL), which is implemented by NinjaService
(also BLL) which contains INinjaRepository
(DAL), which is implemented by NinjaRepository
(also DAL).
Since I am intending to use dependency injection, there is also a composition root, which would have to depend on all the above 5 definitions, so as to build the dependency graph. So far, everything makes sense.
Where I run into trouble is when I start to split things up into different assemblies. My current understanding (or lack thereof) is as follows:
Assembly 1 contains the BLL implementation as well as the DAL interface; thus Assembly 1 depends on Assembly 0 for its DAL interface.
Finally Assembly 2 contains the DAL implementation, which depends on Assembly 1's BLL interface.
However, Assembly 0 also contains the composition root, which depends on both the BLL and DAL interfaces, as well as the API, BLL, and DAL implementations.
So there is a cyclic project dependency between Assembly 0 and Assembly 1, where the root in 0 depends on the BLL implementation in 1 and the BLL implementation in 1 depends on the BLL interface in 0.
The best I can do so far is to have the BLL interface reside in Assembly 1 as well, but that seems to defeat the entire purpose of the interface.
So would someone kindly point out where my misunderstanding is, and if possible, how to achieve this design?
First, I probably ought to have clarified my setup by means of more than a tag - I am using an ASP.NET Web API application layer (not .NET Core).
Second, to further illustrate my intended setup, something like the following (again based on the above cited example from forevolve.com):
Assembly 0 (see 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();
...
}
}
Assembly 1 (see https://www.forevolve.com/en/articles/2017/08/30/design-patterns-web-api-service-and-repository-part-6/ and 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();
...
}
}
Assembly 2 (see 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();
}
...
}
}
Unfortunately this example project is using .NET Core, and I am at this point merely trying to grasp the concept of using DI in a multi-tier web application. So I am trying to just get a better understanding of the concept, though I do need to eventually bring it home to a non-Core ASP.NET Web API.
The following diagram represents the approach I am now considering taking.
Terms:
Please feel free to critique, all feedback welcome. Especially more bones for Dogbert.
Options: