Autofac 模块应该注册自己的依赖模块吗?

Should Autofac modules register their own dependent modules?

假设我有一个应用程序使用三个库:lib1、lib2 和 lib3。在每个库中,我都实现了一个 Module 来注册在该库中实现的依赖项。

其中一些实现有它们自己的依赖项,例如,lib2 和 lib3 可能都需要 lib1 中存在的一些实现。

我的问题是,我是否让 lib2 和 lib3 中的模块将 lib1 中的模块注册为它们 Load 实现的一部分?如果我的应用程序注册了 lib2 和 lib3 的模块,这是否会注册该模块两次?

或者我是否避免让一个模块注册另一个模块,将其留给应用程序,缺点是在启动时可能会丢失一些注册?

一般情况下,应该只有一个包含配置的库。这个库是启动项目,应用程序中的这个地方通常被称为 Composition Root. Typically only startup projects have a composition root, and only when multiple start-up projects in a single solution share a lot of duplicate registration, you start to extract this code to a common location that those composition roots can reuse. But be careful: in general Composition Roots should not be reused.

我不建议在您的图书馆内进行注册。在大多数情况下,您应该有一个组合根来组合您的所有应用程序。

A Composition Root is a (preferably) unique location in an application where modules are composed together.

这里解释了这个概念composition root

顺便说一句,如果您多次注册一个模块,Autofac 将多次注册组件。如果你的库中必须有模块,你应该只创建注册库组件的模块。

Autofac Module 只不过是帮助注册组件的外观。它不应该为了注册已知是其自身注册的依赖项的实现而调用其他模块,因为这会将两个模块联系在一起,增加耦合。

此外,如果同一个模块被多次注册,注册本身也会被多次应用,因为 Autofac 不会跟踪已经注册的模块。这通常不会造成问题,除非组合根无意中取消了任何旨在覆盖现有注册的注册。

我知道有点晚了,但我认为回答这个问题可能会更好,而不是仅仅引用关于多重组合根是如何邪恶的经文...

Is this going to register that module twice if my app registers the modules of lib2 and lib3?

使用 Autofac 4.8.1 测试,结果:

public class Lib1Module: Module
{
    protected override void Load(ContainerBuilder builder)
    {
        Console.WriteLine("Registering Lib1Module");

        // Register Types...

    }
}

public class Lib2Module: Module
{
    protected override void Load(ContainerBuilder builder)
    {
        Console.WriteLine("Registering Lib2Module");

        builder.RegisterModule<Lib1Module>();

        // Register Types...

    }
}

public class Lib3Module: Module
{
    protected override void Load(ContainerBuilder builder)
    {
        Console.WriteLine("Registering Lib3Module");

        builder.RegisterModule<Lib1Module>();

        // Register Types...

    }
}

public class Program
{
    public void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterModule<Lib2Module>();
        builder.RegisterModule<Lib3Module>();

        using(var container = builder.Build())
        {
            // Do Stuff
        }
    }
}

输出:

Registering Lib2Module
Registering Lib1Module
Registering Lib3Module
Registering Lib1Module

您可以在 IComponentRegistry/ContainerBuidler 上使用 Properties 字典(Module 基础 class 在内部创建它传递给 Load 的容器生成器使用 IComponentRegistry - source) 中的属性解决此问题并强制对模块进行单一注册,如果 Lib1ModuleLoad 方法更改为:

protected override void Load(ContainerBuilder builder)
{
    if (builder.Properties.ContainsKey(GetType().AssemblyQualifiedName))
    {
        return;
    }
    builder.Properties.Add(GetType().AssemblyQualifiedName, null);

    Console.WriteLine("Registering Lib1Module");

    // Register Types...

}

则输出变为:

Registering Lib2Module
Registering Lib1Module
Registering Lib3Module

显然,如果 Lib2Module/Lib3Module 可以成为其他模块的依赖项,则必须将类似的代码放入它们的 Load 方法中,类似地,如果任何模块使用 AttachToRegistrationSource and/or AttachToComponentRegistration 并希望确保他们 运行 只有在他们也需要检查时。或者(如果这是你需要做的很多事情,可能最好)你可以创建自己的 class 实现 IModule 并在 Configure.

内进行检查

我确实在生产代码中使用了这种模式来减少重复次数,因为我有多个入口点和它们自己的组合根(例如网络 api、网络应用程序和重复出现的控制台应用程序)尽管如此共享大量代码,但我可以忍受这让我成为 DI 纯粹主义者中不受欢迎的角色。