In today’s fast-paced software development landscape, building scalable, maintainable, and resilient applications is more critical than ever. One architectural style that has gained immense popularity is the microservices pattern. This blog post aims to provide a comprehensive understanding of microservices, illustrating key concepts with example code in .NET 8 and C#.

What Are Microservices?

Microservices is an architectural style that structures an application as a collection of small, autonomous services modeled around a business domain. Each service is self-contained, independently deployable, and communicates with other services using lightweight protocols, typically HTTP.

Key Concepts of Microservices

  1. Single Responsibility Principle: Each microservice is responsible for a specific piece of functionality.
  2. Decentralized Data Management: Each service manages its own database.
  3. Independent Deployment: Services can be deployed independently of each other.
  4. Inter-Service Communication: Services communicate through APIs, often using REST or messaging queues.

Implementing Microservices in .NET 8 and C

Setting Up the Environment

First, ensure you have the .NET 8 SDK installed. You can create a new solution with multiple projects representing different microservices.

dotnet new sln -n MicroservicesDemo
mkdir Services
cd Services
dotnet new webapi -n OrderService
dotnet new webapi -n ProductService
dotnet new webapi -n CustomerService

Add the projects to the solution:

cd ..
dotnet sln add Services/OrderService/OrderService.csproj
dotnet sln add Services/ProductService/ProductService.csproj
dotnet sln add Services/CustomerService/CustomerService.csproj

Example Microservice: ProductService

Let’s start with the ProductService. This service will manage product-related operations.

ProductService/Program.cs:

using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ProductDbContext>(opt => opt.UseInMemoryDatabase("ProductDb"));
builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();
app.Run();

public class ProductDbContext : DbContext {
    public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options) { }
    public DbSet<Product> Products { get; set; }
    }
    
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }

ProductService/Controllers/ProductController.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly ProductDbContext _context;

    public ProductController(ProductDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        return await _context.Products.ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return product;
    }

    [HttpPost]
    public async Task<ActionResult<Product>> PostProduct(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }
}

Example Microservice: OrderService

Next, let’s create the OrderService.

OrderService/Program.cs:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<OrderDbContext>(opt => opt.UseInMemoryDatabase("OrderDb"));
builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();
app.Run();

public class OrderDbContext : DbContext
{
    public OrderDbContext(DbContextOptions<OrderDbContext> options) : base(options) { }
    public DbSet<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal TotalPrice { get; set; }
}

OrderService/Controllers/OrderController.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly OrderDbContext _context;

    public OrderController(OrderDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Order>>> GetOrders()
    {
        return await _context.Orders.ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetOrder(int id)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null)
        {
            return NotFound();
        }
        return order;
    }

    [HttpPost]
    public async Task<ActionResult<Order>> PostOrder(Order order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }
}

Inter-Service Communication

For inter-service communication, you can use HTTP or messaging queues. Here’s an example of OrderService calling ProductService to get product details:

OrderService/Controllers/OrderController.cs (modified):

[HttpPost]
public async Task<ActionResult<Order>> PostOrder(Order order)
{
    using var httpClient = new HttpClient();
    var productResponse = await httpClient.GetAsync($"http://localhost:5001/api/Product/{order.ProductId}");

    if (!productResponse.IsSuccessStatusCode)
    {
        return BadRequest("Invalid Product ID");
    }

    var product = await productResponse.Content.ReadFromJsonAsync<Product>();
    order.TotalPrice = order.Quantity * product.Price;

    _context.Orders.Add(order);
    await _context.SaveChangesAsync();
    return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Deploying Microservices

Each microservice can be deployed independently. You can use Docker to containerize the services and Kubernetes to manage the deployment. Here’s a basic Dockerfile for ProductService:

ProductService/Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["ProductService/ProductService.csproj", "ProductService/"]
RUN dotnet restore "ProductService/ProductService.csproj"
COPY . .
WORKDIR "/src/ProductService"
RUN dotnet build "ProductService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ProductService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProductService.dll"]

Summary

The microservices pattern offers several advantages, including:

  • Scalability: Each service can be scaled independently based on demand.
  • Resilience: Failure in one service doesn’t necessarily bring down the entire system.
  • Flexibility: Different technologies can be used for different services.

However, it also comes with its challenges:

  • Complexity: Managing multiple services can be complex.
  • Inter-Service Communication: Requires careful handling to ensure reliable communication.
  • Data Consistency: Ensuring data consistency across services can be difficult.

In conclusion, the microservices pattern provides a powerful way to build scalable, resilient, and flexible applications. By breaking down applications into smaller, manageable services, developers can enhance maintainability and agility, enabling rapid development and deployment of new features.

Understanding the Microservices Pattern

Johannes Rest


.NET Architekt und Entwickler


Beitragsnavigation


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert