5 Packages for .NET Core Microservice-Based Architecture Apps

19 Mar 2021
|
7 read
Behind The Code

Microservice architecture is kind of a hot thing right now. The idea of micro web services was first introduced back in 2005 by Dr. Peter Rogers during a conference on cloud computing. Microservices started their growth near 2011 and they became one of the commonly used architectures across .NET developers in the last few years.

When you are designing a microservice-based application, it doesn’t seem to be that hard. But when you start implementing it, a lot of things suddenly become a problem:

  • How are we going to pass some details to another service and make it process the data asynchronously in the background?
  • How can we make the structure of each application easy to understand, and keep every action isolated?
  • How are we going to bring monitoring functionality to help us maintain the application?
  • How are we going to communicate our services between each other, without worrying about the location of each service?

All those problems are really common – these are some basic challenges every developer faces. How to solve them and also apply really good patterns, so that we know that our system will be designed as it should be?

This is where some NuGet packages come into place.

Let me introduce you to five open-source packages. We are using them in our microservice-based applications, and they help us sleep better.

Learn more about software architecture: Monolith, Microservices, Serverless | Software Architecture Overview

Time to adopt microservices? We can help!

Let’s discuss your ideas and work together.

Contact us

NuGet Packages for Microservice-Based Architecture Applications

Code on computer monitor

1. MediatR: Simple Mediator Implementation

This package introduces queries, commands, notifications, and events both in synchronous and asynchronous ways.

Using this package will help you isolate each action, and make it much easier to change anything in a specific flow without worrying that you can break something elsewhere.

Here is just a simple example of a query.

Inside the query, we specify the request parameters. Here, we just want to let consumers of the API apply pagination.

Under the Handler class, we specify the whole logic standing behind each query. We can use the Dependency Injection mechanism here, and just put the whole logic under the ‘Handle’ method.

MediatR Example

public class GetImageList
{
   public class Query : IRequest<PageableResponseDto<ImageListDto>>
   {
       public int Page { get; set; }
       public int ItemsPerPage { get; set; }
   }
  
   public class Handler : IRequestHandler<Query, PageableResponseDto<ImageListDto>>
   {
       private readonly ImageDbContext _context;
       private readonly IMapper _mapper;
  
       public Handler(IDatabaseContextProvider<ImageDbContext> contextProvider, IMapper mapper)
       {
           _context = contextProvider.GetContext() ?? throw new ArgumentNullException(nameof(contextProvider));
           _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
       }

       public async Task<PageableResponseDto<ImageListDto>> Handle(Query request, CancellationToken cancellationToken)
       {
           var query = _context.Images.AsNoTracking().Where(i => !i.Deleted);
           var totalCount = await query.CountAsync(cancellationToken);
           query = query.TryApplyPagination(request.Page, request.ItemsPerPage).OrderBy(i => i.Name.Length)
               .ThenBy(i => i.Name).ThenBy(i => i.Id);
          
           var mappedEntities = await _mapper.ProjectTo<ImageListDto>(query)
               .ToListAsync(cancellationToken);
           return new PageableResponseDto<ImageListDto>(mappedEntities, totalCount);
       }
   }
}

This is how we call the action in a controller:

[Route(APIRoutes.Images)]
public class ImageController : BaseController
{
  
   [HttpGet("")]
   public Task<PageableResponseDto<ImageListDto>> GetAll([FromQuery] GetImageList.Query request, CancellationToken cancellationToken) =>
       Mediator.Send(request, cancellationToken);
} 

Then whenever we want to change something within this endpoint, we just edit the GetImageList.Handler – then we don’t have to worry about whether we can break something elsewhere.

Check more details on MediatR.


2. Flurl: A Simple and Great URL Builder

Very frequently, applications are communicating with other applications.

You have to make some integrations, and when you are communicating over HTTP protocol, you have to provide an URL.

In .NET Core, built-in libraries provide really small support for conveniently building URLs. That’s where Flurl comes in to play.

Imagine you only specify the base URL for a service, and build the URL however you want, without creating your classes, and without creating multiple non-sense lines. Flurl lets you build a URL with its great fluent API.

Let’s say we want to use our previously created service from a different application. This is how we are going to build a URI which we can use later on in HttpRequestMessage.

Flurl Example

var uri = _configuration.ImageServiceBaseAddress.AppendPathSegment("images")
   .SetQueryParams(new {Page = 1, ItemsPerPage = 36}).ToUri();

Isn’t this great? One line, to completely build parameterized URL without worrying about the slashes, ampersands, question marks, and the other stuff related to URLs.

Check more details on Flurl.


3. MassTransit – Asynchronous Messaging

MassTransit makes it easy to create applications and services that leverage message-based, loosely-coupled asynchronous communication for higher availability, reliability, and scalability.

Let’s say that we are using microservice architecture across our application, and we want to dispatch notifications from the notification service. Let’s say one of our services, for example, user service, which is responsible for registering users needs to send an activation link to a user to finish the process.

How we can let the notification service know that it should dispatch an email to the new user?

Well, the deal is pretty simple. We just have to use asynchronous messaging.

We have to combine some kind of message-broker (such as Azure Service Bus or maybe RabbitMQ) in combination with MassTransit, and this is it.

This is a simple class describing a message.

MassTransit Example

public class RegisterUserRequest
{
   public string Email { get; set; }
   public string Token { get; set; }
}

From our User Service, we need to dispatch notifications to the message broker. We can use the IBus interface injected into our action:

await _eventBus.Publish(new RegisterUserRequest { Email = user.Email, Token = token });

Then, in Notification Service, we can consume this message and process it however we want.

public class RegisterUserNotificationRequestConsumer : IConsumer<RegisterUserRequest>
{
   private readonly IEmailService _emailService;

   public RegisterUserNotificationRequestConsumer(IEmailService emailService)
   {
       _emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
   }

   public async Task Consume(ConsumeContext<ChangePasswordRequest> context)
   {
       var message = context.Message;
       await _emailService.SendAsync(message.Email, new { Token = message.Token }, NotificationTypeEnum.USER_REGISTERED);
   }  
}

To configure the MassTransit, RabbitMQ and services, please refer to the MassTransit documentation, which has a lot of great examples.

Recommended reading: Microservices Communication Using Azure Service Bus


4. Serilog: Flexible, Structured Events — Log File Convenience

When you deploy the application, you always want to know how it is working, whether there are any exceptions or not.

If you get a notification that a bug occurred and you need to fix it, I can’t imagine a situation when you don’t have any monitoring attached to your application. Especially in the microservice-architecture which is transferring a lot of data between services.

With a combination of ASP.NET Core built-in interfaces for logging, we can use Serilog. How to add Serilog to our application?

It’s pretty simple. Under our Startup class, we can modify the constructor, and specify the logging configuration.

Serilog Example

public Startup(IConfiguration configuration)
{
   Configuration = configuration;
   Log.Logger = new LoggerConfiguration()
                       .ReadFrom.Configuration(configuration)
                       .WriteTo.File("Logs/log-.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error)
                       .Enrich.FromLogContext()
                       .CreateLogger();
}

Now we have provided a configuration for a Serilog, it’s time to wire it up into our logger factory.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
   loggerFactory.AddSerilog();

   app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}

Then simply, at any place, we can just inject the ILogger and create some logs.

public class DuckCalculator
{
   private readonly ILogger<DuckCalculator> _logger;

   public DuckCalculator(ILogger<DuckCalculator> logger)
   {
       _logger = logger;
   }

   public void DoSomething()
   {
       _logger.LogInformation("Duck duck, I am going to calculate number of members in our duck army");
       var a = 10;
       var b = 20;
       _logger.LogInformation($"{a} ducks + {b} ducks = army of {(a+b)} ducks");
       _logger.LogWarning("This is just a warning that you should not mess with the ducks");
   }
}

Check more details on Serilog.


5. Ocelot: More than A Simple API Gateway

As I mentioned at the beginning of the article – a common problem is locating the services.

Sometimes you worry about the location of a particular service. You want to make life easier for your front-end friends, so you want to provide one base address for the whole back-end, and just let them know what endpoints to use.

Under the hood of your great single-address back-end, can be a microservice-based application. You can even have thousands of services, and you always want the front-end friends to use this single address you have provided them. How to achieve that?

Ocelot will do it for you.

Well, not exactly for you, because you have to configure it properly, but that’s all you have to do. Configure it, and just be a boss.

First, you have to provide an Ocelot configuration:

Ocelot.json

{
 "Routes": [
   {
     "DownstreamPathTemplate": "/api/users/{everything}",
     "DownstreamScheme": "https",
     "DownstreamHostAndPorts": [
       {
         "Host": "app-duckwars-users.azurewebsites.net",
         "Port": 80
       }
     ],
     "UpstreamPathTemplate": "/api/users/{everything}",
     "UpstreamHttpMethod": [ "POST", "GET", "PUT", "DELETE" ]
   },
   {
     "DownstreamPathTemplate": "/api/notifications/{everything}",
     "DownstreamScheme": "https",
     "DownstreamHostAndPorts": [
       {
         "Host": "app-duckwars-notifications.azurewebsites.net",
         "Port": 80
       }
     ],
     "UpstreamPathTemplate": "/api/notifications/{everything}",
     "UpstreamHttpMethod": [ "POST", "GET", "PUT", "DELETE" ]
   }
 ]
}

Then, under Program.cs you have to let the ASP.NET Core that you have specified ocelot.json file for configuration:

public class Program
{
   public static void Main(string[] args)
   {
       CreateHostBuilder(args).Build().Run();
   }

   public static IHostBuilder CreateHostBuilder(string[] args) =>
       Host.CreateDefaultBuilder(args)
           .ConfigureWebHostDefaults(webBuilder =>
           {
               webBuilder.UseStartup<Startup>();
           }).ConfigureAppConfiguration((hostingContext, config) =>
               {
                   var env = hostingContext.HostingEnvironment;
                   config
                   .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                   .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
                   .AddJsonFile("ocelot.json", optional: true, reloadOnChange: true)
                   .AddEnvironmentVariables();
               }
           );
}

And then, simply under Startup.cs – ConfigureServices method you just put a single line:

services.AddOcelot();

This is it. Now when your friend from the front-end will call your application, by the URL you just provided, they are going to use endpoints which you defined under ocelot.json, and then the requests will be passed directly to the place you have set.

A well-known word will better describe this whole process: gateway.

Check more details on the Ocelot.


5 Packages for .NET Core Summary

Microservice-based architecture is a great thing.

It is becoming more and more popular, which means that you will have a lot of problems solved with the power of the community.

Every architecture has its challenges, and also some advantages. Presented packages will boost your efficiency and make your applications much more stable, scalable, and maintainable. I forgot to mention: all those packages are available to anyone, and can be used everywhere for free – that’s because they are open-source.

As they are open-source, you can always contribute and make them even more powerful – it’s up to you!

.NET Core
Snippet
Microservices
Microservices Communication
.Net
API

Written by