• ENode框架Conference案例分析系列之


    前言

    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();
    
    </span><span style="color: #0000ff;">var</span> assemblies = <span style="color: #0000ff;">new</span><span style="color: #000000;">[]
    {
        Assembly.Load(</span><span style="color: #800000;">"</span><span style="color: #800000;">Conference.Common</span><span style="color: #800000;">"</span><span style="color: #000000;">),
        Assembly.Load(</span><span style="color: #800000;">"</span><span style="color: #800000;">Registration.Domain</span><span style="color: #800000;">"</span><span style="color: #000000;">),
        Assembly.Load(</span><span style="color: #800000;">"</span><span style="color: #800000;">Registration.CommandHandlers</span><span style="color: #800000;">"</span><span style="color: #000000;">),
        Assembly.Load(</span><span style="color: #800000;">"</span><span style="color: #800000;">Registration.ProcessManagers</span><span style="color: #800000;">"</span><span style="color: #000000;">),
        Assembly.Load(</span><span style="color: #800000;">"</span><span style="color: #800000;">Registration.ReadModel</span><span style="color: #800000;">"</span><span style="color: #000000;">),
        Assembly.Load(</span><span style="color: #800000;">"</span><span style="color: #800000;">Registration.ProcessorHost</span><span style="color: #800000;">"</span><span style="color: #000000;">)
    };
    </span><span style="color: #0000ff;">var</span> setting = <span style="color: #0000ff;">new</span><span style="color: #000000;"> ConfigurationSetting
    {
        SqlServerDefaultConnectionString </span>=<span style="color: #000000;"> ConfigSettings.ConferenceENodeConnectionString
    };
    
    _enodeConfiguration </span>=<span style="color: #000000;"> _ecommonConfiguration
        .CreateENode(setting)
        .RegisterENodeComponents()
        .RegisterBusinessComponents(assemblies)
        .RegisterAllTypeCodes()
        .UseSqlServerLockService()
        .UseSqlServerCommandStore()
        .UseSqlServerEventStore()
        .UseSqlServerSequenceMessagePublishedVersionStore()
        .UseSqlServerMessageHandleRecordStore()
        .UseEQueue()
        .InitializeBusinessAssemblies(assemblies);
    _logger.Info(</span><span style="color: #800000;">"</span><span style="color: #800000;">ENode initialized.</span><span style="color: #800000;">"</span><span style="color: #000000;">);
    

    }

    复制代码

    从上面的代码可以看到,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;
    
    </span><span style="color: #008000;">//</span><span style="color: #008000;">aggregates</span>
    provider.RegisterType&lt;Order&gt;(<span style="color: #800080;">120</span><span style="color: #000000;">);
    provider.RegisterType</span>&lt;OrderSeatAssignments&gt;(<span style="color: #800080;">121</span><span style="color: #000000;">);
    
    </span><span style="color: #008000;">//</span><span style="color: #008000;">commands</span>
    provider.RegisterType&lt;MakeSeatReservation&gt;(<span style="color: #800080;">207</span><span style="color: #000000;">);
    provider.RegisterType</span>&lt;CommitSeatReservation&gt;(<span style="color: #800080;">208</span><span style="color: #000000;">);
    provider.RegisterType</span>&lt;CancelSeatReservation&gt;(<span style="color: #800080;">209</span>);</pre>
    
    复制代码

    持久化相关实现类的配置

    由于真实的项目,我们肯定需要做各种持久化,比如事件的持久化,事件处理记录的持久化等。所以通过上面代码中的各种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(</span><span style="color: #0000ff;">string</span><span style="color: #000000;">.Empty);
    
    _logger </span>= ObjectContainer.Resolve&lt;ILoggerFactory&gt;().Create(<span style="color: #0000ff;">typeof</span><span style="color: #000000;">(Program).Name);
    _logger.Info(</span><span style="color: #800000;">"</span><span style="color: #800000;">ENode started...</span><span style="color: #800000;">"</span><span style="color: #000000;">);
    

    }

    复制代码

    EQueue相关配置

    因为ENode需要使用EQueue这个分布式消息队列来实现命令或事件的发布和订阅。所以,我们也需要配置EQueue。通过调用UseEQueue实现,该方法的内部实现需要根据当前服务器所需要使用的功能而定。

    类似于像下面这样:

    复制代码
    public static ENodeConfiguration UseEQueue(this ENodeConfiguration enodeConfiguration)
    {
        var configuration = enodeConfiguration.GetCommonConfiguration();
    
    configuration.RegisterEQueueComponents();
    
    </span><span style="color: #0000ff;">var</span> producerEndpoint = <span style="color: #0000ff;">new</span><span style="color: #000000;"> IPEndPoint(SocketUtils.GetLocalIPV4(), ConfigSettings.BrokerProducerPort);
    </span><span style="color: #0000ff;">var</span> consumerEndpoint = <span style="color: #0000ff;">new</span><span style="color: #000000;"> IPEndPoint(SocketUtils.GetLocalIPV4(), ConfigSettings.BrokerConsumerPort);
    </span><span style="color: #0000ff;">var</span> producerSetting = <span style="color: #0000ff;">new</span> ProducerSetting { BrokerProducerIPEndPoint =<span style="color: #000000;"> producerEndpoint };
    </span><span style="color: #0000ff;">var</span> consumerSetting = <span style="color: #0000ff;">new</span> ConsumerSetting { BrokerConsumerIPEndPoint =<span style="color: #000000;"> consumerEndpoint };
    
    _applicationMessagePublisher </span>= <span style="color: #0000ff;">new</span> ApplicationMessagePublisher(<span style="color: #800000;">"</span><span style="color: #800000;">PaymentsApplicationMessagePublisher</span><span style="color: #800000;">"</span><span style="color: #000000;">, producerSetting);
    _domainEventPublisher </span>= <span style="color: #0000ff;">new</span> DomainEventPublisher(<span style="color: #800000;">"</span><span style="color: #800000;">PaymentsDomainEventPublisher</span><span style="color: #800000;">"</span><span style="color: #000000;">, producerSetting);
    
    configuration.SetDefault</span>&lt;IMessagePublisher&lt;IApplicationMessage&gt;, ApplicationMessagePublisher&gt;<span style="color: #000000;">(_applicationMessagePublisher);
    configuration.SetDefault</span>&lt;IMessagePublisher&lt;DomainEventStreamMessage&gt;, DomainEventPublisher&gt;<span style="color: #000000;">(_domainEventPublisher);
    
    _commandConsumer </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> CommandConsumer(
        </span><span style="color: #800000;">"</span><span style="color: #800000;">PaymentCommandConsumer</span><span style="color: #800000;">"</span><span style="color: #000000;">,
        </span><span style="color: #800000;">"</span><span style="color: #800000;">PaymentCommandConsumerGroup</span><span style="color: #800000;">"</span><span style="color: #000000;">,
        consumerSetting)
    .Subscribe(Topics.PaymentCommandTopic);
    
    _eventConsumer </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> DomainEventConsumer(
        </span><span style="color: #800000;">"</span><span style="color: #800000;">PaymentEventConsumer</span><span style="color: #800000;">"</span><span style="color: #000000;">,
        </span><span style="color: #800000;">"</span><span style="color: #800000;">PaymentEventConsumerGroup</span><span style="color: #800000;">"</span><span style="color: #000000;">,
        consumerSetting)
    .Subscribe(Topics.PaymentDomainEventTopic);
    
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> enodeConfiguration;
    

    }

    复制代码

    使用ENode框架开发的应用,可能会用到如下的角色:

    1. 发送命令,使用CommandService;
    2. 发送领域事件,使用DomainEventPublisher;
    3. 发送应用层消息,使用ApplicationMessagePublisher;
    4. 发送异常,使用PublishableExceptionPublisher;
    5. 消费命令,使用CommandConsumer;
    6. 消费领域事件,DomainEventConsumer;
    7. 消费应用层消息,使用ApplicationMessageConsumer;
    8. 消费异常,使用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();
    
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> 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框架的配置部分如何使用。是不是很复杂?呵呵,其实只要我写的这些例子你看多了,也是很简单的,大家做项目时参考我的案例中的写法即可。

  • 相关阅读:
    深入了解Go Playground
    计算机程序设计艺术学习笔记1
    Docker 和一个正常的虚拟机有何区别?
    现代计算机架构常见时延(摘自计算机系统结构--量化研究方法)
    内核开发时应该注意的点
    gem5线程相关的类—SimpleThread类,ThreadState类(src/cpu/thread_state.*)
    GEM5中模拟的系统调用(部分没实现)
    字典树(trie)
    UML类图几种关系的总结
    C,C++宏中#与##的讲解
  • 原文地址:https://www.cnblogs.com/jobs-lgy/p/6357698.html
Copyright © 2020-2023  润新知