Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Post Top Ad

Responsive Ads Here

Wednesday, September 3, 2025

Microservices with ASP.NET Core and Docker—Step-by-Step Guide

 

Introduction: Why Microservices Matter in Modern Development

Welcome to this comprehensive guide on building microservices with ASP.NET Core and Docker! In today's fast-paced software world, monolithic applications are giving way to microservices architectures. Imagine running an e-commerce platform like Amazon: instead of one massive codebase handling everything from user authentication to inventory management, you break it into independent services. This allows teams to scale, update, and deploy parts without disrupting the whole system.

This tutorial is designed for everyone—from complete beginners dipping their toes into .NET to seasoned developers tackling enterprise-scale projects. We'll start with the basics and ramp up to advanced scenarios, using a realistic e-commerce app as our example. Think of it as building "ShopifyLite": separate services for Users, Products, Orders, and Payments.

To make it interactive, I'll include exercises at the end of each module. You'll get pros, cons, alternatives, best practices, and standards throughout. We'll use real code examples (tested in .NET 8, as of 2025), and I'll explain everything step-by-step.

By the end, you'll have a deployable microservices setup. Let's dive in!

Prerequisites: Basic C# knowledge, Visual Studio or VS Code installed, Docker Desktop, and .NET SDK 8.0+.

Module 1: Understanding Microservices Architecture – From Monolith to Modular Mastery

What Are Microservices?

Microservices are an architectural style where an application is composed of small, independent services that communicate over a network. Each service focuses on a single business capability, like user management in our ShopifyLite app.

Real-Life Example: Netflix uses microservices to handle streaming, recommendations, and billing separately. If recommendations fail, streaming continues uninterrupted.

Pros:

  • Scalability: Scale individual services (e.g., Orders during Black Friday sales).
  • Fault Isolation: One service crash doesn't take down the app.
  • Tech Flexibility: Use .NET for one service, Node.js for another.
  • Faster Deployment: Independent CI/CD pipelines.

Cons:

  • Complexity: Managing inter-service communication and data consistency.
  • Overhead: More network calls mean potential latency.
  • Debugging Challenges: Distributed tracing needed for errors.
  • Operational Burden: Requires robust monitoring tools.

Alternatives:

  • Monolithic Architecture: Simpler for small apps but hard to scale (e.g., traditional ASP.NET MVC apps).
  • Serverless: Like Azure Functions—pay-per-use, but less control over infrastructure.
  • Modular Monoliths: A middle ground, like a single app with loosely coupled modules.

Best Practices and Standards:

  • Follow Domain-Driven Design (DDD): Bound services by business domains.
  • Use API-First Design: Define contracts early (e.g., OpenAPI/Swagger).
  • Adhere to 12-Factor App principles: Treat backing services as attached resources.
  • Standards: RESTful APIs, gRPC for performance, or GraphQL for flexibility.

Basic Scenario: Monolith to Microservices Migration

Start with a monolithic e-commerce app. Identify seams: Split into UsersService (handles auth), ProductsService (inventory), OrdersService (cart/checkout).

Exercise: Sketch your own app's domains on paper. What services would you create?

Advanced Scenario: Handling Distributed Transactions

In advanced setups, use Saga Pattern for transactions across services (e.g., order placement deducts inventory and processes payment). Avoid 2-Phase Commit due to failure risks.

Example Code: Basic Service Structure in ASP.NET Core Create a new ASP.NET Core Web API project:

bash
dotnet new webapi -o UsersService
cd UsersService
dotnet run

In Program.cs (minimal API style):

csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/users/{id}", (int id) => new { Id = id, Name = "John Doe" });
app.Run();

This is your first microservice endpoint. Test it at http://localhost:5000/users/1.

Module 2: ASP.NET Core Fundamentals for Microservices – Building Robust APIs

ASP.NET Core Overview

ASP.NET Core is a cross-platform framework for building web apps and APIs. It's modular, performant, and perfect for microservices due to its lightweight nature.

Real-Life Relatability: In ShopifyLite, the ProductsService uses ASP.NET Core to expose endpoints for listing items, integrating with a database like PostgreSQL.

Pros:

  • High Performance: Faster than traditional .NET Framework.
  • Cross-Platform: Runs on Windows, Linux, macOS.
  • Built-in Dependency Injection (DI): Easy to manage services.
  • Middleware Pipeline: Customizable request handling.

Cons:

  • Learning Curve: If coming from older .NET, minimal APIs feel sparse.
  • Configuration Overhead: For complex apps, setup can be verbose.
  • Not Ideal for UI-Heavy Apps: Better for APIs; use Blazor for frontends.

Alternatives:

  • Node.js with Express: Lighter but less typed safety.
  • Spring Boot (Java): Similar but ecosystem differs.
  • Go with Gin: For ultra-light services.

Best Practices and Standards:

  • Use Minimal APIs for simplicity in microservices.
  • Implement Health Checks: /health endpoint for monitoring.
  • Secure with JWT: Standards like OAuth 2.0.
  • Logging: Use Serilog or built-in ILogger, following structured logging.

Basic Scenario: Creating a CRUD API

For ProductsService:

bash
dotnet new webapi -o ProductsService

Add a model in Product.cs:

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

In Program.cs:

csharp
var products = new List<Product> { new() { Id = 1, Name = "Laptop", Price = 999.99m } };
app.MapGet("/products", () => products);
app.MapPost("/products", (Product product) => { products.Add(product); return Results.Created($"/products/{product.Id}", product); });

Run and test with Postman: POST to /products with JSON body.

Interactive Element: Modify to add validation (e.g., Name required). What happens if you send invalid data?

Advanced Scenario: Integrating Databases and EF Core

Use Entity Framework Core for ORM.

bash
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools

Create AppDbContext.cs:

csharp
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=products.db");
}

Update endpoints to use DbContext via DI:

csharp
builder.Services.AddDbContext<AppDbContext>();
app.MapGet("/products", async (AppDbContext db) => await db.Products.ToListAsync());

Migrate: dotnet ef migrations add InitialCreate; dotnet ef database update.

Multiple Examples:

  1. In-Memory DB for Testing: Swap to UseInMemoryDatabase in tests.
  2. PostgreSQL for Prod: Change connection string.

Exercise: Add error handling with try-catch and custom exceptions.

Module 3: Docker Fundamentals – Containerizing Your Services

What is Docker?

Docker packages apps into containers—lightweight, portable units with code, runtime, and dependencies. It's key for microservices consistency across dev, test, prod.

Realistic Example: In ShopifyLite, containerize UsersService so it runs identically on your laptop or AWS.

Pros:

  • Isolation: Each service in its own container.
  • Portability: "Build once, run anywhere."
  • Efficiency: Shares OS kernel, lighter than VMs.
  • Version Control: Dockerfiles as code.

Cons:

  • Learning Curve: Dockerfiles, volumes, networks.
  • Security Risks: Vulnerable images if not scanned.
  • Overhead: For tiny services, native might be faster.
  • Debugging: Harder inside containers.

Alternatives:

  • Podman: Rootless, daemonless alternative.
  • Containerd: Lower-level, used by Kubernetes.
  • Virtual Machines: Heavier but full isolation (e.g., Hyper-V).

Best Practices and Standards:

  • Use Multi-Stage Builds: Slim down images.
  • Scan Images: Tools like Trivy for vulnerabilities.
  • Follow Docker Bench for Security standards.
  • Use Official Base Images: Like mcr.microsoft.com/dotnet/aspnet.

Basic Scenario: Dockerizing a Simple ASP.NET Core App

Create Dockerfile in UsersService:

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 . .
RUN dotnet restore
RUN dotnet build -c Release -o /app/build
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "UsersService.dll"]

Build and run:

bash
docker build -t users-service .
docker run -d -p 8080:80 users-service

Access at http://localhost:8080/users/1.

Interactive: Change port mappings. What if you forget to expose?

Advanced Scenario: Multi-Container Setup with Docker Compose

For inter-service: Create docker-compose.yml:

yaml
version: '3.8'
services:
users:
image: users-service
ports:
- "8080:80"
products:
image: products-service
ports:
- "8081:80"
depends_on:
- users # Example dependency

Run: docker-compose up.

Examples:

  1. Volumes for Data: Add volumes: - dbdata:/data/db for persistence.
  2. Networks: Custom networks for isolation.

Exercise: Add a database container (e.g., postgres) and link it.

Module 4: Containerization in Depth – Optimizing for Production

Building Efficient Containers

Focus on slim images for faster pulls and security.

Pros/Cons Recap: Pros include rapid scaling; cons: image bloat if not optimized.

Best Practices: Use .dockerignore to exclude unnecessary files. Layer caching in Dockerfiles.

Example Code: Multi-Stage with Secrets Enhance Dockerfile with build secrets:

dockerfile
# syntax=docker/dockerfile:1.4
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG PAT
WORKDIR /src
COPY . .
RUN --mount=type=secret,id=pat dotnet nuget add source --name nuget.org "https://api.nuget.org/v3/index.json" --username __token__ --password $(cat /run/secrets/pat)
RUN dotnet restore
# ... rest as before

Build with: docker build --secret id=pat,src=pat.txt -t image .

Advanced: Self-Hosted Runners and CI/CD

Integrate with GitHub Actions: Dockerfile in repo, auto-build on push.

Alternatives: Kubernetes for orchestration over Compose for large scales.

Exercise: Optimize your image size—aim under 200MB.

Module 5: Inter-Service Communication – Connecting the Dots

Communication Patterns

Services talk via HTTP/REST, gRPC, or message queues.

Real-Life: In ShopifyLite, OrdersService calls ProductsService to check stock.

Pros:

  • Loose Coupling: Services evolve independently.
  • Resilience: Async messaging handles failures.

Cons:

  • Latency: Network hops add time.
  • Complexity: Service discovery needed.

Alternatives:

  • REST: Simple, human-readable.
  • gRPC: Binary, faster for internal comms.
  • Message Brokers: RabbitMQ/AMQP for async.

Best Practices: Use API Gateway (e.g., Ocelot) for routing. Circuit Breakers (Polly) for resilience. Standards: HTTP/2, Protobuf.

Basic Scenario: HTTP Communication

In OrdersService, call UsersService: Add HttpClient in Program.cs:

csharp
builder.Services.AddHttpClient("Users", client => client.BaseAddress = new Uri("http://users:80"));

Endpoint:

csharp
app.MapPost("/orders", async (IHttpClientFactory factory) =>
{
var client = factory.CreateClient("Users");
var user = await client.GetFromJsonAsync<User>("/users/1");
// Process order
return Results.Ok();
});

In Compose, use service names for DNS.

Advanced Scenario: gRPC Integration

Add gRPC to projects:

bash
dotnet add package Grpc.AspNetCore

Define proto in shared project:

proto
syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}

Server in ProductsService:

csharp
app.MapGrpcService<GreeterService>();

Client in OrdersService:

csharp
var channel = GrpcChannel.ForAddress("http://products:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "World" });

Multiple Examples:

  1. Async with Kafka: Use Confluent.Kafka package for pub/sub.
  2. GraphQL Federation: For querying multiple services as one.

Exercise: Implement retry logic with Polly.

Module 6: Advanced Microservices Topics – Scaling and Resilience

Orchestration with Kubernetes

Move beyond Compose to K8s for auto-scaling.

Pros: Self-healing, load balancing. Cons: Steep learning curve.

Example: Deploy YAML manifests for services.

Monitoring and Logging

Use Prometheus + Grafana. ELK Stack alternative.

Best Practices: Distributed tracing with Jaeger.

Security in Microservices

JWT auth, mTLS for gRPC.

Example Code: Add auth middleware.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam

Post Bottom Ad

Responsive Ads Here