Search code examples
c#dependency-injectioninterfaceshareproject-structure

How to share interface between 3 projects in C#


I'm trying to understand sharing interfaces between modules and I faced following problem: I have three projects in which:

  • Console Application - is main project, which wants to fetch data.
  • Core Library - is library, which decides which data service will be used based on shared interfaces.
  • Data Library - is library, which provides necessary data

Basic concept is that when I would like to change between databases like MySQL and SQL Server I do not have change all base code but only would need to change one module which is Data Library. Thanks to Core Library other projects doesn't need to know what is happening in w Data Library, because Core Library is one which manages which class should be used.

Projects references look like:

ConsoleApplication --> CoreLibrary <-- DataLibrary

So I've created following projects to present the problem.

Console application

class Program
{
    static void Main(string[] args)
    {
        var core = new Core();
        var dataService = core.GetDataService();
    }
}

Core Library

public class Core
{
    public IDataService GetDataService()
    {
        return ???
    } 
}

public interface IDataService
{
    public IUserData UserData { get; set; }
}

public interface IUserData
{
    public string Name { get; set; }
    public string LastName { get; set; }
}

Data Library

public class Data : IDataService
{
    public IUserData UserData { get; set; }
}
public class UserData : IUserData
{
    public string Name { get; set; }
    public string LastName { get; set; }
}

In Core Library's method GetDataService I can't return Data's instance of object because it is not referenced to DataLibrary. If I reverse the reference between them i will not be able to implement interface from CoreLibrary. In the case I want to reference both to each other i get an error "A reference to 'DataLibrary' could not be added. Adding this project as a reference would cause a circular dependency".

What mistake do I do. Is it good approach but I'm missing something? Or maybe the whole thing is wrong and I should consider other solution?


Solution

  • The Console application is the startup path of your application and will always require a dependency on all other libraries in the system. Even the Console application would not directly depend the data access library, it will not compile and function without that indirect dependency. In other words, assembly dependencies are transitive. In this regard, there is a wealth of information in the answers to this question that you should certainly checkout. It's too much to repeat here.

    The only way to prevent a dependency from the startup path to an assembly is by the use of late binding. This means you dynamically load the assembly at startup as plugin. Although in theory, you can load plugins in .NET in a few lines of code, the wider the contract such plugin exposes, the more work it is to design and setup the plugin system. For instance, if each plugin contains only plugins that implement a single IPlugin interface, loading is fairly easy, but it gets harder with the amount of interfaces that are implemented. Eventually, you'll end up with plugin assemblies that contain their own parts of the logic that builds object graphs, possibly integrated with the used DI Container library.

    Late binding is typically not a great fit for libraries such as data access layers, as they usually expose a wide API. And as long as there is no requirement to dynamically switch from data access technologies without the need to recompile your software, you should prevent the complexity that late binding brings to the table.

    A common practice with Dependency Injection (DI) is to make the the application's entry point the place where all application components (the classes that contain the application's behavior) are created. In DI terminology, this place is called the Composition Root.

    This means that the Console application itself is the most reasonable place to create the Data Library's Data class. For instance, like this:

    class Program
    {
        static void Main(string[] args)
        {
            var dataService = new DataLibrary.Data();
    
            var appService = new CoreLibrary.ApplicationService(dataService);
    
            appService.DoSomething();
        }
    }
    

    This method allows the central Core Library to stay isolated, allows the data access layer to be swapped in the future, and the only responsibility of the Main method is to 'tie' everything together and kick-start the application.

    Essential in achieving this is 'inverting' the dependency from the Core library to the Data access library. This is what you did by letting your DataLibrary depend on the CoreLibrary. Because of this, you are applying the Dependency Inversion Principle, which advocates this inversion.