0

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.

1
  • 1
    Ok at first glance, the abstractions appear to be in the wrong assemblies Commented Nov 18, 2019 at 22:45

2 Answers 2

1

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
Sign up to request clarification or add additional context in comments.

3 Comments

I'm leaning towards the first option; is that possible in an ASP.NET Web API?
@Bondolin sure - controllers can be in separate assembly just fine. Note that your actual problem is "0 contains the API implementation as well as the BAL interface" - you explicitly setup circular dependency there - moving "BAL interface" to separate assembly (or even the same as "BAL implementation" - which is fine for regular case where you have just prod and test implementations) would solve your particular case.
I guess I was hanging up on needing to have API controllers in the main assembly. Putting API controllers and their corresponding BLL interfaces in an assembly separate from both the entry point and the composition root would probably be what I want.
1

At first glance, the abstractions appear to be in the wrong assemblies.

Starting from the bottom (base layer or core)

Assembly 2 / DAL should be focused on its domain types

// 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 will reference down to DAL and also define its abstractions.

// 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 will reference lower layers

// 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(); } //... } } 

And as composition root it will be aware of all the dependencies to be able to map abstractions to their implementations.

No cyclic dependencies with the above structure.

2 Comments

This appears to be a correct way of arranging them, even just looking at the namespaces. My reason for having the lower-level interface with the higher-level implementation was so, for example, you could have potentially many DAL assemblies that could be interchanged without touching the BLL assembly. With the above approach I would need to either make all interchangeable DAL implementations in the same assembly or change which DAL assembly the BLL is referencing. However, I suppose I have to do something like this anyway when it comes to the composition root. Thanks for the feedback.
@Bondolin you can move all the interfaces/abstraction into their own assemblies if you want to separate them from their implementations to allow reuse. I would suggest reading up on Clean Architecture

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.