Silky微服務框架之服務引擎

構建服務引擎

註冊Silky微服務應用一節中,我們了解到在ConfigureServices階段,通過IServiceCollection的擴展方法AddSilkyServices<T>()除了註冊必要的服務之外,更主要的是構建了服務引擎(IEngine)。

下面,我們學習在IServiceCollection的擴展方法AddSilkyServices<T>()中完成了什麼樣的工作。如下所示的程式碼為在包 Silky.CoreServiceCollectionExtensions.cs中提供的擴展方法AddSilkyServices<T>()

public static IEngine AddSilkyServices<T>(this IServiceCollection services, IConfiguration configuration,
            IHostEnvironment hostEnvironment) where T : StartUpModule
{
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // 指定通訊管道的加密傳輸協議
    CommonSilkyHelpers.DefaultFileProvider = new SilkyFileProvider(hostEnvironment); // 構建文件服務提供者
    services.TryAddSingleton(CommonSilkyHelpers.DefaultFileProvider);  // 向services註冊單例的文件服務提供者
    var engine = EngineContext.Create(); // 創建單例的服務引擎
    services.AddOptions<AppSettingsOptions>()
        .Bind(configuration.GetSection(AppSettingsOptions.AppSettings)); // 新增AppSettingsOptions配置
    var moduleLoader = new ModuleLoader(); // 創建模組載入器
    engine.LoadModules<T>(services, moduleLoader); // 載入所有模組
    services.TryAddSingleton<IModuleLoader>(moduleLoader); // 註冊單例的模組載入器
    services.AddHostedService<InitSilkyHostedService>();  // 註冊 InitSilkyHostedService 後台任務服務,該服務用於初始化各個模組的任務或是在應用停止時釋放模組資源
    services.AddSingleton<ICancellationTokenProvider>(NullCancellationTokenProvider.Instance); //註冊默認的CancellationTokenProvider
    engine.ConfigureServices(services, configuration, hostEnvironment); // 通過服務引擎掃描所有IConfigureService介面的類,其實現類可以通過IServiceCollection對服務進行註冊;以及通過各個模組的ConfigureServices方法對服務進行註冊
    return engine; // 返回服務引擎對象
}

創建服務引擎的對象方法如下所示,我們可以看出,服務引擎在整個應用的生命周期是全局單例的。

internal static IEngine Create()
{
    return Singleton<IEngine>.Instance ?? (Singleton<IEngine>.Instance = new SilkyEngine()); // 服務引擎在應用的整個生命周期是單例的
}

通過我們對上述程式碼注釋可以看出,在AddSilkyServices<T>()方法中,在該方法中做了如下關鍵性的工作:

  1. 構建了一個關鍵性的對象 文件服務提供者(SilkyFileProvider) ,該對象主要用於掃描或是獲取指定的文件(例如應用程式集等)以及提供文件夾等幫助方法;

  2. 使用EngineContext創建了服務引擎對象SilkyEngine對象;

  3. 使用IServiceCollection註冊了必要的核心的對象,如:SilkyFileProviderModuleLoaderNullCancellationTokenProvider等;

  4. 創建模組載入器ModuleLoader對象,並通過服務引擎解析、載入silky模組,需要指出的是,在這裡我們需要指定啟動模組,系統會根據啟動模組指定的依賴關係進行排序;

  5. 註冊後台任務服務InitSilkyHostedService,該服務用於初始化各個模組的任務或是在應用停止時釋放模組資源;在各個模組的初始化工作中完成了很多核心的工作,例如:對應用服務以及服務條目的解析、服務元數據的註冊、服務實例的註冊與更新、Rpc消息監聽者的啟動等等;

  6. 在調用服務引擎的ConfigureServices()方法時,通過服務引擎掃描所有IConfigureService介面的類,通過反射創建實現類的對象,通過IServiceCollection對服務進行註冊;以及通過遍歷所有的Silky模組實例,通過模組的提供的ConfigureServices()的方法通過IServiceCollection對服務進行註冊。

提示

如果熟悉 nopCommerce 框架的小夥伴們應該注意到,SilkyEngine服務引擎的作用與構建與該框架的設計基本是一致的。

服務引擎的作用

服務引擎的SilkyEngine的作用主要由如下幾點:

  1. 通過模組載入器ModuleLoader解析和載入模組,關於模組如何解析和載入,請查看下一節模組內容;

  2. 實現服務的依賴注入,本質上來說要麼通過IServiceCollection服務實現服務註冊,要麼通過Autufac提供的ContainerBuilder實現服務註冊;

服務引擎實現服務的依賴注入主要由如下幾種方式實現:

2.1 通過掃描所有IConfigureService介面的類,並通過反射的方式構建實現類的對象,然後可以通過IServiceCollection對服務進行註冊;以及通過遍歷所有的Silky模組實例,通過模組的提供的ConfigureServices()的方法通過IServiceCollection對服務進行註冊。

如下程式碼為服務引擎提供的ConfigureServices()方法源碼:

// SilkyEngine 實現的ConfigureServices註冊服務的方法
public void ConfigureServices(IServiceCollection services, IConfiguration configuration,
            IHostEnvironment hostEnvironment)
{
    _typeFinder = new SilkyAppTypeFinder(); // 創建類型查找器
    ServiceProvider = services.BuildServiceProvider();
    Configuration = configuration;
    HostEnvironment = hostEnvironment;
    HostName = Assembly.GetEntryAssembly()?.GetName().Name;  // 解析應用服務主機名稱

    var configureServices = _typeFinder.FindClassesOfType<IConfigureService>(); // 通過查找器查找所有的`IConfigureService`實現類

    var instances = configureServices
        .Select(configureService => (IConfigureService)Activator.CreateInstance(configureService));  // 通過反射的方式創建`IConfigureService`實現類的實例

    foreach (var instance in instances) // 遍歷`IConfigureService`的實現類的實例,並通過其實例實現通過IServiceCollection對服務的註冊
        instance.ConfigureServices(services, configuration);
    // configure modules 
    foreach (var module in Modules) // 遍歷各個模組,通過各個模組提供`ConfigureServices`實現服務的註冊
        module.Instance.ConfigureServices(services, configuration);

    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

}

在上述程式碼中,我們可以看到在該方法體內主要完成如下工作:

A) 創建類型查找器、構建服務提供者以及為配置器、主機環境變數、主機名等賦值;

B) 使用類型查找器查找到所有IConfigureService實現類,並通過反射的方式創建其實例,遍歷其實例,其實例通過IServiceCollection實現對服務的註冊;

C) 遍歷所有的模組,通過模組的實例提供的ConfigureServices()方法,通過IServiceCollection實現對服務的註冊;

2.2 在上一章註冊silky微服務應用中有指出, 執行ContainerBuilder方法時,主要通過AutofacContainerBuilder實現服務的依賴註冊。

public static IHostBuilder RegisterSilkyServices<T>(this IHostBuilder builder)
  where T : StartUpModule
{   
    // 其他程式碼略...
    builder
      .UseServiceProviderFactory(new AutofacServiceProviderFactory()) // 替換服務提供者工作類
      .ConfigureContainer<ContainerBuilder>(builder => // 通過ContainerBuilder實現服務依賴註冊
        {
            engine.RegisterModules(services, builder);
            engine.RegisterDependencies(builder);
        })
}

我們看到,如何通過ContainerBuilder實現服務註冊,也是通過服務引擎巧妙的實現:一種方式是通過模組,另外一種方式是通過約定的依賴方式。

2.2.1 通過模組註冊服務

SilkyModule的定義中,我們看到模組的基類是Autofac.Module,我們在遍歷所有的模組實例的過程中,通過ContainerBuilder提供的RegisterModule()方法實現模組指定的服務的註冊。換句話說,就是在在執行RegisterModule()的方法過程中,Autofac會調用模組的提供的RegisterServices(ContainerBuilder builder)實現具體的服務註冊。

public void RegisterModules(IServiceCollection services, ContainerBuilder containerBuilder)
{
    containerBuilder.RegisterInstance(this).As<IModuleContainer>().SingleInstance();
    var assemblyNames = ((AppDomainTypeFinder)_typeFinder).AssemblyNames;
    foreach (var module in Modules)
    {
        if (!assemblyNames.Contains(module.Assembly.FullName))
        {
            ((AppDomainTypeFinder)_typeFinder).AssemblyNames.Add(module.Assembly.FullName);
        }

        containerBuilder.RegisterModule((SilkyModule)module.Instance);
    }
}

所以在Silky模組的定義SilkyModule中,提供了如下虛方法(RegisterServices),實際上是Autofac的基類Autofac.Module的一個基礎方法,在調用containerBuilder.RegisterModule((SilkyModule)module.Instance)時,底層會通過調用模組的Load()實現模組的具體服務的註冊。在Load()方法中,每個模組會調用RegisterServices(builder)實現通過ContainerBuilder對服務進行註冊。

protected override void Load([NotNull] ContainerBuilder builder)
{
    base.Load(builder);
    RegisterServices(builder);
}

所以,Silky具體的模組可以通過重寫RegisterServices([NotNull] ContainerBuilder builder)實現該模組使用ContainerBuilder實現服務的依賴註冊。

protected virtual void RegisterServices([NotNull] ContainerBuilder builder)
{
}

提示
使用ContainerBuilder實現服務的註冊和通過IServiceCollection實現服務的註冊的效果是一致的;使用ContainerBuilder實現服務的註冊的優勢在於支援命名服務的註冊。也就是在服務註冊的過程中,可以給服務起個名字,在服務解析的過程中,通過名稱去解析到指定名稱的介面的實現的對象。

2.2.2 通過約定註冊服務

服務引擎SilkyEngine通過調用RegisterDependencies()方法,使用ContainerBuilder實現對約定的規範的服務進行註冊。

 public void RegisterDependencies(ContainerBuilder containerBuilder)
{
    containerBuilder.RegisterInstance(this).As<IEngine>().SingleInstance();
    containerBuilder.RegisterInstance(_typeFinder).As<ITypeFinder>().SingleInstance();

    var dependencyRegistrars = _typeFinder.FindClassesOfType<IDependencyRegistrar>();
    var instances = dependencyRegistrars
        .Select(dependencyRegistrar => (IDependencyRegistrar)Activator.CreateInstance(dependencyRegistrar))
        .OrderBy(dependencyRegistrar => dependencyRegistrar.Order);
    foreach (var dependencyRegistrar in instances)
        dependencyRegistrar.Register(containerBuilder, _typeFinder);
}

在上面的程式碼中,我們看到通過構建約定註冊器(IDependencyRegistrar)的實例,通過約定註冊器實現指定服務的註冊。系統存在兩個默認的約定註冊器:

(1) DefaultDependencyRegistrar,該服務註冊器可以實現對標識介面的服務註冊;

A) 對繼承ISingletonDependency的類註冊為單例;
B) 對繼承ITransientDependency的類註冊為瞬態;
C) 對繼承IScopedDependency的類註冊為範圍;

(2) NamedServiceDependencyRegistrar 實現了對命名服務的註冊;在某個類繼承上述標識介面時,如果通過InjectNamedAttribute特性對服務進行命名,那麼該服務的將會被命名為該名稱的服務,在解析該服務的時候,可以通過名稱進行解析。
例如:

// 該服務將會被註冊為範圍的,並被命名為:DemoService,在服務解析過程中可以通過服務名 DemoService 解析到
[InjectNamed("DemoService")]
public class DemoService : IScopedDependency
{

}

  1. 服務引擎提供了多種判斷服務是否註冊以及服務解析方法;

  2. 服務引擎提供了獲取指定的配置項的方法;

  3. 可以通過服務引擎獲取類型查找器(TypeFinder)、服務配置器(Configuration)、主機環境變數提供者(IHostEnvironment)、以及主機名(HostName)等資訊。

獲取和使用服務引擎

在開發過程中,可以通過EngineContext.Current獲取服務引擎,並使用服務引擎提供的各個方法,例如:判斷服務是否註冊、解析服務、獲取配置類、獲取當前原因的主機名稱、或是使用類型查找器(TypeFinder)、服務配置器(Configuration)、主機環境變數提供者(IHostEnvironment)等。

提示
在開發過程中,使用服務引擎的大部分場景是,在不方便實現對某個服務進行構造注入的場景下,通過服務引擎實現對某個服務解析,從而得到該服務的實例。

開源地址

在線文檔