Implementation of event-driven architecture under ASP. (iii): RABBITMQ-based event bus

Source: Internet
Author: User
Tags new set rabbitmq

In the previous section, we discussed the object life cycle of the event handler, and before entering a new discussion, let's summarize what we've achieved. The following class diagram describes the components that we have implemented and the relationships between them, and it seems that the system has become more and more complex.

The green part is the newly implemented part of the above, including a simple event Store, an interface for the execution context of an events handler, and an implementation context based on the ASP. NET core Dependency Injection framework. Next, we plan to phase out Passthrougheventbus and then implement a new set of event buses based on RABBITMQ.

Reconstruction of Event Bus

According to the previous conclusion, the execution of the event bus relies on the event handler execution context, which is the reference to the Ieventhandlerexecutioncontext in the class diagram above Passthrougheventbus. More specifically, it is necessary to register the event handler in Ieventhandlerexecutioncontext when the event bus subscribes to some type of event. There is a similar design requirement when implementing RABBITMQ, that Rabbitmqeventbus also relies on ieventhandlerexecutioncontext interfaces to ensure the validity of the event processor life cycle.

To do this, we create a new base class: Baseeventbus, and extract this part of the public code, you need to pay attention to the following points:

    1. By passing the Baseeventbus constructor to the Ieventhandlerexecutioncontext instance, the implementation of all subclasses is qualified, and the Ieventhandlerexecutioncontext instance must be passed in the constructor. This is advantageous for the design of the framework: when implementing the new event bus, the user of the framework can know the relationship between the event bus and the Ieventhandlerexecutioncontext without looking at the API documentation, which is consistent with the solid principle of open/closed Principle
    2. The implementation of Baseeventbus should be placed in the Edasample.common assembly, or rather, it should be placed under the EdaSample.Common.Events namespace because it is a component of the framework level and does not depend on the components of any infrastructure tier

The code for Baseeventbus is as follows:

Public abstract class baseeventbus:ieventbus{    protected readonly Ieventhandlerexecutioncontext Eventhandlerexecutioncontext;    Protected Baseeventbus (Ieventhandlerexecutioncontext eventhandlerexecutioncontext)    {        This.eventhandlerexecutioncontext = Eventhandlerexecutioncontext;    }    Public abstract Task publishasync<tevent> (tevent @event, CancellationToken cancellationtoken = default) where tevent:ievent;    public abstract void Subscribe<tevent, teventhandler> ()        where Tevent:ievent        where Teventhandler: ieventhandler<tevent>;//Disposable Interface Implementation code omitted}

In the above code, the Publishasync and subscribe methods are abstract methods so that subclasses are implemented according to different needs.

The next step is to adjust the Passthrougheventbus to inherit from the Baseeventbus:

public sealed class passthrougheventbus:baseeventbus{private readonly eventqueue eventqueue = new EventQueue ();    Private ReadOnly ILogger logger;        Public Passthrougheventbus (Ieventhandlerexecutioncontext context, ilogger<passthrougheventbus> logger)        : Base (context) {This.logger = logger; Logger. Loginformation ($ "Passthrougheventbus constructor call completed.) Hash Code:{this.        GetHashCode ()}. ");    eventqueue.eventpushed + = eventqueue_eventpushed; } private Async void Eventqueue_eventpushed (object sender, Eventprocessedeventargs e) = await This.eventhand    Lerexecutioncontext.handleeventasync (e.event);        public override Task publishasync<tevent> (tevent @event, CancellationToken cancellationtoken = default) {    Return Task.Factory.StartNew (() = Eventqueue.push (@event)); } public override void Subscribe<tevent, teventhandler> () {if (!this.eventhandlerexecutioncontext.hand Lerregistered<tevent, TeventhaNdler> ()) {this.eventhandlerexecutioncontext.registerhandler<tevent, teventhandler> (); }}//disposable interface implementation code omitted}

The code is simple, and not much to explain, then we start to implement Rabbitmqeventbus.

The realization of Rabbitmqeventbus

You first need to create a new. NET Standard 2.0 project, which can be referenced by the. NET Framework 4.6.1 or. NET Core 2.0 applications by using the. NET Standard 2.0 project template. The purpose of creating a new class library project is because the implementation of Rabbitmqeventbus relies on an external reference to the RABBITMQ C # Development Library. Therefore, in order to ensure the purity and stability of the core of the framework, we need to implement Rabbitmqeventbus in the new Class Library project.

Note: For the introduction of RABBITMQ and its C # library, this article is no longer involved, there are a lot of information and documents on the Web, blog Park has many friends in this area have the use of experience sharing, RABBITMQ official documents are also written in very detailed, of course, English version, if English is better words, It is recommended to refer to the official documentation.

Here is the Edasample case, the implementation of Rabbitmqeventbus, we first read the code, and then do some analysis of this part of the code.

public class rabbitmqeventbus:baseeventbus{private readonly iconnectionfactory connectionfactory;    Private readonly iconnection connection;    Private ReadOnly IModel channel;    Private readonly string Exchangename;    Private readonly string ExchangeType;    Private readonly string queuename;    Private readonly bool Autoack;    Private ReadOnly ILogger logger;    private bool disposed; Public Rabbitmqeventbus (Iconnectionfactory connectionfactory, ilogger<rabbitmqeventbus> logger, IEvent Handlerexecutioncontext context, String exchangename, String exchangetype = Exchangetype.fanout, stri ng queuename = NULL, bool Autoack = false): Base (context) {this.connectionfactory = Connectionfa        Ctory;        This.logger = logger;        This.connection = This.connectionFactory.CreateConnection ();        This.channel = This.connection.CreateModel ();        This.exchangetype = ExchangeType; This.exchangename = ExchanGename;        This.autoack = Autoack;        This.channel.ExchangeDeclare (This.exchangename, This.exchangetype); This.queuename = this.        Initializeeventconsumer (QueueName); Logger. Loginformation ($ "Rabbitmqeventbus constructor call completed.) Hash Code:{this.    GetHashCode ()}. "); public override Task publishasync<tevent> (tevent @event, CancellationToken cancellationtoken = Default ( CancellationToken) {var json = Jsonconvert.serializeobject (@event, new Jsonserializersettings {Typenamehandli        ng = Typenamehandling.all});        var eventbody = Encoding.UTF8.GetBytes (JSON); Channel. Basicpublish (This.exchangename, @event. GetType ().        FullName, NULL, eventbody);    return task.completedtask; } public override void Subscribe<tevent, teventhandler> () {if (!this.eventhandlerexecutioncontext.hand Lerregistered<tevent, teventhandler> ()) {This.eventhandlerexecutioncontext.registerhandler<tev EnT, Teventhandler> (); This.channel.QueueBind (This.queuename, This.exchangename, typeof (Tevent).        FullName);            }} protected override void Dispose (bool disposing) {if (!disposed) {if (disposing)                {This.channel.Dispose ();                This.connection.Dispose (); Logger. Loginformation ($ "Rabbitmqeventbus has been dispose of. Hash Code:{this.            GetHashCode ()}. ");            disposed = true; Base.        Dispose (disposing);        }} private string Initializeeventconsumer (String queue) {var localqueuename = queue; if (string. IsNullOrEmpty (Localqueuename)) {localqueuename = This.channel.QueueDeclare ().        QueueName;        } else {This.channel.QueueDeclare (Localqueuename, True, False, false, NULL);        } var consumer = new Eventingbasicconsumer (This.channel); Consumer.   Received + = Async (model, eventargument) =     {var eventbody = eventargument.body;            var json = Encoding.UTF8.GetString (eventbody); var @event = (IEvent) jsonconvert.deserializeobject (JSON, new jsonserializersettings {typenamehandling =            Typenamehandling.all});            Await This.eventHandlerExecutionContext.HandleEventAsync (@event); if (!autoack) {channel.            Basicack (Eventargument.deliverytag, false);        }        };        This.channel.BasicConsume (Localqueuename, AutoAck:this.autoAck, Consumer:consumer);    return localqueuename; }}

To read the above code, note the following points:

  1. As mentioned above, the constructor needs to accept the Ieventhandlerexecutioncontext object and pass the object to the base class through the base call of the constructor
  2. constructor, the QueueName parameter is an optional parameter, which means:
      1. If you send an event message through Rabbitmqeventbus, you do not need to specify the QueueName parameter, just specify Exchangename, because in Rabbitmq, the publisher of the message does not need to know which queue the message is sent to
      2. If you receive event messages through Rabbitmqeventbus, there are two cases:
        1. If two processes are using Rabbitmqeventbus and the QueueName parameter is specified, and the value of QueueName is the same, the two processes will take turns processing messages routed to the QueueName queue
        2. If two processes are using Rabbitmqeventbus and the QueueName parameter is specified, but the value of queuename is not the same, or none of the QueueName parameters are specified, then both processes will process messages routed to the QueueName queue at the same time
      3. For the concepts of exchange and queue, refer to the official documentation for RABBITMQ
  3. In the Subscribe method, in addition to registering the event handler with the event handler execution context, the specified queue is bound to exchange by means of the Queuebind method
  4. Event data is serialized and deserialized through Newtonsoft.json, using the Typenamehandling.all setting, which enables the serialization of JSON strings with type name information. Doing this here is both reasonable and necessary because if there is no information with the type name, the Jsonconvert.deserializeobject will not be able to determine whether the resulting object can be converted to the IEvent object, and an exception will occur. However, if a more generic messaging system is implemented, the event messages distributed by the application may also be used by applications implemented by Python or Java, and for these applications, They do not know what Newtonsoft.json is, and they cannot learn the original intent of the event message (Intent) by Newtonsoft.json the type name added, and the type information that Newtonsoft.json takes will appear redundant. Therefore, simply using Newtonsoft.json as the serialization and deserialization tool for event messages is actually a defect. A better practice is to implement a custom message serialization, deserialization, which, when serialized, attaches. NET-related such as type information, as metadata (metadata) to the serialized content. In theory, it is reasonable to add some metadata information to the serialized data, except that we make some annotations to the metadata to show that it is. NET Framework, if the third-party system does not care about this information, it can not do any processing of the meta-data
  5. In the Dispose method, pay attention to dispose of the resources used by the RABBITMQ
Using Rabbitmqeventbus

In the customer service, it is very simple to use Rabbitmqeventbus, only to reference the Rabbitmqeventbus assembly. Then in the Configureservices method of the Startup.cs file, replace the use of Passthrougheventbus:

public void Configureservices (iservicecollection services) {this.logger.LogInformation ("Configuring the Service ..."); Services.    Addmvc (); Services. addtransient<ieventstore> (serviceprovider = new Dappereventstore (configuration["mssql:connectionString"    ], serviceprovider.getrequiredservice<ilogger<dappereventstore>> ())); var eventhandlerexecutioncontext = new Eventhandlerexecutioncontext (Services, SC = = SC.    Buildserviceprovider ()); Services.    Addsingleton<ieventhandlerexecutioncontext> (Eventhandlerexecutioncontext); Services.    Addsingleton<ieventbus, passthrougheventbus> ();    var connectionfactory = new ConnectionFactory {HostName = "localhost"}; Services. addsingleton<ieventbus> (sp = new Rabbitmqeventbus (connectionfactory, SP). Getrequiredservice<ilogger<rabbitmqeventbus>> (), Sp. Getrequiredservice<ieventhandlerexecutioncontext> (), Rmq_exchange, Queuename:rm Q_Q ueue)); This.logger.LogInformation ("Service configuration completed, registered to IOC container!") ");}

Note: A better approach is to configure the IOC container through a configuration file, which is handy for using configuration files in the previous Microsoft Patterns and practices Enterprise Library Unity container. This only requires that the customer service be able to configure the IOC container through the configuration file, while only requiring the customer service to rely on (note, not the assembly reference) on different event bus implementations without recompiling the customer service.

Below to verify the effect. First make sure the RABBITMQ is configured and started properly, I am installing on the local machine, using the default installation. Start the ASP. NET Core Web API First, and then create a customer request two times through PowerShell:

Check to see if the database is updated properly:

And check the log information:

Information for Exchange in RABBITMQ:

Summarize

This article provides a rabbitmqeventbus implementation that is sufficient for the time being, and that this implementation can be used in real-world projects. In actual use, may also encounter some problems related to RABBITMQ itself, which requires specific analysis of specific problems. Additionally, this article does not address the issue of loss of event messages, re-sending, and eventual consistency, which are discussed later. Starting with the following, we are moving towards the domain event and event storage part of the CQRS architecture.

Use of source code

The source code for this series of articles is in Https://github.com/daxnet/edasample, the GitHub repo, which distinguishes between different chapters by different release tags. The source code of this article please refer to chapter_3 this tag, as follows:

Welcome to visit my blog new station: http://sunnycoding.net .


Implementation of event-driven architecture under ASP. (iii): RABBITMQ-based event bus

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.