2

I've a root project (root), some modules (A, B), and these modules have some external dependencies (Z). I'm using an IoC container.

I'm using C# here, but is a generic pattern question. Let say that my container is services, and I can initialize IoC configuration with use of extension methods. So in root project I'm writing:

services.AddModuleA();
services.AddModuleB();

On module A I've this method:

public static void AddModuleA(this IServiceCollection services)
{
    // Init module internal services.
    //services.AddScoped<IA1Service, A1Service>();
    //...

    // Init module external dependencies.
    services.AddDependencyZ();
}

On module B I've a similar method:

public static void AddModuleB(this IServiceCollection services)
{
    // Init module internal services.
    //...

    // Init module external dependencies.
    services.AddDependencyZ();
}

Obviously Z dependency was already been added, so this tells me that I should not configure it inside a module extension method, and I should rather declare its configuration in root project:

services.AddModuleA();
services.AddModuleB();
services.AddDependencyZ();

But doesn't this break the Least Knowledge principle? Importing and configuring a module A (or B) will bring to a cascade explicit declaration of all dependency configurations.

And related question, is declaring the extension methods AddModuleA() and AddModuleB() a bad pattern at all? Could be a better idea to configure directly on root only services that I will use? And in this case (a bit extreme), what about config of internal use only class?

tmm360
  • 404
  • 4
  • 13
  • 2
    Consider using a `TryAddDependencyZ()` that checks first that it has not already been added before trying to add it. – Nkosi May 24 '19 at 04:15

1 Answers1

2

Supposing that those extension methods are defined in your application's Composition Root, why don't you just remove the call to AddDependencyZ from within AddModuleA and AddModuleB?

public static void AddModuleA(this IServiceCollection services)
{
    // Init module internal services.
    //services.AddScoped<IA1Service, A1Service>();
    //...
}

public static void AddModuleB(this IServiceCollection services)
{
    // Init module internal services.
    //...
}

You would then, as you write, configure the container in the Composition Root (this is the Register phase in the RRR pattern):

services.AddModuleA();
services.AddModuleB();
services.AddDependencyZ();

Does this break the Least Knowledge principle? Yes, it does, but that's one of the many drawbacks of using a DI Container.

I recommend Pure DI instead of using a DI Container. That would make the problem disappear because all the code in the OP would be redundant.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Is it better for a module to export a callback function that register it’s dependencies or for a single file that imports the classes of each module and do all the dependency registering for all the modules ? – calveeen Jun 19 '22 at 05:27
  • @calveeen The latter. That's the point of the Composition Root. If you do the former, you couple the module to a particular DI framework. Also, different application contexts may require different configurations (lifetimes, etc.). The module can't predict how it will be used, so a callback may not offer sufficient reusability. – Mark Seemann Jun 19 '22 at 22:23
  • I see, OPs method of registering dependencies seem to be that of callback based. The composition root approach would have the `addModuleA` and `addModuleB` methods inside the composition root package rather than in `moduleA` and `moduleB` ? – calveeen Jun 19 '22 at 23:54
  • @calveeen, yes. – Mark Seemann Jun 20 '22 at 08:08