Реализация шины событий с помощью RabbitMQ для среды разработки или тестированияImplementing an event bus with RabbitMQ for the development or test environment

Начать следует с того, что если вы создаете пользовательскую шину событий на основе RabbitMQ в контейнере, как это делается в приложении eShopOnContainers, ее следует использовать только в средах разработки и тестирования.We should start by saying that if you create your custom event bus based on RabbitMQ running in a container, as the eShopOnContainers application does, it should be used only for your development and test environments. Не применяйте ее в рабочей среде, если только вы не разрабатываете ее в рамках служебной шины, готовой к развертыванию в рабочей среде.Don't use it for your production environment, unless you are building it as a part of a production-ready service bus. Простая пользовательская шина событий может быть лишена многих критически важных для рабочей среды возможностей, которыми обладают коммерческие служебные шины.A simple custom event bus might be missing many production-ready critical features that a commercial service bus has.

Одна из пользовательских реализаций шины событий в eShopOnContainers по сути представляет собой библиотеку, использующую API RabbitMQ.One of the event bus custom implementations in eShopOnContainers is basically a library using the RabbitMQ API. (Существует и еще одна реализация на основе Служебной шины Azure.)(There's another implementation based on Azure Service Bus.)

Реализация шины событий с помощью RabbitMQ позволяет микрослужбам подписываться на события, публиковать и принимать их, как показано на рисунке 6–21.The event bus implementation with RabbitMQ lets microservices subscribe to events, publish events, and receive events, as shown in Figure 6-21.

Схема, на которой показан API RabbitMQ между отправителем и получателем сообщений.

Рис. 6–21.Figure 6-21. Реализация шины событий на основе RabbitMQRabbitMQ implementation of an event bus

RabbitMQ выступает в роли посредника между издателем сообщения и подписчиками и обрабатывает распространение.RabbitMQ functions as an intermediary between message publisher and subscribers, to handle distribution. В коде класс EventBusRabbitMQ реализует универсальный интерфейс IEventBus.In the code, the EventBusRabbitMQ class implements the generic IEventBus interface. Для этого применяется внедрение зависимостей, что позволяет переходить от этой версии для разработки и тестирования к рабочей версии.This is based on Dependency Injection so that you can swap from this dev/test version to a production version.

public class EventBusRabbitMQ : IEventBus, IDisposable
{
    // Implementation using RabbitMQ API
    //...
}

Реализация образца шины событий для разработки и тестирования на основе RabbitMQ представляет собой стандартный код.The RabbitMQ implementation of a sample dev/test event bus is boilerplate code. Она должна обрабатывать подключение к серверу RabbitMQ и предоставлять код для публикации события сообщения в очередях.It has to handle the connection to the RabbitMQ server and provide code for publishing a message event to the queues. Кроме того, должен быть реализован словарь коллекций, содержащий обработчики событий интеграции для каждого типа событий. Для каждого из этих типов событий могут применяться разные способы создания экземпляра и подписки для каждой микрослужбы-получателя, как показано на рисунке 6–21.It also has to implement a dictionary of collections of integration event handlers for each event type; these event types can have a different instantiation and different subscriptions for each receiver microservice, as shown in Figure 6-21.

Реализация простого метода публикации с помощью RabbitMQImplementing a simple publish method with RabbitMQ

Следующий код является упрощенной версией реализации шины событий для RabbitMQ, демонстрирующей весь сценарий.The following code is a simplified version of an event bus implementation for RabbitMQ, to showcase the whole scenario. Вам необязательно обрабатывать подключение таким образом.You don't really handle the connection this way. Чтобы увидеть полную реализацию, просмотрите фактический код в репозитории dotnet-architecture/eShopOnContainers.To see the full implementation, see the actual code in the dotnet-architecture/eShopOnContainers repository.

public class EventBusRabbitMQ : IEventBus, IDisposable
{
    // Member objects and other methods ...
    // ...

    public void Publish(IntegrationEvent @event)
    {
        var eventName = @event.GetType().Name;
        var factory = new ConnectionFactory() { HostName = _connectionString };
        using (var connection = factory.CreateConnection())
        using (var channel = connection.CreateModel())
        {
            channel.ExchangeDeclare(exchange: _brokerName,
                type: "direct");
            string message = JsonConvert.SerializeObject(@event);
            var body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish(exchange: _brokerName,
                routingKey: eventName,
                basicProperties: null,
                body: body);
       }
    }
}

Реальный код метода Publish в приложении eShopOnContainers улучшен с помощью политики повтора Polly, которая пытается выполнить задачу повторно некоторое число раз, если контейнер RabbitMQ не готов.The actual code of the Publish method in the eShopOnContainers application is improved by using a Polly retry policy, which retries the task a certain number of times in case the RabbitMQ container is not ready. Это может произойти, если контейнеры запускаются с помощью docker-compose. Например, контейнер RabbitMQ может запускаться медленнее других.This can occur when docker-compose is starting the containers; for example, the RabbitMQ container might start more slowly than the other containers.

Как было сказано ранее, в RabbitMQ возможно множество конфигураций, поэтому этот код следует использовать только в средах разработки и тестирования.As mentioned earlier, there are many possible configurations in RabbitMQ, so this code should be used only for dev/test environments.

Реализация кода подписки с помощью интерфейса API RabbitMQImplementing the subscription code with the RabbitMQ API

Так же как и в случае с кодом публикации, приведенный ниже код представляет собой часть упрощенной реализации шины событий для RabbitMQ.As with the publish code, the following code is a simplification of part of the event bus implementation for RabbitMQ. Изменять его также обычно не нужно, если его не требуется улучшить.Again, you usually do not need to change it unless you are improving it.

public class EventBusRabbitMQ : IEventBus, IDisposable
{
    // Member objects and other methods ...
    // ...

    public void Subscribe<T, TH>()
        where T : IntegrationEvent
        where TH : IIntegrationEventHandler<T>
    {
        var eventName = _subsManager.GetEventKey<T>();

        var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
        if (!containsKey)
        {
            if (!_persistentConnection.IsConnected)
            {
                _persistentConnection.TryConnect();
            }

            using (var channel = _persistentConnection.CreateModel())
            {
                channel.QueueBind(queue: _queueName,
                                    exchange: BROKER_NAME,
                                    routingKey: eventName);
            }
        }

        _subsManager.AddSubscription<T, TH>();
    }
}

С каждым типом событий связан канал для получения событий из RabbitMQ.Each event type has a related channel to get events from RabbitMQ. Для каждого канала и типа событий может быть столько обработчиков событий, сколько требуется.You can then have as many event handlers per channel and event type as needed.

Метод Subscribe принимает объект IIntegrationEventHandler, который похож на метод обратного вызова в текущей микрослужбе, а также связанный с ним объект IntegrationEvent.The Subscribe method accepts an IIntegrationEventHandler object, which is like a callback method in the current microservice, plus its related IntegrationEvent object. Затем добавляется обработчик событий в список обработчиков событий, которые может иметь каждый тип событий интеграции в клиентской микрослужбе.The code then adds that event handler to the list of event handlers that each integration event type can have per client microservice. Если код клиента еще не подписался на событие, для данного типа событий создается канал, который позволяет получать события из RabbitMQ принудительным образом, когда они публикуются из любой другой службы.If the client code has not already been subscribed to the event, the code creates a channel for the event type so it can receive events in a push style from RabbitMQ when that event is published from any other service.

Как упоминалось выше, шина событий, реализованная в eShopOnContainers, предназначена только для образовательных целей, так как она обрабатывает только основные сценарии и не готова к рабочей среде.As mentioned above, the event bus implemented in eShopOnContainers has only and educational purpose, since it only handles the main scenarios, so it's not ready for production.

Сведения о рабочих сценариях просмотрите в дополнительных ресурсах о RabbitMQ и разделе Реализация связи на основе событий между микрослужбами.For production scenarios check the additional resources below, specific for RabbitMQ, and the Implementing event-based communication between microservices section.

Дополнительные ресурсыAdditional resources

Готовое к работе решение с поддержкой RabbitMQ.A production-ready solution with support for RabbitMQ.