Silky微服務框架之模組

模組的定義

Silky是一個包括多個nuget包構成的模組化的框架,每個模組將程式劃分為一個個小的結構,在這個結構中有著自己的邏輯程式碼和自己的作用域,不會影響到其他的結構。

模組類

一般地,一個模組的定義是通過在該程式集內創建一個派生自 SilkyModule的類,如下所示:


public class RpcModule : SilkyModule
{

}

SilkyModule是一個抽象的類,它定義了模組的基礎方法,體現了模組在框架中的作用;

SilkyModule模組定義的核心程式碼如下所示:

public abstract class SilkyModule : Autofac.Module, ISilkyModule, IDisposable
{

   protected SilkyModule()
   {
       Name = GetType().Name.RemovePostFix(StringComparison.OrdinalIgnoreCase, "Module");
   }

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

    public virtual void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
    }
 
    protected virtual void RegisterServices([NotNull] ContainerBuilder builder)
    {
    }

    public virtual Task Initialize([NotNull] ApplicationContext applicationContext)
    {
       return Task.CompletedTask;
    }

    public virtual Task Shutdown([NotNull] ApplicationContext applicationContext)
    {
        return Task.CompletedTask;
    }

    public virtual string Name { get; }

   // 其他程式碼略...
}

通過對SilkyModule模組程式碼定義的分析我們可以得知,一個Silky模組有如下幾個作用:

  1. ConfigureServices()方法中,通過IServiceCollection實現服務註冊;

  2. RegisterServices()方法中,通過ContainerBuilder實現服務註冊;

  3. 在應用程式啟動時,通過Initialize()方法實現模組的初始化方法;

  4. 在應用程式停止時,執行Shutdown()方法,可以實現模組資源的釋放;

關於上述第1、2 點的作用, 我們已經在服務引擎一章中做了詳細的解析;關於第3、4點的作用,應用程式是如何在啟動時調用Initialize()方法或是在停止時執行Shutdown()方法呢?

構建服務引擎一章中,我們提到,在構建服務引擎時,我們有一項很重要的工作就是註冊了InitSilkyHostedService後台任務

後台任務InitSilkyHostedService的源碼如下所示:

    public class InitSilkyHostedService : IHostedService
    {
        private readonly IModuleManager _moduleManager;
        private readonly IHostApplicationLifetime _hostApplicationLifetime;

        public InitSilkyHostedService(IServiceProvider serviceProvider,
            IModuleManager moduleManager,
            IHostApplicationLifetime hostApplicationLifetime)
        {
            if (EngineContext.Current is SilkyEngine)
            {
                EngineContext.Current.ServiceProvider = serviceProvider;
            }

            _moduleManager = moduleManager;
            _hostApplicationLifetime = hostApplicationLifetime;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine(@"                                              
   _____  _  _  _           
  / ____|(_)| || |          
 | (___   _ | || | __ _   _ 
  \___ \ | || || |/ /| | | |
  ____) || || ||   < | |_| |
 |_____/ |_||_||_|\_\ \__, |
                       __/ |
                      |___/
            ");
            var version = Assembly.GetExecutingAssembly().GetName().Version;
            var ver = $"{version.Major}.{version.Minor}.{version.Build}";
            Console.WriteLine($" :: Silky ::        {ver}");
            _hostApplicationLifetime.ApplicationStarted.Register(async () =>
            {
                await _moduleManager.InitializeModules();
            });
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            _hostApplicationLifetime.ApplicationStopped.Register(async () =>
            {
                await _moduleManager.ShutdownModules();
            });
        }
    }
  1. 在後台任務StartAsync(),在列印Silky的banner後,在應用啟動時註冊一個回調方法,通過模組管理器IModuleManager執行初始化模組方法;

  2. 在後台任務StopAsync(),在應用停止後註冊一個回調方法,通過模組管理器IModuleManager執行關閉模組方法,一般用於各個模組的資源釋放;

下面,我們查看模組管理器ModuleManager是如何初始化模組的:

        public async Task InitializeModules()
        {
            foreach (var module in _moduleContainer.Modules)
            {
                try
                {
                    Logger.LogInformation("Initialize the module {0}", module.Name);
                    await module.Instance.Initialize(new ApplicationContext(_serviceProvider, _moduleContainer));
                }
                catch (Exception e)
                {
                    Logger.LogError($"Initializing the {module.Name} module is an error, reason: {e.Message}");
                    throw;
                }
            }
        }

模組容器_moduleContainer的屬性_moduleContainer.Modules是通過模組載入器ModuleLoader載入並通過依賴關係進行排序得到的所有模組的實例,我們看到通過foreach對所有的模組實例進行遍歷,並依次執行各個模組的Initialize()方法。

同樣的,在應用程式停止時,會調用InitSilkyHostedService任務的StopAsync(),該方法通過調用模組管理器的ShutdownModules()方法,實現對各個模組資源的釋放;

    public async Task ShutdownModules()
    {
        foreach (var module in _moduleContainer.Modules)
        {
            await module.Instance.Shutdown(new ApplicationContext(_serviceProvider, _moduleContainer));
        }
    }

模組的類型

在Silky框架中,我將模組的類型劃分為如下幾種類型:

  1. 模組的定義SilkyModule: SilkyModule是一個抽象的模組,用於定義模組的概念;其他業務模組必須要派生自該類;

  2. 業務模組: 直接派生自SilkyModule類的非抽象類,Silky框架中,幾乎所有的包在通過定義業務模組後從而實現模組化編程的,很多核心的包都是業務模組,如:SilkyModuleConsulModuleDotNettyModule等等模組都屬於業務模組;

  3. Http類型的業務模組:該類型的業務模組派生自HttpSilkyModule,相比一般的業務模組,該類型的模組增加了Configure(IApplicationBuilder application)方法,該類型的模組一般用於通過web主機構建的微服務應用或是網關中,可以在Configure()方法中通過IApplicationBuilder引用http中間件,在silky框架中,諸如: CorsModuleIdentityModuleMiniProfilerModule等均是該類型的模組; 需要特別注意的是,需要http業務模組配置的中間件起效果的話,不要忘記需要在Startup類中的Configure進行如下配置:


public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 
{
   app.ConfigureSilkyRequestPipeline();
}
  1. 啟動模組:該類型的模組派生自StartUpModule的非抽象類;在模組載入過程中,通過指定啟動模組,從而得知模組的依賴關係,模組載入器會通過模組的依賴對模組進行排序,從而影響應用在啟動時各個模組的執行的先後順序;Silky模組預定義了多個啟動模組,可以用於不同silky主機的構成:
    A) DefaultGeneralHostModule 用於構建普通的業務主機,一般用於託管只提供RPC服務的微服務應用;
    B) WebSocketHostModule 用於構建提供WebSocket服務能力的業務主機;
    C) DefaultWebHostModule 用於構建能夠提供Http服務的業務主機,對外可以提供http服務,也可以用於內部rpc通訊;
    D) DefaultGatewayHostModule 用於構建網關微服務,一般為微服務集群暴露對外部的http訪問埠,通過路由機制,將http請求轉發到具體某個服務條目,對內通過RPC進行通訊;

除此之外,開發者也可以自己的需求,為自己定義需要的啟動模組,在構建微服務主機時,指定相應的啟動模組。

模組的載入

Silky所有的模組是在什麼時候以及如何進行載入和排序的呢?

在之前的構建服務引擎的一章中,我們知道在AddSilkyServices<T>()方法中,我們通過泛型T來指定應用程式的啟用模組StartUpModule類型。並構建了模組載入器對象ModuleLoader,並且將模組載入器對象作為服務引擎的LoadModules()方法參數:

public static IEngine AddSilkyServices<T>(this IServiceCollection services, IConfiguration configuration,
            IHostEnvironment hostEnvironment) where T : StartUpModule
{
    var moduleLoader = new ModuleLoader();
    engine.LoadModules<T>(services, moduleLoader);
}

在服務引擎SilkyEngine實現類中,除了實現IEngine介面之外,還需要實現了IModuleContainer介面,IModuleContainer只定義了一個只讀屬性Modules,要求通過該屬性獲取所有的模組;在服務引擎中,我們通過模組載入器對象moduleLoader.LoadModules()方法實現對模組的載入與解析,並對屬性Modules進行賦值;

internal sealed class SilkyEngine : IEngine, IModuleContainer
{
  // 其他程式碼略...

  
   public void LoadModules<T>(IServiceCollection services, IModuleLoader moduleLoader)
   where T : StartUpModule
   {
      Modules = moduleLoader.LoadModules(services, typeof(T));
   }
  
   // 實現IModuleContainer定義的屬性
   public IReadOnlyList<ISilkyModuleDescriptor> Modules { get; private set; }
}

模組載入器ModuleLoader要求傳遞兩個參數,一個是IServiceCollection的對象services,一個是啟動模組StartupModule的的類型typeof(T);下面我們來描述模組載入的過程:

  1. 通過SilkyModuleHelper.FindAllModuleTypes(startupModuleType) 查找到啟動模組StartupModule類型依賴的所有模組類型;

  2. 通過反射創建模組的實例,並通過IServiceCollection註冊單例的模組實例,並創建模組描述符SilkyModuleDescriptor;

  3. 根據模組的依賴關係對模組進行排序;

模組的依賴關係是通過特性DependsOnAttribute指定的,通過DependsOnAttribute在對模組的類進行標註,就可以解析到各個模組的依賴關係,從而實現通過模組的依賴關係進行排序;

提示
熟悉APB框架的小夥伴應該可以看出來,Silky模組的設計主要是借鑒了APB框架的模組設計,在一些細節方面做了調整。

Silky的核心模組

通過上面的介紹, 我們知道一個模組類的最重要的工作主要由兩點: 1. 實現服務的註冊; 2. 在應用啟動時或是停止時執行指定的方法完成初始化任務或是釋放資源的任務;

如何判斷是否是silky的核心模組呢? 核心模組最重要的一個作用就是在應用啟動時,通過Initialize()方法執行該模組的初始化資源的任務;

通過查看源碼,我們發現大部分silky模組在應用啟動時並沒有重寫Initialize()方法,也就是說,大部分silky模組在應用啟動過程時主要是完成各個模組的服務類的註冊並不需要做什麼工作。

img

如上圖所示,我們看到silky框架定義的模組,由如上幾個模組是在應用啟動是完成了主機啟動時的關鍵性作業;

我們再根據模組的依賴關係,可以看到主機在應用啟動時,通過模組初始化任務的一個執行順序如下所示:

RpcModule --> DotNettyTcpModule | TransactionModule | WebSocketModule | [RpcMonitorModule] 

--> GeneralHostModule(啟動模組[StartUpModule])[DefaultGeneralHostModule|WebSocketHostModule|DefaultWebSocketHostModule] 

通過上述的依賴關係,我們可以知道:

  1. Rpc模組在應用啟動時是最早被執行;

  2. 然後依次執行: DotNettyTcpModule | TransactionModule | WebSocketModule | [RpcMonitorModule] 等模組;

  3. 最後執行應用啟動模組指定的初始化方法;

在上述的過程中,Silky主機在啟動時需要完成如下的工作:

  1. 實現Rpc消息監聽的訂閱;

  2. 解析應用服務與服務條目;

  3. 啟動Rpc消息監聽;

  4. 解析服務主機和註冊該主機實例對應的端點;

  5. 向服務註冊中心更新或是獲取服務元數據(應用服務描述資訊和服務條目描述資訊);

  6. 向服務註冊中心註冊該服務當前實例的端點以及從服務註冊中心獲取該服務對應的所有實例;

  7. 通過心跳的方式從服務註冊中心獲取最新的服務元數據以及服務實例資訊;

在下面的篇章中,我們將著重介紹上述的過程是如何實現的。