前言
Conference案例是使用ENode框架來開發的。之前我沒有介紹過ENode框架是如何啟動的,以及啟動時要注意的一些點,估計很多人對ENode框架的初始化這一塊感覺很復雜,一頭霧水。所以,本文想簡單介紹一下在做一個實際項目時,我們該如何初始化ENode。
使用ENode開發的項目的頂層宿主工程一般有兩類:1)前台Web項目,它的職責就是發送命令;2)后台ProcessorHost項目,負責處理命令或事件;
這兩類項目的初始化方式完全一樣,只是Web項目可能需要多初始化Controller的容器管理。下面我們看看使用ENode框架時的主要初始化邏輯:
private static void InitializeECommon() { _ecommonConfiguration = ECommonConfiguration .Create() .UseAutofac() .RegisterCommonComponents() .UseLog4Net() .UseJsonNet() .RegisterUnhandledExceptionHandler(); _logger = ObjectContainer.Resolve<ILoggerFactory>().Create(typeof(Bootstrap).FullName); _logger.Info("ECommon initialized."); } private static void InitializeENode() { ConfigSettings.Initialize(); var assemblies = new[] { Assembly.Load("Conference.Common"), Assembly.Load("Registration.Domain"), Assembly.Load("Registration.CommandHandlers"), Assembly.Load("Registration.ProcessManagers"), Assembly.Load("Registration.ReadModel"), Assembly.Load("Registration.ProcessorHost") }; var setting = new ConfigurationSetting { SqlServerDefaultConnectionString = ConfigSettings.ConferenceENodeConnectionString }; _enodeConfiguration = _ecommonConfiguration .CreateENode(setting) .RegisterENodeComponents() .RegisterBusinessComponents(assemblies) .RegisterAllTypeCodes() .UseSqlServerLockService() .UseSqlServerCommandStore() .UseSqlServerEventStore() .UseSqlServerSequenceMessagePublishedVersionStore() .UseSqlServerMessageHandleRecordStore() .UseEQueue() .InitializeBusinessAssemblies(assemblies); _logger.Info("ENode initialized."); }
從上面的代碼可以看到,ENode框架的初始化是采用FluentAPI的方式,通過使用富有語義的方法來表達當前在做什么配置。
ECommon初始化
配置ENode一般是分為兩個階段,先配置ECommon。ECommon的初始化邏輯比較簡單,Create方法創建一個ECommonConfiguration類的實例,然后通過調用UseAutofac方法,告訴框架當前使用的是Autofac容器。 然后RegisterCommonComponents就是把一些默認的組件注入到容器中;然后UseLog4Net和UseJsonNet這兩個方法就是告訴框架當前使用的日志組件是Log4Net,JSON序列化組件是JSON.NET。最后,RegisterUnhandledExceptionHandler方法就是告訴框架要捕獲未處理的異常,這樣框架在發現有未處理的異常時,會嘗試記錄錯誤日志。上面的一個配置的兩點是,ENode並沒有和特定的容器綁定,目前僅實現了Autofac,大家可以根據自己的喜好使用其他的IoC容器,比如Untity, Castle, StructureMap等。
ENode初始化
ECommon初始化完成后,開始初始化ENode框架了。ENode的初始化稍微復雜一點,但因為也是通過富有語義的FluentAPI來實現初始化,所以看起來也比較容易理解。
基本配置
我們先創建ENodeConfiguration的全局實例,然后同樣注冊ENode的所有默認實現組件以及給定程序集中的所有標記了Component特性的組件到容器。
類型和Code的映射配置
然后接下來就是通過RegisterAllTypeCodes方法告訴框架所有可能涉及到序列化的類的Code。為什么要做這個配置?因為假如一個Command要發送到消息隊列,在Command發送時,會用JSON序列化;在Command消費者消費時會用JSON反序列化。如果我們不設計這種Code機制,那序列化的JSON字符串里需要包含類型的名稱。而我們的類名可能會調整的。所以為了更好的靈活性,我為ENode框架所有可能需要序列化的地方,都通過這種Code的思想,把類型轉換為Code;反序列化時,根據Code找到對應的類型,然后進行反序列化;該方法的內部實現大概如下面這樣:
public static ENodeConfiguration RegisterAllTypeCodes(this ENodeConfiguration enodeConfiguration) { var provider = ObjectContainer.Resolve<ITypeCodeProvider>() as DefaultTypeCodeProvider; //aggregates provider.RegisterType<Order>(120); provider.RegisterType<OrderSeatAssignments>(121); //commands provider.RegisterType<MakeSeatReservation>(207); provider.RegisterType<CommitSeatReservation>(208); provider.RegisterType<CancelSeatReservation>(209);
持久化相關實現類的配置
由於真實的項目,我們肯定需要做各種持久化,比如事件的持久化,事件處理記錄的持久化等。所以通過上面代碼中的各種UseSql打頭的方法,就可以配置各種場景的持久化實現。然后這些Sql的實現類所使用的數據庫連接,上面的例子都是使用默認的數據庫連接,見上面SqlServerDefaultConnectionString屬性的賦值。如果你只是想運行一下基於內存的模型,那這些UseSql的方法是不需要調用的。比如像ENode框架里自帶的一些例子的配置都很簡單,以NoteSample為例:
static void InitializeENodeFramework() { var assemblies = new[] { Assembly.Load("NoteSample.Domain"), Assembly.Load("NoteSample.Commands"), Assembly.Load("NoteSample.CommandHandlers"), Assembly.GetExecutingAssembly() }; _configuration = Configuration .Create() .UseAutofac() .RegisterCommonComponents() .UseLog4Net() .UseJsonNet() .RegisterUnhandledExceptionHandler() .CreateENode() .RegisterENodeComponents() .RegisterBusinessComponents(assemblies) .RegisterAllTypeCodes() .UseEQueue() .InitializeBusinessAssemblies(assemblies) .StartEQueue(); Console.WriteLine(string.Empty); _logger = ObjectContainer.Resolve<ILoggerFactory>().Create(typeof(Program).Name); _logger.Info("ENode started..."); }
EQueue相關配置
因為ENode需要使用EQueue這個分布式消息隊列來實現命令或事件的發布和訂閱。所以,我們也需要配置EQueue。通過調用UseEQueue實現,該方法的內部實現需要根據當前服務器所需要使用的功能而定。
類似於像下面這樣:
public static ENodeConfiguration UseEQueue(this ENodeConfiguration enodeConfiguration) { var configuration = enodeConfiguration.GetCommonConfiguration(); configuration.RegisterEQueueComponents(); var producerEndpoint = new IPEndPoint(SocketUtils.GetLocalIPV4(), ConfigSettings.BrokerProducerPort); var consumerEndpoint = new IPEndPoint(SocketUtils.GetLocalIPV4(), ConfigSettings.BrokerConsumerPort); var producerSetting = new ProducerSetting { BrokerProducerIPEndPoint = producerEndpoint }; var consumerSetting = new ConsumerSetting { BrokerConsumerIPEndPoint = consumerEndpoint }; _applicationMessagePublisher = new ApplicationMessagePublisher("PaymentsApplicationMessagePublisher", producerSetting); _domainEventPublisher = new DomainEventPublisher("PaymentsDomainEventPublisher", producerSetting); configuration.SetDefault<IMessagePublisher<IApplicationMessage>, ApplicationMessagePublisher>(_applicationMessagePublisher); configuration.SetDefault<IMessagePublisher<DomainEventStreamMessage>, DomainEventPublisher>(_domainEventPublisher); _commandConsumer = new CommandConsumer( "PaymentCommandConsumer", "PaymentCommandConsumerGroup", consumerSetting) .Subscribe(Topics.PaymentCommandTopic); _eventConsumer = new DomainEventConsumer( "PaymentEventConsumer", "PaymentEventConsumerGroup", consumerSetting) .Subscribe(Topics.PaymentDomainEventTopic); return enodeConfiguration; }
使用ENode框架開發的應用,可能會用到如下的角色:
- 發送命令,使用CommandService;
- 發送領域事件,使用DomainEventPublisher;
- 發送應用層消息,使用ApplicationMessagePublisher;
- 發送異常,使用PublishableExceptionPublisher;
- 消費命令,使用CommandConsumer;
- 消費領域事件,DomainEventConsumer;
- 消費應用層消息,使用ApplicationMessageConsumer;
- 消費異常,使用PublishableExceptionConsumer;
從上面的8個角色中,我們可以知道,主要分為兩類:1)消息生產者;2)消息消費者;前面4個是消息生產者;后面4個屬於消息消費者;然后生產者內部就是封裝了EQueue的Producer;消費者內部就是封裝了EQueue的Consumer;是不是很對稱,呵呵。
然后一般一個Web工程,它只需要發送命令即可,所以一般只需要初始化CommandService即可;而一般一個ProcessorHost工程,因為可能要處理命令、事件、應用層消息,以及異常消息。且因為可能有Saga流程的存在,所以它們可能需要配置所有上面8個角色。具體需要配置哪些角色,還是要看具體的應用而定。
當我們實例化好了需要的角色后,我們就可以啟動或者關閉它們了。代碼類似如下:
public static ENodeConfiguration StartEQueue(this ENodeConfiguration enodeConfiguration) { _applicationMessageConsumer.Start(); _eventConsumer.Start(); _commandConsumer.Start(); _domainEventPublisher.Start(); _commandService.Start(); return enodeConfiguration; } public static ENodeConfiguration ShutdownEQueue(this ENodeConfiguration enodeConfiguration) { _commandService.Shutdown(); _domainEventPublisher.Shutdown(); _commandConsumer.Shutdown(); _eventConsumer.Shutdown(); _applicationMessageConsumer.Shutdown(); return enodeConfiguration; }
關於Controller所依賴的服務如何注入
上面介紹了ENode框架的主要配置如何使用,但還有一種場景沒有介紹。就是ASP.NET MVC Web項目,Controller往往也是需要讓容器來管理實例的。那這個如何配置呢?其實也很簡單。我們只需要在ENode框架配置完成后,在通過如下的代碼就可以實現Controller的生命周期的管理了。
private void RegisterControllers() { var webAssembly = Assembly.GetExecutingAssembly(); var container = (ObjectContainer.Current as AutofacObjectContainer).Container; var builder = new ContainerBuilder(); builder.RegisterControllers(webAssembly); builder.Update(container); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); }
上面的代碼中,關注點是:AutofacDependencyResolver這個類,這個類是Autofac.Integration.Mvc這個組件提供的。這個組件是Autofac和MVC集成的一個組件。通過它,我們可以方便的實現對Controller的依賴注入。
結束語
好了,基本介紹了一下ENode框架的配置部分如何使用。是不是很復雜?呵呵,其實只要我寫的這些例子你看多了,也是很簡單的,大家做項目時參考我的案例中的寫法即可。