Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

In this article, we are going to discuss the usage of RabbitMQ Message Queue, Asynchronous communication between microservices, and containerization of microservices using docker

Before starting this article, I will suggest you read my following articles related to microservice

Agenda

  • Introduction of RabbitMQ
  • Benefits of RabbitMQ
  • Implementation of Producer and Consumer Application
  • RabbitMQ Message Queue Setup using Docker
  • Containerization of Producer and Consumer using Docker

Prerequisites

  • Visual Studio 2022
  • Docker Desktop
  • .NET Core 6 SDK
  • SQL Server

Introduction of RabbitMQ

  • Rabbit MQ is the message broker that acts as a middleware while using multiple microservices.
  • RabbitMQ is an open-source message broker software. It is sometimes also called message-oriented middleware.
  • RabbitMQ is written in the Erlang programming language.
  • RabbitMQ is used to reduce a web application's load and delivery time when some of the resources have taken a lot of time to process the data.
    RabbitMQ
  • As you can see in the diagram above, there is one producer who sends a message to the RabbitMQ server. The server will store that message inside the queue in a FIFO manner.
  • Once the producer has sent the message to the queue, there may be multiple consumers that want the message produced by the producer. In that case, consumers subscribe to the message and get that message from the Message Queue as you see in the above diagram.
  • In this section, we will use one eCommerce Site as an example to understand more fully.
  • There are multiple microservices running in the background while we are using the eCommerce website. There is one service that takes care of order details, and another service that takes care of payment details and receipts.
  • Suppose we placed one order. At that time, the order service will start and process our order. After taking the order details, it will send data to the payment service, which takes the payment and sends the payment receipt to the end-users.
  • In this case, there may be a chance of some technical issue occurring in the payment service. If the user did not receive the payment receipt due to this, the user will be impacted and connected with the support team to get the status of the order.
  • There may be another scenario on the user(consumer) side. Perhaps due to some technical issue, the user is exit from the application when the payment is in process. but he will not get any receipt details after payment is successfully processed from backend services.
  • In these scenarios, the RabbitMQ plays an essential role to process messages in the message queue. So, when the consumer gets online, he will receive that order receipt message from the message queue, produced by the producer without impacting the web application.
  • All these examples are just for understanding purposes. There are a lot of scenarios in which RabbitMQ may play an important role while using multiple microservices. Sometimes RabbitMQ is used fully to load balancing between multiple services, or for many other purposes.

Benefits of RabbitMQ

There are many benefits to using a Message Broker to send data to the consumer. Below, we will discuss a few of these benefits.

High Availability

When multiple microservices are used by the application, if one of the microservices is stopped due to technical reasons at that time, the message will never be lost. Instead, it persists in the RabbitMQ server. After some time, when our service starts working, it will connect with RabbitMQ and take the pending message easily.

Scalability

When we use RabbitMQ, at that time our application does not depend on only one server and virtual machine to process a request. If our server is stopped at that time, RabbitMQ will transfer our application load to another server that has the same services running in the background.

Implementation of Producer and Consumer Application

Let’s start the practical implementation of the Application. Here we will take the example of a product owner, who sends a product to offer to product users using a message queue and users will receive that message using a background service that continuously accesses the message queue to receive the product offer message which is sent by the owner and save into the database

Step 1

Create Blank Solution

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 2

Configure Solution

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 3

Create Product Owner Microservice which adds and sends product offers to end user

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 4

Configure Microservice

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 5

Provide additional information

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 6

Create Product User Microservice as a consumer. Follow the same steps to create a web API application as Product Owner Service

Step 7

Project Structure

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 8

Let’s start Implementation of Product Owner Service

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Also, Install the following NuGet Packages

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 9

Create Product Details and Product Offer Detail Class inside the Model folder

Product Details

namespace ProductOwner.Microservice.Model
{
    public class ProductDetails
    {
        public int Id { get; set; }
        public string ProductName { get; set; }
        public int ProductPrice { get; set; }
    }
}

Product Offer Detail

namespace ProductOwner.Microservice.Model
{
    public class ProductOfferDetail
    {
        public int Id { get; set; }
        public int ProductID { get; set; }    
        public string ProductName { get; set; }
        public string ProductOfferDetails { get; set; }
    }
}

Step 10

Next, create DbContextClass inside the Data folder

using Microsoft.EntityFrameworkCore;
using ProductOwner.Microservice.Model;

namespace ProductOwner.Microservice.Data
{
    public class DbContextClass : DbContext
    {
        protected readonly IConfiguration Configuration;

        public DbContextClass(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
        }

        public DbSet<ProductDetails> Products { get; set; }
    }
}

Step 11

Later on, create Static Configuration Manager to configure the appsettings.json file to retrieve environmental variables

namespace ProductOwner.Microservice.Utility
{
    static class StaticConfigurationManager
    {
        public static IConfiguration AppSetting
        {
            get;
        }
        static StaticConfigurationManager()
        {
            AppSetting = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
        }
    }
}

Step 12

Create IProductService and ProductService class for data manipulation

IProductService

using ProductOwner.Microservice.Model;

namespace ProductOwner.Microservice.Services
{
    public interface IProductService
    {
        public Task<IEnumerable<ProductDetails>> GetProductListAsync();
        public Task<ProductDetails> GetProductByIdAsync(int id);
        public Task<ProductDetails> AddProductAsync(ProductDetails product);
        public bool SendProductOffer(ProductOfferDetail productOfferDetails);
    }
}

ProductService                                                              

using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using ProductOwner.Microservice.Data;
using ProductOwner.Microservice.Model;
using ProductOwner.Microservice.Utility;
using RabbitMQ.Client;
using System.Text;

namespace ProductOwner.Microservice.Services
{
    public class ProductService : IProductService
    {
        private readonly DbContextClass _dbContext;

        public ProductService(DbContextClass dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<IEnumerable<ProductDetails>> GetProductListAsync()
        {
            return await _dbContext.Products.ToListAsync();
        }
        public async Task<ProductDetails> GetProductByIdAsync(int id)
        {
            return await _dbContext.Products.Where(x => x.Id == id).FirstOrDefaultAsync();
        }

        public async Task<ProductDetails> AddProductAsync(ProductDetails product)
        {
            var result = _dbContext.Products.Add(product);
            await _dbContext.SaveChangesAsync();
            return result.Entity;
        }

        public bool SendProductOffer(ProductOfferDetail productOfferDetails)
        {
            var RabbitMQServer = "";
            var RabbitMQUserName = "";
            var RabbutMQPassword = "";

            if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production")
            {
                RabbitMQServer = Environment.GetEnvironmentVariable("RABBIT_MQ_SERVER");
                RabbitMQUserName = Environment.GetEnvironmentVariable("RABBIT_MQ_USERNAME");
                RabbutMQPassword = Environment.GetEnvironmentVariable("RABBIT_MQ_PASSWORD");

            }
            else
            {
                RabbitMQServer = StaticConfigurationManager.AppSetting["RabbitMQ:RabbitURL"];
                RabbitMQUserName = StaticConfigurationManager.AppSetting["RabbitMQ:Username"];
                RabbutMQPassword = StaticConfigurationManager.AppSetting["RabbitMQ:Password"];
            }

            try
            {
                var factory = new ConnectionFactory()
                { HostName = RabbitMQServer, UserName = RabbitMQUserName, Password = RabbutMQPassword };
                using (var connection = factory.CreateConnection())
                using (var channel = connection.CreateModel())
                {
                    //Direct Exchange Details like name and type of exchange
                    channel.ExchangeDeclare(StaticConfigurationManager.AppSetting["RabbitMqSettings:ExchangeName"], StaticConfigurationManager.AppSetting["RabbitMqSettings:ExchhangeType"]);

                    //Declare Queue with Name and a few property related to Queue like durabality of msg, auto delete and many more
                    channel.QueueDeclare(queue: StaticConfigurationManager.AppSetting["RabbitMqSettings:QueueName"],
                                         durable: true,
                                         exclusive: false,
                                         autoDelete: false,
                                         arguments: null);

                    //Bind Queue with Exhange and routing details
                    channel.QueueBind(queue: StaticConfigurationManager.AppSetting["RabbitMqSettings:QueueName"], exchange: StaticConfigurationManager.AppSetting["RabbitMqSettings:ExchangeName"], routingKey: StaticConfigurationManager.AppSetting["RabbitMqSettings:RouteKey"]);

                    //Seriliaze object using Newtonsoft library
                    string productDetail = JsonConvert.SerializeObject(productOfferDetails);
                    var body = Encoding.UTF8.GetBytes(productDetail);

                    var properties = channel.CreateBasicProperties();
                    properties.Persistent = true;

                    //publish msg 
                    channel.BasicPublish(exchange: StaticConfigurationManager.AppSetting["RabbitMqSettings:ExchangeName"],
                                         routingKey: StaticConfigurationManager.AppSetting["RabbitMqSettings:RouteKey"],
                                         basicProperties: properties,
                                         body: body);

                    return true;
                }
            }
       
            catch (Exception)
            {
            }
            return false;
        }
    }
}

Step 13

Add the connection string and some RabbitMQ server settings related to configuration

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=DESKTOP;Initial Catalog=ProductOwnerServiceDB;User Id=sa;Password=database;"
  },
  "RabbitMQ": {
    "RabbitURL": "localhost",
    "Username": "guest",
    "Password": "guest"
  },
  "RabbitMqSettings": {
    "ExchangeName": "OfferExchange",
    "ExchhangeType": "direct",
    "QueueName": "offer_queue",
    "RouteKey": "offer_route"
  }
}

Step 14

Register a few services inside Program.cs class

using ProductOwner.Microservice.Data;
using ProductOwner.Microservice.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddDbContext<DbContextClass>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseSwagger();
app.UseSwaggerUI();


app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Step 15

Next, Create a Product Controller

using Microsoft.AspNetCore.Mvc;
using ProductOwner.Microservice.Model;
using ProductOwner.Microservice.Services;

namespace ProductOwner.Microservice.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        private readonly IProductService productService;

        public ProductsController(IProductService _productService)
        {
            productService = _productService;
        }

        [HttpGet("list")]
        public Task<IEnumerable<ProductDetails>> ProductListAsync()
        {
            var productList = productService.GetProductListAsync();
            return productList;

        }
        [HttpGet("filterlist")]
        public Task<ProductDetails> GetProductByIdAsync(int Id)
        {
            return productService.GetProductByIdAsync(Id);
        }

        [HttpPost("addproduct")]
        public Task<ProductDetails> AddProductAsync(ProductDetails product)
        {
            var productData = productService.AddProductAsync(product);
            return productData;
        }

        [HttpPost("sendoffer")]
        public bool SendProductOfferAsync(ProductOfferDetail productOfferDetails)
        {
            bool isSent = false;
            if (productOfferDetails != null)
            {
                isSent = productService.SendProductOffer(productOfferDetails);

                return isSent;
            }
            return isSent;
        }
    }
}

Step 16

Apply migration and update database using the following command

add-migration "Initial"

update-database

Let’s start the Implementation of Product User Service

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 1

Install the following NuGet Packages

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 2

Create Product Offer Details class inside model

namespace ProductUser.Microservice.Model
{
    public class ProductOfferDetail
    {
        public int Id { get; set; }
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public string ProductOfferDetails { get; set; }
    }
}

Step 3

Next, add DbContextClass inside the Data folder for data manipulation

using Microsoft.EntityFrameworkCore;
using ProductUser.Microservice.Model;

namespace ProductUser.Microservice.Data
{
    public class DbContextClass : DbContext
    {
        protected readonly IConfiguration Configuration;

        public DbContextClass(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
        }

        public DbSet<ProductOfferDetail> ProductOffers { get; set; }
    }
}

Step 4

Later on, create Static Configuration Manager to configure the appsettings.json file to retrieve environmental variables

namespace ProductUser.Microservice.Utility
{
    static class StaticConfigurationManager
    {
        public static IConfiguration AppSetting
        {
            get;
        }
        static StaticConfigurationManager()
        {
            AppSetting = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
        }
    }
}

Step 5

Create IUserService and UserService class for data manipulation

IUserService

using ProductUser.Microservice.Model;

namespace ProductUser.Microservice.Services
{
    public interface IUserService
    {
        public Task<IEnumerable<ProductOfferDetail>> GetProductListAsync();
        public Task<ProductOfferDetail> GetProductByIdAsync(int id);
        public Task<ProductOfferDetail> AddProductAsync(ProductOfferDetail product);
    }
}

UserService                   

using Microsoft.EntityFrameworkCore;
using ProductUser.Microservice.Data;
using ProductUser.Microservice.Model;

namespace ProductUser.Microservice.Services
{
    public class UserService : IUserService
    {
        private readonly DbContextClass _dbContext;

        public UserService(DbContextClass dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<ProductOfferDetail> GetProductByIdAsync(int id)
        {
            return await _dbContext.ProductOffers.Where(x => x.Id == id).FirstOrDefaultAsync();
        }

        public async Task<IEnumerable<ProductOfferDetail>> GetProductListAsync()
        {
            return await _dbContext.ProductOffers.ToListAsync();
        }

        public async Task<ProductOfferDetail> AddProductAsync(ProductOfferDetail product)
        {
            var result = _dbContext.ProductOffers.Add(product);
            await _dbContext.SaveChangesAsync();
            return result.Entity;
        }
    }
}

 Step 6

Add the connection string and some RabbitMQ server settings related to configuration

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=DESKTOP;Initial Catalog=ProductUserServiceDB;User Id=sa;Password=database;"
  },
  "RabbitMQ": {
    "RabbitURL": "localhost",
    "Username": "guest",
    "Password": "guest"
  },
  "RabbitMqSettings": {
    "ExchangeName": "OfferExchange",
    "ExchhangeType": "direct",
    "QueueName": "offer_queue",
    "RouteKey": "offer_route"
  }
}

Step 7

Register a few services inside Program.cs class

using ProductUser.Microservice.BackgroundServices;
using ProductUser.Microservice.Data;
using ProductUser.Microservice.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddDbContext<DbContextClass>();
builder.Services.AddHostedService<RabbitMQBackgroundConsumerService>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseSwagger();
app.UseSwaggerUI();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Step 8

Add RabbitMQ Background Consumer Service which is run continuously in the background and checks the product offer message in the message queue which is produced by the producer

using Newtonsoft.Json;
using ProductUser.Microservice.Data;
using ProductUser.Microservice.Model;
using ProductUser.Microservice.Utility;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;

namespace ProductUser.Microservice.BackgroundServices
{
    public class RabbitMQBackgroundConsumerService : BackgroundService
    {
        private IConnection _connection;
        private IModel _channel;
        private IServiceScopeFactory serviceScopeFactory;

        public RabbitMQBackgroundConsumerService(IServiceScopeFactory _serviceScopeFactory)
        {
            serviceScopeFactory = _serviceScopeFactory;
            InitRabbitMQ();
        }

        private void InitRabbitMQ()
        {
            var RabbitMQServer = "";
            var RabbitMQUserName = "";
            var RabbutMQPassword = "";

            if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production")
            {
                RabbitMQServer = Environment.GetEnvironmentVariable("RABBIT_MQ_SERVER");
                RabbitMQUserName = Environment.GetEnvironmentVariable("RABBIT_MQ_USERNAME");
                RabbutMQPassword = Environment.GetEnvironmentVariable("RABBIT_MQ_PASSWORD");

            }
            else
            {
                RabbitMQServer = StaticConfigurationManager.AppSetting["RabbitMQ:RabbitURL"];
                RabbitMQUserName = StaticConfigurationManager.AppSetting["RabbitMQ:Username"];
                RabbutMQPassword = StaticConfigurationManager.AppSetting["RabbitMQ:Password"];
            }

            var factory = new ConnectionFactory()
            { HostName = RabbitMQServer, UserName = RabbitMQUserName, Password = RabbutMQPassword };

            // create connection  
            _connection = factory.CreateConnection();

            // create channel  
            _channel = _connection.CreateModel();

            //Direct Exchange Details like name and type of exchange
            _channel.ExchangeDeclare(StaticConfigurationManager.AppSetting["RabbitMqSettings:ExchangeName"], StaticConfigurationManager.AppSetting["RabbitMqSettings:ExchhangeType"]);

            //Declare Queue with Name and a few property related to Queue like durabality of msg, auto delete and many more
            _channel.QueueDeclare(queue: StaticConfigurationManager.AppSetting["RabbitMqSettings:QueueName"],
                        durable: true,
                        exclusive: false,
                        autoDelete: false,
                        arguments: null);


            _channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);

            _channel.QueueBind(queue: StaticConfigurationManager.AppSetting["RabbitMqSettings:QueueName"], exchange: StaticConfigurationManager.AppSetting["RabbitMqSettings:ExchangeName"], routingKey: StaticConfigurationManager.AppSetting["RabbitMqSettings:RouteKey"]);

            _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown;
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            stoppingToken.ThrowIfCancellationRequested();

            var consumer = new EventingBasicConsumer(_channel);
            consumer.Received += (ch, ea) =>
            {
                // received message  
                var content = System.Text.Encoding.UTF8.GetString(ea.Body.ToArray());

                // acknowledge the received message  
                _channel.BasicAck(ea.DeliveryTag, false);

                //Deserilized Message
                var message = Encoding.UTF8.GetString(ea.Body.ToArray());
                var productDetails = JsonConvert.DeserializeObject<ProductOfferDetail>(message);

                //Stored Offer Details into the Database
                using (var scope = serviceScopeFactory.CreateScope())
                {
                    var _dbContext = scope.ServiceProvider.GetRequiredService<DbContextClass>();
                    var result = _dbContext.ProductOffers.Add(productDetails);
                    _dbContext.SaveChanges();
                }

            };

            consumer.Shutdown += OnConsumerShutdown;
            consumer.Registered += OnConsumerRegistered;
            consumer.Unregistered += OnConsumerUnregistered;
            consumer.ConsumerCancelled += OnConsumerConsumerCancelled;

            _channel.BasicConsume(StaticConfigurationManager.AppSetting["RabbitMqSettings:QueueName"], false, consumer);
            return Task.CompletedTask;
        }

        private void OnConsumerConsumerCancelled(object sender, ConsumerEventArgs e) { }
        private void OnConsumerUnregistered(object sender, ConsumerEventArgs e) { }
        private void OnConsumerRegistered(object sender, ConsumerEventArgs e) { }
        private void OnConsumerShutdown(object sender, ShutdownEventArgs e) { }
        private void RabbitMQ_ConnectionShutdown(object sender, ShutdownEventArgs e) { }

        public override void Dispose()
        {
            _channel.Close();
            _connection.Close();
            base.Dispose();
        }
    }
}

Step 9

Next, create User Offers Controller


using Microsoft.AspNetCore.Mvc;
using ProductUser.Microservice.Model;
using ProductUser.Microservice.Services;

namespace ProductUser.Microservice.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserOffersController : ControllerBase
    {
        private readonly IUserService userService;

        public UserOffersController(IUserService _userService)
        {
           userService = _userService;
        }

        [HttpGet("offerlist")]
        public Task<IEnumerable<ProductOfferDetail>> ProductListAsync()
        {
            var productList = userService.GetProductListAsync();
            return productList;

        }

        [HttpGet("getofferbyid")]
        public Task<ProductOfferDetail> GetProductByIdAsync(int Id)
        {
            return userService.GetProductByIdAsync(Id);
        }
    }
}

Step 10

Add migration and update database using the following command

add-migration "Initial"

update-database

RabbitMQ Message Queue Setup using Docker

Install RabbitMQ docker image using the following command (Note- docker desktop is in running mode):

docker pull rabbitmq:3-management

Next, create a container and start using the RabbitMQ Image that we downloaded:

docker run --rm -it -p 15672:15672 -p 5672:5672 rabbitmq:3-management

Open the following URL to open the RabbitMQ dashboard on the port we set while running docker:

http://localhost:15672/

When you click the URL, the login page will open.

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Login using the default username and password guest

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Here you can see the offer queue when you run the producer and consumer applications. Also, details about the message which are ready and all things related to that.

Containerization of Producer and Consumer using Docker

(Note- I’m not using Visual Studio to create docker related things)

Step 1

Create a Docker file for Product Owner Microservice inside the root folder.

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app

EXPOSE 443
EXPOSE 80 

# copy project csproj file and restore it in docker directory
COPY ./*.csproj ./
RUN dotnet restore

# Copy everything into the docker directory and build
COPY . .
RUN dotnet publish -c Release -o out

# Build runtime final image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "ProductOwner.Microservice.dll"]

Step 2

Next, create a Docker file for Product User Microservice inside the root folder.

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app

EXPOSE 443
EXPOSE 80 

# copy project csproj file and restore it in docker directory
COPY ./*.csproj ./
RUN dotnet restore

# Copy everything into the docker directory and build
COPY . .
RUN dotnet publish -c Release -o out

# Build runtime final image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "ProductUser.Microservice.dll"]

Step 3

Create a docker-compose.yml file inside the main solution folder.

version: '3.5'
services:
  ProductOwner.Microservice:
   image: ${DOCKER_REGISTRY-}ownerservice:v1
   build:
    context: ./ProductOwner.Microservice
    dockerfile: Dockerfile
   environment: 
    - ASPNETCORE_ENVIRONMENT=Production
    - CONNECTIONSTRINGS__DEFAULTCONNECTION=Data Source=192.168.2.1,1433;Initial Catalog=ProductOwnerServiceDB;User Id=sa;Password=database
    - RABBIT_MQ_SERVER=192.168.2.1
    - RABBIT_MQ_USERNAME=guest
    - RABBIT_MQ_PASSWORD=guest
    - RABBITMQSETTINGS__EXCHANGENAME=OfferExchange
    - RABBITMQSETTINGS__EXCHHANGETYPE=direct
    - RABBITMQSETTINGS__QUEUENAME=offer_queue
    - RABBITMQSETTINGS__ROUTEKEY=offer_route
   ports:
    - "4201:80"
  ProductUser.Microservice:
   image: ${DOCKER_REGISTRY-}userservice:v1
   build:
    context: ./ProductUser.Microservice
    dockerfile: Dockerfile
   environment:
    - ASPNETCORE_ENVIRONMENT=Production   
    - CONNECTIONSTRINGS__DEFAULTCONNECTION=Data Source=192.168.2.1,1433;Initial Catalog=ProductUserServiceDB;User Id=sa;Password=database
    - RABBIT_MQ_SERVER=192.168.2.1
    - RABBIT_MQ_USERNAME=guest
    - RABBIT_MQ_PASSWORD=guest
    - RABBITMQSETTINGS__EXCHANGENAME=OfferExchange
    - RABBITMQSETTINGS__EXCHHANGETYPE=direct
    - RABBITMQSETTINGS__QUEUENAME=offer_queue
    - RABBITMQSETTINGS__ROUTEKEY=offer_route
   ports:
    - "4202:80"
  
  • Here you can see that we used the environment section to override the connection string that is present in the appsetting.json file and some environmental variables
  • Also, we put our machine IP Address over there in the connection string and port on which our SQL Server is running mode. Because if you put the server’s name, it will show some error while you are navigating your application which is run in a docker container.
  • You can get your IP address using the ipconfig command through CMD.

Step 4

Open CMD inside the main solution folder where the docker-compose.yml file is present and execute the following command.

docker-compose build

docker-compose up

Step 5

Open Docker Desktop and you can see inside that there are three images created and is in running mode.

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 6

Open the Container section and there you will see your two images running inside one container and another one related to RabbitMQ is on another conatiner with their individual port number.

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 7

Use the following URLs to check your microservices functionality which is running inside a docker container.

http://localhost:4201/swagger/index.html

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

http://localhost:4202/swagger/index.html

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 8

Now when we send some offers using product owner microservice then it will immediately listen by consumer user background service from the RabbitMQ Message Queue and stored in the database of User’s Services

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

Step 9)

Here you will see offer details which is sent by producer and our background services stored that message inside database

Asynchronous Communication Between Microservices Using .NET Core API, RabbitMQ, and Docker

This is a simple scenario for understanding purposes there are many real-time scenarios in which RabbitMQ plays a very important role. Suppose consumer service is offline and the producer produces some message related to offers that persist inside the message queue and when the user is online and then uses services is in a running mode that will take a message from the message queue and store it inside the database. RabbitMQ helps to decouple applications in many cases like this.

Conclusion

Here we discussed RabbitMQ and real-time scenarios with the help of Message Queue. Also, the containerization of microservices using Docker and asynchronous communication between them.

Happy Learning!