Search code examples
c#data-access-layern-tier-architecturebll

What option other than a BLL instantiating a DAL allows for unit testing in an n-tier solution without exposing DAL to the UI or BLL to DAL?


I have a layered solution as follows:

  • UI (User Interface)
  • BLL (Business Logic Layer)
  • DAL (Data Access Layer)
  • SharedEntities (A VS Project with entity POCOs only)

I’d like for the BLL to have a service called GetProductList() which is implemented in my DAL layer. I thought about defining an interface in the BLL and DAL implementation as follows:

Option A:

// Interface defined in BLL 
public interface IDataServices
{
  List<Product> GetProductList();
}

// Interface implemented in DAL
public class DataServices : IDataServices
{
    Public List<Product> GetProductList()
    {
      return //do some database work here and return List<Product>;
    }
}

If I want this to be implemented in the DAL then I would have to have the DAL project reference the BLL project in order to see the interface definition of IDataServices. Or, I could replicate the interface definition in the DAL but then I will end up with duplicate code to maintain (the same interface definition in the BLL and DAL).

Option B: Another way I could do this is to forget the interface idea and just make the following concrete class and method call in the BLL which the UI can use:

// Concrete class defined in the BLL
public class DataServices
{
    Public List<Product> GetProductList()
    {
         DAL aDAL = new DAL();
         Return (aDAL.GetProductList());
    }
}

This is easy enough but then the BLL sees the DAL and has a reference to it but is this really a bad thing here? As long as the BLL does not use any database objects (i.e. datasources, connect strings, etc.) to satisfy a request and the DAL conforms to matching the names of the services I define in the BLL DataServices class isn’t that enough? All the talk I hear about swapping in another database engine can still be done by just making sure the next DAL provides the same services the BLL identifies in the DataServices class such as GetProductList(). In this setup the UI still does not know anything about the DAL and the DAL does not know anything about the BLL. If I entertain the idea of using dependency injection to avoid instantiating the DAL it in the BLL it would mean instantiating it in the UI to be passed into the BLL. I would not want to do this which would give the UI access to the DAL methods.

Option C: I took a short look at the Unity Container but that tool suggested registering all the interfaces and concrete class upfront at the entry point which would have been the UI which in turn ended up giving the UI visibility to both the BLL and DAL which seemed even worse. I saw a reference to use MEF with Unity to get around the problem of the entry point seeing all the layers but also saw that if you do that you can’t really unit test such a configuration. Seems like a lot of work and complexity compared to option B.

Option D: And yet another option I have thought about would be to create a facade layer (another VS Project) between the BLL and DAL. This does not seem to make much sense unless I ended up with a lot of DAL methods which were of no concern of the BLL so they would have to be hidden; allowing the DAL Facade to only show what the BLL needs. If I were to swap in another database I would still have to create methods which the facade needed based on the needs of the BLL as I mentioned in option B.

So based on all this I’m thinking of going with option B but I would like some community input here. What else can I do which meets the following:

  • 1) Does not let the UI see the DAL
  • 2) Does not let the DAL see the BLL
  • 3) The solution still allows for all layers to be unit tested

Solution

  • IDataServices should be defined in the DAL. but then the BLL sees the DAL and has a reference to it that's the natural way to do it. A project can reference the layer below it. If you don't allow referencing downwards there can be no references at all.

    Note, that a Reflection reference is still a reference because you can't change the layer below without changing the layer above. Dependencies are not a compiletime-only concept. Option B does not add any good. It removes a compiletime dependency but does not remove the runtime dependency.

    The point of removing a dependency from A to B is that you can change B without changing A. That's the entire point. Runtime dependencies still count.

    Regarding C: You can make the UI ask the BLL to register its dependencies. The BLL can then ask the DAL to register. That way the UI is shielded from the DAL.

    D: I don't know what this would accomplish.

    Your constraints can easily be satisfied by making the DAL define IDataServices.