7

I am trying to learn about DI, to have a better understanding of IoC, and the other benefits.

Pre DI, I have a project that has a UI project (MVC), a BusinessLogic project and a DataAccess project. I also have a SharedLib project. All projects have a reference to SharedLib. UI has a reference to BusinessLogic, and BusinessLogic has a reference to DataAccess.

I want to add the Interfaces now. So I go to my DataAccess, and add an Interface for each class, and populat them with their methods. I do the same for the business logic layer.

But in order to inject the DataAccess class, which I instantiate in the BusinessLogic class in the UI project, I need a reference to my Data project, because the UI project (correctly, I think) has no idea what an 'IDataAccess' interface is. The only fix I can see is to add a project reference in my UI to my DA project - which seems wrong.

And if I try add Unity as my container (One day in the future, once i work out how that all works), and want to initialise my Interface/Class relationships in the UI project - same issue.

Maybe the interfaces must go in some shared project? Or one project up? How should this be handled?

Robert Moskal
  • 21,737
  • 8
  • 62
  • 86
Craig
  • 18,074
  • 38
  • 147
  • 248
  • Project references (e.g. circular references) and DI are two separate concerns. Adding a reference may not necessarily be a bad thing. A proper answer will depend on what tool is used for DI. I would recommend you investigate whatever DI tool you're comfortable with, and evaluate its feature set against your needs. – Trevor Ash Jul 05 '16 at 01:22
  • To elaborate a bit on what I said above; with many DI tools you have to wire up "somewhere" what services are registered to what interfaces. Once registered, you aren't manually creating instances yourself. When you use DI properly, your UI won't be creating an instance of your business object,it will be injected into the constructor of your UI's controller, just the same as DataAccess will be injected into the business logic classes. E.g. spend a little more time understanding how DI tools operate, especially for MVC applications as they are a special creature. – Trevor Ash Jul 05 '16 at 01:33
  • You can also make your classes internal to the assembly they belong in and only expose the interfaces as public. A DI container like Autofac can wire up all the classes from an assembly and make them available through their interfaces only. – Ian Mercer Jul 05 '16 at 04:12

1 Answers1

9

If you don't want the references between projects you could look into factories/abstract factories.

Your UI knows about your business layer, so you want to define a factory in your business layer which knows how to use the data layer. Then you handle all your DI in your composition root (the UI project in this example).

A simple example below using a console app as the UI, sticking to the references you stated in your question

Data layer

public interface IDataAccess
{
    string GetData();
}

public class XmlDataAccess : IDataAccess
{
    public string GetData()
    {
        return "some data";
    }
}

Business layer

public interface IDataAccessFactory
{
    IDataAccess GetDataAccess();
}

public class XmlDataAccessFactory : IDataAccessFactory
{
    public IDataAccess GetDataAccess()
    {
        return new XmlDataAccess();
    }
}

public class BusinessLogic
{
    IDataAccessFactory dataAccessFactory;

    public BusinessLogic(IDataAccessFactory dataAccessFactory)
    {
        this.dataAccessFactory = dataAccessFactory;
    }

    public void DoSomethingWithData()
    {
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        Console.WriteLine(dataAccess.GetData());
    }

    public string GetSomeData()
    {
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        return dataAccess.GetData();
    }
}

UI

static void Main(string[] args)
{
    IUnityContainer container = new UnityContainer();
    container.RegisterType<IDataAccessFactory, XmlDataAccessFactory>();

    var logic = container.Resolve<BusinessLogic>();
    logic.DoSomethingWithData();

    string useDataInUI = logic.GetSomeData();
    Console.WriteLine("UI " + useDataInUI);

    Console.ReadKey();
}

It's a contrived example so it looks like abstraction for nothing, but with a real world example it would make more sense.

e.g. you might have a bunch of different data access classes in your data layer database, xml files, etc. so you might define a factory for each in your business layer.


Using abstract factories

The factory could contain a lot more logic about the nitty gritty of the data layer, or as an abstract factory provide a set of individual factories to the business logic layer.

Business layer

You might instead have an abstract factory in the business layer such as

public interface IPlatformFactory
{
    IDataAccessFactory GetDataAccessFactory();
    IPricingFactory GetPricingFactory(); // might be in the business project, or another project referenced by it
}

with a concrete factory

public class WebPlatformFactory : IPlatformFactory
{
    IDataAccessFactory GetDataAccessFactory()
    {
        return new XmlDataAccessFactory();
    }

    IPricingFactory GetPricingFactory()
    {
        return new WebPricingFactory(); // not shown in the example
    }
}

(You might have additional concrete factories such as RetailPlatformFactory, etc.)

Your BusinessLogic class would now look something like

public class BusinessLogic
{
    IPlatformFactory platformFactory;

    public BusinessLogic(IPlatformFactory platformFactory)
    {
        this.platformFactory = platformFactory;
    }

    public void DoSomethingWithData()
    {
        IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        Console.WriteLine(dataAccess.GetData());
    }

    public string GetSomeData()
    {
        IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
        IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
        return dataAccess.GetData();
    }
}

Data layer

Your business layer no longer needs to provide an IDataAccessFactory to your UI so you can move it into your data layer in this example. So the data layer classes would be

public interface IDataAccess
{
    string GetData();
}

public class XmlDataAccess : IDataAccess
{
    public string GetData()
    {
        return "some data";
    }
}

public interface IDataAccessFactory
{
    IDataAccess GetDataAccess();
}

public class XmlDataAccessFactory : IDataAccessFactory
{
    public IDataAccess GetDataAccess()
    {
        return new XmlDataAccess();
    }
}

UI

Now you'd in the UI you'd configure the container and perform similar actions as

static void Main(string[] args)
{
    IUnityContainer container = new UnityContainer();
    container.RegisterType<IPlatformFactory, WebPlatformFactory>();

    var logic = container.Resolve<BusinessLogic>();
    logic.DoSomethingWithData();

    string useDataInUI = logic.GetSomeData();
    Console.WriteLine("UI " + useDataInUI);

    Console.ReadKey();
}

The UI then knows nothing about the data layer/access, it's just handing off the factory creation to the business layer, which holds the data (and pricing) references.

Some recommended reading:

Composition Root

Implementing an Abstract Factory

Compose object graphs with confidence

Tone
  • 1,701
  • 1
  • 17
  • 18