Nikita Pavlovski
Nikita Pavlovski

Reputation: 194

Should I separate RabbitMQ consumer from the rest of the code?

When implementing, let's say, a service to handle email messages, should I put the code of the RabbitMQ subscriber to the separate process from the main program? Or should it be in the common codebase?

Are there any drawbacks of putting them together?

We develop a microservice-oriented application with .Net Core 3. We have to use a messaging bus to let many services react to some events published by another services. We use RabbitMQ.

We've already tried to create two separate applications communicating over HTTP. One listens for the new messages and triggers webhooks on another. The second one does all the stuff.

I expect some advice on how to organize the code. Would the common codebase be easier to maintain in future? Is the network requests timing overhead really important?

Upvotes: 0

Views: 2407

Answers (3)

meshtron
meshtron

Reputation: 1224

We wrote a wrapper that we use in our microservices and applications to abstract away the implementation details of RabbitMQ. One of the key things it handles is tracking Subscribed Events and their associated Handlers. So, the application can define/inject a handler class and it automatically gets called whenever a matching message arrives.

For us, we treat the actual messaging implementation as a cross-cutting concern so we package and use it (Nuget package) just like any other, it's a completely separate project from everything else. It's actually a public Nuget package too, feel free to play with it you want (it's not well documented though).

Here's a quick example of how ours works so you can see the level of integration:

In Startup.cs

using MeshIntegrationBus.RabbitMQ;

public class Startup
{
   public RabbitMqConfig GetRabbitMqConfig()
   {
      ExchangeType exchangeType = (ExchangeType)Enum.Parse(typeof(ExchangeType),
         Configuration["RabbitMQ:IntegrationEventExchangeType"], true);

      var rabbitMqConfig = new RabbitMqConfig
      {
         ExchangeName = Configuration["RabbitMQ:IntegrationEventExchangeName"],
         ExchangeType = exchangeType,
         HostName = Configuration["RabbitMQ:HostName"],
         VirtualHost = Configuration["RabbitMQ:VirtualHost"],
         UserName = Configuration["RabbitMQ:UserName"],
         Password = Configuration["RabbitMQ:Password"],
         ClientProviderName = Configuration["RabbitMQ:ClientProviderName"],
         Port = Convert.ToInt32(Configuration["RabbitMQ:Port"])
       }
      return rabbitMqConfig
   }

   public void ConfigureServices(IServicecollection services)
   {
      services.Add.... // All your stuff

      // If this service will also publish events, add that Service as well (scoped!)
      services.AddScoped<IMeshEventPublisher, RabbitMqEventPublisher>(s =>
         new RabbitMqEventPublisher(rabbitConfig));

      // Since this service will be a singleton, wait until the end to actually add it
      // to the servicecollection - otherwise BuildServiceProvider would duplicate it
      RabbitMqListener rabbitMqListener = new RabbitMqListener(rabbitConfig, 
         services.BuildServiceProvider());

      var nodeEventSubs = Configuration.GetSection(
         $"RabbitMQ:SubscribedEventIds:ServiceA").Get<string[]>();

      // Attach our list of events to a handler
      // Handler must implement IMeshEventProcessor and does have
      // access to the IServiceProvider so can use DI
      rabbitMqListener.Subscribe<ServiceAEventProcessor>(nodeEventSubs);

      services.AddSingleton(rabbitMqListener);
   }
}

That's really all there is to it. You can add multiple handlers per subscribed event and/or reuse the same handler(s) for multiple events. It's proven pretty flexible and reliable - we've been using it for a couple years - and it's easy enough to change/update as needed and let individual services pull the changes when they want/need to.

Upvotes: 1

Roman Oira-Oira
Roman Oira-Oira

Reputation: 94

I suppose you are talking about situation when there is one publisher and many different applications (microservices) that process messages from a message queue. It's worth noticing that we are talking about applications that have different business purpose but not about many instances of the same application.

I would recommend to follow the next suggestions:

  1. One queue is always contains messages of one type. In other words when you deserialize json you should know exactly what class it should be.
  2. In most cases one microservice == one VS solution == one repository.
  3. You would like to share the class to deserialize json between microservices. For this purpose you can create a nuget package with it's interface. This nuget should contains DTO classes without any business logic. Usually we call this nuget "*.Contracts".

The main feature of microservice architecture is simplicity of system modification and those advices should help you to keep it simple from one side and prevent a hell of having absolutely unstructured system (3rd advice) from another side.

One more notice about a case when there is one publisher and one consumer. This can happen when you want to process data in background (for instance, some process can take a lot of time but your website should answer to client immediately. Customer in this case usually is a webjob). For this cases we are using one solution (repository) with both publisher and consumer for simplicity development.

Upvotes: 0

memoricab
memoricab

Reputation: 453

I recently designed and developed a software that communicating with other components via AMQP implemented by RabbitMQ.

As my matter of design I wrote a reusable service class which is being called by other service classes. That is happening in service layer or you can say business layer.

But as you asked if there are several applications and all of them need to implement RabbitMQ client I would create a library/module/package that can be easily imported to applications and configurable.

Upvotes: 0

Related Questions