Search code examples
c#asp.net-web-apidependency-injectionarchitecturedata-access-layer

Dependency Injection Cyclic Project Dependencies


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 0 contains the API implementation as well as the BLL interface, for an interchangeable BLL to implement.
  • 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?

EDIT

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.

Edit 2

The following diagram represents the approach I am now considering taking.

Application Architecture

Terms:

  • PL - Presentation Layer (ASPX, HTML, JS, CSS)
  • API - Archibald's Pet Iguana
  • Assembly 0
    1. AL - Application Layer (Web API controllers)
    2. DTO - Data Transfer Objects (serializable data, aka View Models, made in BLL and used in AL)
    3. IBLL - BLL interface
  • Assembly 1
    1. BLL - Business Logic Layer implementation (domain logic, business rules, validation, etc.)
    2. Business Objects (data with behavior, makde in DAL and used in BLL)
    3. IDAL - DAL interface
  • Assembly 2
    1. DAL - Data Access Layer (repository, entity reconstitution, etc.)
    2. Data Access Objects (aka EF Entities, ORM representation of database records, made in DB and used in DAL)
    3. DB - Dogbert's Bone
  • DI - Dependency Injection (container in App Root)

Please feel free to critique, all feedback welcome. Especially more bones for Dogbert.


Solution

  • Options:

    • move dependency root into separate assembly (this way it is the only one that depends on all other assemblies
    • use declarative initialization of the container (if one you use supports it) so you can define in some external configuration file what classes/interfaces to register and where they reside. Exact configuration depends on container you like