views:

277

answers:

5

Hi, I want to implement architecture involving different .NET assemblies (i.e. modules). Some of these modules should provide services which are used as .NET interfaces by other modules. The modules should be loaded and registered dynamically at runtime, I do not want to have "hardcoded" dependencies between them.

Example:

  Module1.dll:
    Defines a class implementing interface IService1
  Module2.dll:
    Uses the class provided by Module1 through the interface IService1 

The problem is where to put the definition of IService1: Both modules need this definition. But since Module2 should also work in absence of Module1 (the availability of the service is checked at runtime) I don't want Module2.dll to reference Module1.dll directly.

One possibility is to split every module into two assemblies (interface and definition), but that would mean that I double the number of DLLs which I do not want.

I thought also of using a separate "Interface Dll" i.e. one single assembly containing all interface definitions, but then again, if I change one single interface or if I add new modules (with new iterfaces), I need to update this central DLL and therefore all other modules (since they all depend on it...)

What I would like is to link the interface definition into both Module1 and Module2, but I do not know if resp. how this is possible.

I'd appreciate any ideas

Edit Perhaps the example was a bit too simple: There could be a scenario where Module1a.dll, Module1b.dll etc. provide implementations for IService and Module2a.dll, Module2b.dll etc. are using them...

A: 

Module2.dll should declare IService and Module1.dll should reference Module2.dll. Then, Module2.dll should use reflection (perhaps with a configuration setting) to create the type declared in Module1.dll and cast it to IService:

// in module2.dll
var serviceInstance = (IService)Activator.CreateInstance(typeToLoad);
Mehrdad Afshari
As I said in the question, I do not want to have direct dependencies between the dlls. See my comment on Martin's answer below ...
MartinStettner
You said you don't want Module2.dll to reference Module1.dll. I thought the other way is OK. You can always go with a separate DLL as already said. The key point is, wherever you declare your interface, consumers and implementors should have direct references to it but you don't need to directly reference the implementation assembly.
Mehrdad Afshari
A: 

What's wrong with putting the interface into Module2.dll?

IService1 is an interface (or a contract) defined by Module2.dll that has to be implemented each class that is to be used by Module2.dll.

In response to your edit: in that case I would probably put the interface(s) into a separate assembly. This can also be useful if the implementation (of the interface) is done by a third-party and you do not want to give them more than necessary (i.e. only the definitions of the interfaces).

M4N
Unfortunately it's not that simple: IService could be implemented by more than one module: imagine the service being a "Contact provider" and having one module for Outlook contacts and another one for Lotus notes. Depending on which module is present, other modules should be able to access Outlook and Notes contacts using the same interface IContactProvider. These other modules should also work in the absence of any contact provider ...
MartinStettner
Just to make it clear, my previous comment was about your first answer... thanks for the edit!
MartinStettner
A: 

One common approach to this is to have a separate assembly for your "contract" interfaces, one that does not contain any implementation details, just the interface definitions.

Module1 references CoreInterfaces.dll (or whatever you call it) Module2 references CoreInterfaces.dll

Tim Jarvis
+1  A: 

This isn't about modules and dll, this is about OO design.

Is IService1 expected to be implemented by a number of other modules, or is it unique to Module1? If you are wanted a number of different modules to all implement IServer1 with out depending on one another, then you will need a dll holding this interface and any of its supporting file.

This wont double the number of dll's you have, it will only add 1 extra.

So either define your interface in Module1.dll or in a 3rd dll for just interfaces (contracts).

Any time you have a number of modules to deal with you will have a Common module holding the core files that everything uses (interfaces, api, etc..). The module usually doesnt reference other modules.

shimpossible
Thanks for pointing that out. I think for me it's more a deployment problem: I would like to ship modules as independently as possible. For that it would be nicest if each module would correspond exactly to one dll and would work without any additional references.
MartinStettner
Use interfaces or abstract classes defined in a 3rd common module. Module1 will interact with Module2 through the use of these common classes/interfaces. All Module1/2 need to know is they are talking to an IService that returns a IBaseClass, they dont need to know which module defines BaseClassEx, just that its a IBaseClass.The module defining IService, IBaseClass is going to be a common module that is only made once. You can then reference by any number of additional modules that you want. When making this 'common' module, do not reference you're other modules from it.
shimpossible
A: 

I think you should look into designing by contract. This way all components will be totally isolated from each other from the compile-time. But this will shift the work to maintaining the manually-written contracts instead of a compile-time-checked code interface.

Basically, you'd have the same interface declaration on both side of the wire and then those are wired automatically by an agent which is either a custom implementation of Castle DynamicProxy and plus some Reflection stuff or an IoC container like Ninject which supports service locators.


Basically, you write the "contract" on both Module1.dll and Module2.dll

public interface IFooProvider {
    void Foo GetFoo();
}

Then you need a service locator implementation which is a central piece of code that will help module register themselves and consumer finds the registered module. This is the normal work of a DI or IoC Container that I've mentioned.

It'll look one way or another like this:

public interface IServiceLocator {

    object LocateProvider<ContractType>();

    void RegisterProvider<ContractType>(object implementation);
}

Basically, Module1, on load, should register itself with the ServiceLocator along withs the contract it is providing and then Module2, on load, would call the "LocateProvider" method supplying the contract it wants and gets the Module1 implementation.

Something alongs:

public class Module1Implementation : IProviderContract {
    void Foo GetFoo() { return new Foo(); }
}

public class Main {
    public void Main() {

        var locator = ServiceLocator.GetLocator();
        locator.RegisterProvider<IFooProvider>(new Module1Implementation());

    }
}

And in Module2.dll:

public class Consumer {

    public IFooProvider FooProvider { get; set; }

    public Consumer() {
        var locator = ServiceLocator.GetLocator();
        FooProvider = locator.LocateProvider<IFooProvider>();

        // if Module1.dll is loaded, the locator should supply 
        // Module1Implementation for you
    }
}

Then all "providers" module should reference the service locator DLL which should be 1 single extra DLL or it might have been built right into the core application itself. And on load, every module should register itself with the ServiceLocator along with the "contract" they are representing.

Then, the last piece of the puzzle, is how you make contracts look-a-like which are defined in separate DLLs to appear as the same, which why I've mentioned Castle DynamicProxy and Reflection. How to do it is beyond this already long answer so I'll leave it to Google. :-)


How will this helps? All dependencies between all DLLs but the service locator implementation are removed. So your deployment headache is down to:

  • Make sure the service locator implementation is rock solid, since its the only thing that can't be easily changed.
  • Make sure components that need to communicate with each other share the same "contract"
  • Should the contract changes, use the Facade pattern to enable backward-compatibility.

Whew! That was kinda long and buzzwords-filled but I hope it answers your question.

chakrit