ASP.NET Core Microservices is a type of Architecture in which application is created as multiple small independent serviceable components. This article will cover in detail how to create microservices with ASP.NET Core, Serilog, Swagger UI, Health Checks & Docker containers.
Table of Contents
Microservices Architecture
The Microservices architecture style is shown in the figure above. Microservices Architecture is a style in which one large application is developed as a set of small independent services. Here each service implements a specific functionality and has its own data store. Each service functionality should be small enough to implement just one use case and big enough to provide some value. Each service should be deployable separately so that it can be scaled independently. As far as possible these services should be independent of each other and if there is a need for inter-service communication then some lightweight communication protocol can be used.
Identity Provider is used to provide user authentication services to an application.
To know details about Identity Provider & also to know about how to secure your ASP.NET Core based application you can check my series on ASP.NET Core Security
API Gateway is a single entry point for all requests that help in managing the endpoints and coordinates with different services.
Container is a standard unit of software that bundles application or feature and all of its dependencies so that application can be deployed quickly and reliably on any new system that has container host.
Container Orchestration is a piece of software that is used to manage the life-cycle of containers in a large application. This helps to scale application on basis of the load.
Microservice is the actual small independent service which is bundled in a container along with it dependencies
Data Store is used to store microservice data and the basic principle is that each service manages its own data.
Monolithic v/s Microservices
Monolithic | Microservices |
---|---|
Single service/application should contain all the business functionality | Single service should contains only one business functionality |
All service are tightly coupled | All services are loosely coupled |
Application is developed in one single programming language | Each service can be in different programming language |
Single database for all services. | Each service has separate database |
All services needs to be deployed together on VM | Each service can be deployed on separate VM |
All services run in same process so if one service goes down then whole application breaks | Each service runs in different process so failure of one service does not affects other services |
Difficult to scale a particular service as new instance will have to have all services | Can be Scaled easily as any single service can be deployed independently |
Single large team works on whole application | Separate small team work on each Service which are more focused. |
Simple to develop & test small applications | Add complexity to the application by the fact that its a distributed system |
Why microservices with ASP.NET Core?
.NET Core provides following advantages which works for microservices with ASP.NET Core
- A light-weight framework built from the ground up
- Cross-platform support
- Optimized for containerization
Implement Microservices with ASP.NET Core
Here is the short & quick video on implementation of Microservices with ASP.NET Core (.NET Core Microservices Example)
Here we will cover in detail the step by step process to create microservice with ASP.NET Core. We will be creating an order service that will provide endpoints to Add, Cancel, Get Order By Id & Get Order(s) By Customer Id in the application. This demo has been executed in Visual Studio 2019 version 16.6.2
Create Service with CRUD operations
Create ASP.NET Core Project
For microservices with ASP.NET Core demo, we will be creating an ASP.NET Core 3.1 Web API project to demonstrate .net Core Web API microservices.
Implement Order Service
To demonstrate Microservices with ASP.NET Core we will be creating order microservice which will contain functionality only related to orders. This is the basic requirement of Microservices that it should implement only one function so our Microservice will contain the functionality related to orders only. We will be implementing following endpoints
- Add – To create a new order
- Cancel – To cancel an existing order
- GetById – Get Order by Id
- GetByCustomerId – Get all orders for Customer Id
Below we will quickly add the entity model for Order & enable entity framework core for order microservice.
If you need further details on how an entity framework works then check my other article on Entity Framework Core in ASP.NET Core 3.1
Add Model
Add an order entity class
public class Order { public string Id { get; set; } public string ProductId { get; set; } public double Cost { get; set; } public DateTime Placed { get; set; } public string CustomerId { get; set; } public string Status { get; set; } }
Add CRUD operations to the API with entity framework core
We will make use of Entity Framework Core to implement database operations for the order service.
Install required packages for entity framework core
Install-Package Microsoft.EntityFrameworkCore Install-Package Microsoft.EntityFrameworkCore.Design Install-Package Microsoft.EntityFrameworkCore.SqlServer Install-Package Microsoft.EntityFrameworkCore.Tools
Add database context class
This is the main class that co-ordinates with entity framework functionality for a given model class.
public interface IApplicationDbContext { DbSet<Order> Orders { get; set; } Task<int> SaveChanges(); }
public class ApplicationDbContext : DbContext, IApplicationDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Order> Orders { get; set; } public new async Task<int> SaveChanges() { return await base.SaveChangesAsync(); } }
Add Order Repository
The repository is a component that encapsulates the objects related to data storage and operations performed over them. DbContext is passed as a parameter in the constructor using dependency injection.
public interface IOrderRepository { Task<string> Add(Order order); Task<Order> GetById(string id); Task<string> Cancel(string id); Task<Order> GetByCustomerId(string custid); }
public class OrderRepository : IOrderRepository { private IApplicationDbContext _dbcontext; public OrderRepository(IApplicationDbContext dbcontext) { _dbcontext = dbcontext; } public async Task<string> Add(Order order) { _dbcontext.Orders.Add(order); await _dbcontext.SaveChanges(); return order.Id; } public async Task<string> Cancel(string id) { var orderupt = await _dbcontext.Orders.Where(orderdet => orderdet.Id == id).FirstOrDefaultAsync(); if (orderupt == null) return "Order does not exists"; orderupt.Status = "Cancelled"; await _dbcontext.SaveChanges(); return "Order Cancelled Successfully"; } public async Task<Order> GetByCustomerId(string custid) { var order = await _dbcontext.Orders.Where(orderdet => orderdet.CustomerId == custid).FirstOrDefaultAsync(); return order; } public async Task<Order> GetById(string id) { var order = await _dbcontext.Orders.Where(orderdet => orderdet.Id == id).FirstOrDefaultAsync(); return order; } }
Connect application to the database
Specify the SQL Server connection string in appsettings.json file.
"ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=OrderDb;Trusted_Connection=True;MultipleActiveResultSets=true" }
Register services in startup class
You need to configure the database context & order repository as a service in method ConfigureServices in the startup class
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"), ef => ef.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>()); services.AddTransient<IOrderRepository, OrderRepository>(); services.AddControllers(); //Remaining code has been removed }
Add Migrations
To automate the migrations & create a database we need to run the following commands in the package manager console.
add-migration InitialMigration update-database
One thing to note here is that table is created for order details only. Product & Customer tables are not created with foreign key reference as you have to keep microservice small & focussed on one single functionality. Product & Customer will be in a separate database of their own with their own microservice implementation. If you need product details or customer details to be displayed as part of order details then you need to call the respective microservice and fetch the required details.
Add Order Controller
Here is the code for the order controller which has been added to expose the endpoints of the order microservice. Order Repository has been passed as a constructor parameter using dependency injection
[Route("api/[controller]")] [ApiController] public class OrderController : ControllerBase { private IOrderRepository _orderRepository; public OrderController(IOrderRepository orderRepository) { _orderRepository = orderRepository; } [HttpPost] [Route("Add")] public async Task<ActionResult> Add([FromBody] Order orderdet) { string orderid = await _orderRepository.Add(orderdet); return Ok(orderid); } [HttpGet] [Route("GetByCustomerId/{id}")] public async Task<ActionResult> GetByCustomerId(string id) { var orders = await _orderRepository.GetByCustomerId(id); return Ok(orders); } [HttpGet] [Route("GetById/{id}")] public async Task<ActionResult> GetById(string id) { var orderdet = await _orderRepository.GetById(id); return Ok(orderdet); } [HttpDelete] [Route("Cancel/{id}")] public async Task<IActionResult> Cancel(string id) { string resp = await _orderRepository.Cancel(id); return Ok(resp); } }
Our order service is ready to perform operations. But to make it a microservice we will have to enable features like Logging, Exception Handling, Documentation, Monitoring, Containerization, etc. Let’s look at how we will implement these features for Microservices with ASP.NET Core
Add Web API versioning
Microservices should be built in such a way that it is easy to change without breaking existing clients and also should be able to support multiple versions side by side so Web API versioning will help us achieve this.
Microservices with ASP.NET Core supports Web API versioning, a feature using which we can implement multiple versions of the same API so that different clients can work with the required version of API.
Implement Web API versioning using URL
In Web API versioning using URL, the version number is part of the URL i.e. http://server:port/api/v1/order/add
Install Web API versioning package
Install-Package Microsoft.AspNetCore.Mvc.Versioning
Configure Web API versioning in Startup class
Enable support for Web API versioning in ConfigureServices method in the startup.cs file.
public void ConfigureServices(IServiceCollection services) { services.AddApiVersioning(apiVerConfig => { apiVerConfig.AssumeDefaultVersionWhenUnspecified = true; apiVerConfig.DefaultApiVersion = new ApiVersion(new DateTime(2020, 6, 6)); }); //Remaining code has been removed }
Add URL Web API versioning to order controller
You need to add parameter v{version:apiVersion} in route attribute like Route(“api/v{version:apiVersion}/[controller]”) so that API version becomes part of URL.
[ApiVersion("1.0")] [Route("api/v{version:apiVersion}/[controller]")] [ApiController] public class OrderController : ControllerBase { //Remaining code has been removed }
If you need further details on Web API versioning in ASP.NET Core then check my other article on Web API Versioning in ASP.NET Core 3.1
Add logging to microservice
After deploying to production it should be easy to track and analyze issues. Logs help us to analyze complex issues which sometimes might be difficult to simulate. There will always be a need to troubleshoot application issues for which logs will be required for analysis. For Microservices with ASP.NET Core, we will be using Serilog to log application details.
Implement logging with Serilog
There are many third-party providers and one of these is Serilog. Serilog is a popular third party logging provider that is supported in ASP.NET Core Logging.
For a detailed explanation on implementing Serilog in ASP.NET Core, you can refer to my another article on ASP.NET Core Logging with Serilog
Install required Serilog packages
Install-Package Serilog.AspNetCore Install-Package Serilog.Extensions.Logging Install-Package Serilog.Extensions.Hosting Install-Package Serilog.Sinks.RollingFile Install-Package Serilog.Sinks.Async
Add Serilog configuration
Serilog RollingFile Sink is implemented by adding configuration to the appsettings.json file. Log level can be specified by setting the log level value in the property named MinimumLevel.
"Serilog": { "MinimumLevel": "Information", "WriteTo": [ { "Name": "Async", "Args": { "configure": [ { "Name": "RollingFile", "Args": { "pathFormat": "Serilogs\\AppLogs-{Date}.log", "outputTemplate": "{Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}", "fileSizeLimitBytes": 10485760 } } ] } } ] }
Configure Serilog in Program & Startup class
Add UseSerilog() to CreateDefaultBuilder in Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
Load Serilog configuration from appsettings.json file in Startup.cs
public Startup(IConfiguration configuration) { Configuration = configuration; Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .CreateLogger(); }
Add logging in order controller
Serilog.Log class has been used to add logs with method Debug for debugging & Error in case of some exception.
[HttpPost] [Route("Add")] public async Task<ActionResult> Add([FromBody] Order orderdet) { try { Log.Debug("Order Addition Started"); Log.Debug("Order Addition Input", orderdet); string orderid = await _orderRepository.Add(orderdet); Log.Debug("Order Addition Output", orderid); return Ok(orderid); } catch (Exception ex) { Log.Error("Order Addition Failed", ex); throw new Exception("Order Addition Failed", innerException: ex); } } //Remaining code has been removed
Here for demonstration purposes, I have added logging feature only to the controller action ‘Add’ but as good practice, you need to add logs in a complete application with proper log levels which can assist in debugging complex issues on production. Since this is microservice, Async log writing has been configured as that reduces the overhead of logging calls by delegating work to a background thread.
Also, one more thing to note here is if you are running ASP.NET core application in docker container then you need to be careful with log file location as if you store the log file in the same container itself then there is a possibility of losing that data. In containerized environment logs should be stored on some persistent volume.
Add service monitoring mechanism
It is always good to keep a check on whether our service is up and running or functioning properly. Before our clients inform us about our broken service we should be able to proactively identify our broken services and take corrective actions. Healthchecks allow us to check if service is healthy i.e. up & running. Healthcheck endpoint can also be used to check its status from load balancer & disable a server on load balancer if our service returned failure for a health check on that server. For Microservices with ASP.NET Core, we will be using ASP.NET Core HealthChecks for Monitoring.
Implement microservice monitoring using ASP.NET Core Healthchecks
Healthchecks is an in-built middleware in ASP.NET Core for reporting the health of an application. Healthchecks can be exposed as one more endpoint in the application.
Install healthchecks packages
Using package manager console install the required packages for healthchecks
Install-Package Microsoft.Extensions.Diagnostics.HealthChecks Install-Package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore
Configure healthchecks ins Startup class
Here we have configured basic application health check & Entity Framework database context health check that confirms that the app can communicate with the database configured for an Entity Framework Core DbContext
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddHealthChecks() .AddDbContextCheck<ApplicationDbContext>(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseHealthChecks("/checkhealth"); } //Remaining code has been removed }
You can check the service health using URL http://serverip:port/checkhealth
Create documentation for microservices with ASP.NET Core
It is always good to maintain updated documentation for microservices. Other teams should be able to refer to these API specifications and consume microservice accordingly. We will be implementing Swashbuckle.AspNetCore for generating Swagger documents for order microservice.
Implement documentation using Swashbuckle Swagger
Swashbuckle is an open-source library to generate swagger documentation for ASP.NET Core Web API. This documentation can be used to explore and test API.
Install required swashbuckle swagger packages
Using package manager console install the required packages for swashbuckle
Install-Package Swashbuckle.AspNetCore Install-Package Microsoft.OpenApi
Configure swagger ins Startup class
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Microservice - Order Web API", Version = "v1", Description = "Sample microservice for order", }); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseSwagger(); app.UseSwaggerUI(options => options.SwaggerEndpoint("/swagger/v1/swagger.json", "PlaceInfo Services")); } //Remaining code has been removed }
Below is the documentation generated with swagger for order microservice
Add containerization to microservice
Containerization is used to bundle an application or feature of an application, all of it dependencies & its configuration in a container image. This image is deployed on the host operating system and bundled application works as a unit. This concept of container images allows us to deploy these across environments with little or no modifications at all. This way it is easy to scale out microservice quickly as the new containers can be easily deployed for short term purposes.
Docker will be used to add containerization to our microservice. Docker is an open-source project for creating containers that can run on docker host either on cloud or on-premises.
Implement containerization using Docker
Here is a quick .NET core microservices docker tutorial. To enable Docker support in ASP.NET Core project right on the project in the solution explorer and select Add=>Docker Support from the Menu
This will enable docker and also create a Dockerfile to the project as shown below
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. #Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed. #For more information, please see https://aka.ms/containercompat FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-nanoserver-sac2016 AS base WORKDIR /app EXPOSE 80 FROM mcr.microsoft.com/dotnet/core/sdk:3.1-nanoserver-sac2016 AS build WORKDIR /src COPY ["ProCodeGuide.Sample.Microservice/ProCodeGuide.Sample.Microservice.csproj", "ProCodeGuide.Sample.Microservice/"] RUN dotnet restore "ProCodeGuide.Sample.Microservice/ProCodeGuide.Sample.Microservice.csproj" COPY . . WORKDIR "/src/ProCodeGuide.Sample.Microservice" RUN dotnet build "ProCodeGuide.Sample.Microservice.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "ProCodeGuide.Sample.Microservice.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "ProCodeGuide.Sample.Microservice.dll"]
This makes the application run within a container on the Docker host. For this to work docker desktop should be installed on the windows machine.
Benefits of Microservices
- Gentle Learning Curve – As business functionality is broken into small services it allows developers to learn just the functionality of that service only.
- Better Scaling – Each service is deployed independently and so can be scaled separately.
- Lesser time to market – The application development cycle can be shorter as each service component is independent of the other.
- Fault Isolation – Failure of one service does not affect the operation of other services.
- No dependency on a single programming language & deployment model.
- Parallel & Fast development of different Services is possible as separate teams are working on different services.
Drawbacks with Microservices
- Small independent services require coordination among each other which may be not simple as compared to Monolith Application
- Managing distributed transactions across multiple services can be complex.
- There are Multiple Services/Components to Monitor.
- Testing can be little time consuming as each independent service needs to be tested before integrated testing.
- Whole deployment architecture for large applications becomes very Complex to Manage.
Microservices Architecture is about better handling a large & complex system but to achieve that it exposes its own set of complexities & implementation Challenges. Despite the disadvantages or problems, the benefits of adopting Microservices are driving factors for many companies to implement Microservices.
Best Practices
- Microservice should implement only one single functionality that should be able to deliver value.
- Each Microservice should have their own datastore and this should not be shared across services.
- Always maintain updated documentation for Microservices.
- Use containers for the deployment of services.
- DevOps & CI/CD practices
Summary
We covered what is microservice architecture and how to get started with microservices with ASP.NET Core 3.1. I have not covered one more important feature of microservice i.e automated testing. Automated unit testing is a very vast topic in itself and I will do a separate article on it.
Quick recap on .Net Core with microservices characteristics
- Microservices is a type of Architecture in which application is created as multiple small independent serviceable components
- Microservice should contain only single business functionality and should be small enough to stay focussed and big enough to deliver value.
- Each Microservice should have its own data store.
- Microservices can communicate with each other using lightweight protocol i.e. over HTTP or Advanced Message Queue Protocol (AMQP)
- Microservice can be deployed independently on a separate VM and can be scaled independently.
- There are benefits in implementing API Gateway for large & Complex Microservices based Applications.
Source code download
Here is the sample source code for Microservices with ASP.NET Core
https://github.com/procodeguide/Microservices.Sample
The most challenging part of microservices architecture is how services deal with each other. I was reading the article to reach a point to learn more about communication details, isolation, messaging mechanism, debugging, etc. Even I learned a lot in this article but I’m looking for an article about the real-world with details. I hope that you help me to find a one.
Thanks for your feedback. Sorry that you were not able to find the required details. I will try to elaborate on Communication in Microservices. I have provided very brief details on communication in Microservices in my other article Microservices Architecture
Very good and long article on Microservices in asp.net core.
Thanks!
This is a great overview of microservices in the .Net Core space and I’m sure it will be very helpful to a lot of people. Thanks for the time and effort you’ve taken to put it together. Look forward to your post on testing microservices.
Thanks a lot!
It’s pretty good overview about Implement Microservices with ASP.NET Core. It will help who wants to learn and build microservices using ASP.NET Core.
Thanks!
Very helpful!
Great job
Thanks!
Great tutorial. For the record, this tutorial was added to this awesome repository. https://github.com/ mjebrahimi/ Awesome-Microservices-NetCore
Thank You!
Brilliant documentation. Will definitely help new learners.. Thank you for your effort.
Thanks for your feedback!
Great post! Could you also elaborate on the most left blocks of your architecture diagram: gateway and identity server? How are requests passed from the gateway to the microservices? Does the gateway receive the request, and create the request to the microservice (doesn’t this slow the API down?).
Also how do you share security across those microservices? The credentials of endusers have to be passed from the gateway to the microservices, right? But maybe also security between the microservices itself? Or are they allowed to perform any request?
Thanks for your feedback, I will try and do an article on Microservices Gateway.
For security you can check my series on the same – https://procodeguide.com/programming/aspnet-core-security/
nice tutorial I learned a lot please help me with the error “visual studio container tools require Docker to be running” contanier.targets 198
Docker for windows should be installed and running on windows. Verify that it’s running.