DEV Community

Cover image for Building Microservices with .NET Core: A Practical Guide
Soham Galande
Soham Galande

Posted on

Building Microservices with .NET Core: A Practical Guide

Introduction:
Microservices architecture has gained widespread popularity due to its ability to build scalable, maintainable, and independently deployable services. By breaking down monolithic applications into smaller, self-contained services, teams can work on different parts of the application simultaneously, allowing for faster development cycles and easier scaling. .NET Core, with its cross-platform capabilities and lightweight architecture, is an excellent framework for building microservices. In this guide, we’ll explore how to design, build, and deploy microservices using .NET Core.

1. What is Microservices Architecture?

1.1. Microservices vs. Monoliths:

  • Monolithic Architecture: A monolith is a single, large application that handles all business logic, user interface, data access, and more within a single codebase. While this approach is simpler to start with, it becomes challenging to scale and maintain as the application grows.

  • Microservices Architecture: Microservices break the application into smaller, independent services, each responsible for a specific business capability. Each microservice can be developed, deployed, and scaled independently, allowing for greater flexibility.

1.2. Benefits of Microservices:

  • Scalability: Each service can scale independently based on its load.
  • Agility: Teams can work on different microservices simultaneously without affecting each other.
  • Resilience: If one microservice fails, others can continue functioning.
  • Technology Flexibility: Each microservice can be built using the most suitable technology for its needs.

2. Designing Microservices:

2.1. Domain-Driven Design (DDD):

A good practice for designing microservices is to follow Domain-Driven Design (DDD). DDD helps define boundaries for your microservices by identifying the core business domains and splitting them into bounded contexts. Each bounded context can become its own microservice.

2.2. Service Boundaries:

When designing microservices, ensure that each service has a single responsibility and that it encapsulates its data and business logic. Services should communicate through well-defined APIs, and dependencies between services should be kept to a minimum.

  • Example:

    • Order Service: Handles order processing and fulfillment.
    • Inventory Service: Manages inventory levels and stock.
    • Customer Service: Manages customer profiles and preferences.

2.3. Database per Service:

One key principle of microservices is that each service should manage its own data. This prevents tight coupling between services and allows each service to evolve independently.

  • Example: The OrderService and InventoryService should not share the same database. Instead, they should each have their own data store.

3. Setting Up a Microservice in .NET Core:

3.1. Creating a New Microservice Project:

To get started, create a new ASP.NET Core Web API project for your microservice:



dotnet new webapi -n OrderService
cd OrderService


Enter fullscreen mode Exit fullscreen mode

3.2. Defining the API:

Each microservice typically exposes a REST API. In the OrderService, let’s define an endpoint to create a new order:



[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    [HttpPost]
    public IActionResult CreateOrder(Order order)
    {
        // Logic to create an order
        return Ok(new { message = "Order created successfully!" });
    }
}


Enter fullscreen mode Exit fullscreen mode

3.3. Configuring the Database:

Each microservice should have its own database. For example, let’s set up a SQLite database for the OrderService.

  • Install Entity Framework Core and SQLite packages:


dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design


Enter fullscreen mode Exit fullscreen mode
  • Configure the DbContext:


public class OrderContext : DbContext
{
    public DbSet<Order> Orders { get; set; }

    public OrderContext(DbContextOptions<OrderContext> options) : base(options) { }
}

public class Order
{
    public int Id { get; set; }
    public string Product { get; set; }
    public int Quantity { get; set; }
    public DateTime OrderDate { get; set; }
}


Enter fullscreen mode Exit fullscreen mode
  • Add the DbContext in Startup.cs:


public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<OrderContext>(options =>
        options.UseSqlite(Configuration.GetConnectionString("OrderDatabase")));

    services.AddControllers();
}


Enter fullscreen mode Exit fullscreen mode
  • Configure the connection string in appsettings.json:


{
  "ConnectionStrings": {
    "OrderDatabase": "Data Source=orders.db"
  }
}


Enter fullscreen mode Exit fullscreen mode

3.4. Testing the API:
Run the OrderService microservice and use tools like Postman or cURL to test the API.



dotnet run


Enter fullscreen mode Exit fullscreen mode

4. Communication Between Microservices:

4.1. Synchronous Communication (REST/HTTP):
The most common way microservices communicate is through RESTful APIs over HTTP. For example, the OrderService might call the InventoryService to check stock levels before creating an order.



public async Task<bool> CheckInventory(int productId, int quantity)
{
    using HttpClient client = new HttpClient();
    var response = await client.GetAsync($"http://inventory-service/api/inventory/{productId}/{quantity}");
    return response.IsSuccessStatusCode;
}


Enter fullscreen mode Exit fullscreen mode

4.2. Asynchronous Communication (Message Brokers):

In some cases, asynchronous communication using a message broker (e.g., RabbitMQ, Kafka, Azure Service Bus) is preferable. This decouples services and improves reliability.

For example, after an order is created, the OrderService could publish an OrderCreated event to a message broker. Other services, like InventoryService or ShippingService, can then subscribe to this event.



public async Task PublishOrderCreated(Order order)
{
    var factory = new ConnectionFactory() { HostName = "localhost" };
    using var connection = factory.CreateConnection();
    using var channel = connection.CreateModel();

    channel.QueueDeclare(queue: "orderQueue", durable: false, exclusive: false, autoDelete: false, arguments: null);

    var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(order));
    channel.BasicPublish(exchange: "", routingKey: "orderQueue", basicProperties: null, body: body);
}


Enter fullscreen mode Exit fullscreen mode

4.3. API Gateway:

An API Gateway is a single entry point for all client requests to your microservices. It routes requests to the appropriate microservice and can handle cross-cutting concerns like authentication, logging, and rate limiting. Ocelot is a popular API Gateway in .NET Core.

  • Install Ocelot:


dotnet add package Ocelot


Enter fullscreen mode Exit fullscreen mode
  • Configure Ocelot in appsettings.json:


{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/orders",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/orders",
      "UpstreamHttpMethod": [ "GET", "POST" ]
    }
  ]
}


Enter fullscreen mode Exit fullscreen mode
  • Run Ocelot:


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

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("ocelot.json");
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}


Enter fullscreen mode Exit fullscreen mode

5. Securing Microservices:

5.1. Authentication with OAuth2 and JWT:

Use OAuth2 with JWT tokens to secure microservices. For example, a client can authenticate via an authorization server (e.g., IdentityServer4) and receive a JWT token. This token can be passed in the Authorization header for all API requests.



services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://identityserver.com";
        options.Audience = "order-service";
    });


Enter fullscreen mode Exit fullscreen mode

5.2. Service-to-Service Authentication:

Ensure that services authenticate each other to prevent unauthorized access. You can achieve this using mutual TLS or OAuth2 with client credentials.

6. Deploying Microservices:

6.1. Dockerizing Microservices:

Microservices can be packaged into containers for easy deployment. Here’s how to dockerize the OrderService:

  • Create a Dockerfile:


FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "OrderService.dll"]


Enter fullscreen mode Exit fullscreen mode
  • Build and run the Docker container:


docker build -t orderservice .
docker run -d -p 5001:80 orderservice


Enter fullscreen mode Exit fullscreen mode

6.2. Orchestrating Microservices with Kubernetes:

For production-grade deployment, use Kubernetes to orchestrate and manage your microservices. You can define Kubernetes deployments and services for each microservice.

7. Monitoring and Logging:

7.1. Distributed Logging:

Use distributed logging tools like ELK Stack (Elasticsearch, Logstash, Kibana) or Azure Monitor to track logs across multiple microservices. Centralized logging helps you monitor the behavior of the entire system and troubleshoot issues.

7.2. Monitoring with Prometheus and Grafana:

Use Prometheus to monitor metrics and Grafana for visualizing performance data. This will help ensure that your microservices are running efficiently and that you can detect bottlenecks early.

Conclusion:

Building microservices with .NET Core allows you to create scalable, maintainable, and flexible applications. By designing services around well-defined boundaries, leveraging communication patterns like REST and messaging, and deploying with Docker and Kubernetes, you can ensure that your microservices architecture is resilient and future-proof. While microservices introduce complexity, following best practices and using the right tools can make the journey much smoother.


Top comments (3)

Collapse
 
marmiksoni profile image
Marmik soni

Great guide on building microservices with .NET Core! I appreciate the clear explanations and practical examples, especially around service boundaries and deployment strategies. We could also consider including tips on handling distributed tracing for better monitoring and debugging, or exploring additional patterns like API Gateways for managing cross-cutting concerns. Overall, this was an excellent read and very insightful for those looking to deepen their understanding of microservices with .NET Core.

Thanks for sharing!

Collapse
 
soham_galande profile image
Soham Galande

Thank you for the thoughtful feedback! I'm glad you found the guide helpful. Distributed tracing and API Gateways are excellent suggestions—I’ll look into covering them in a future update. Appreciate your insights!

Collapse
 
ivy_michael_0396c1970d8e7 profile image
Ivy Michael

Building microservices with .NET Core has been a game-changer for my projects. The practical guide helped me understand Spectra Precision RD20 Wireless Remote Display how to design, develop, and deploy scalable services efficiently. Leveraging .NET Core’s performance and flexibility, I’ve been able to create robust, modular applications that are easier to maintain and enhance.