Introduction
Modern applications are becoming increasingly large, complex, and interconnected. In such scenarios, microservices architecture offers a scalable, maintainable, and flexible approach compared to traditional monolithic systems.
In this article, we’ll explore how to design and implement microservices using ASP.NET Core, covering step-by-step setup, communication, data management, and deployment strategies.
Why Microservices?
Before diving into the implementation, let’s understand why microservices are important:
Scalability: Each microservice can be scaled independently based on load.
Isolation: Fault in one service doesn’t affect others.
Technology Freedom: Different teams can use different technologies.
Faster Deployment: Smaller codebases mean faster CI/CD cycles.
Maintainability: Each service is easier to understand and modify.
Microservices vs Monolithic Architecture
| Feature | Monolithic | Microservices |
|---|
| Deployment | Single deployment for the whole app | Independent deployment for each service |
| Scalability | Scale entire app | Scale only needed services |
| Codebase | Single large project | Multiple smaller services |
| Fault Isolation | Harder | Easier |
| Database | Shared | Separate per service |
Step 1: Designing the Microservice Architecture
A typical ASP.NET Core microservices setup may look like this:
API Gateway – Entry point that routes requests to the correct microservice.
Microservices – Each handling a specific domain (e.g., Orders, Inventory, Users).
Database per service – Each microservice manages its own schema.
Communication – Services communicate via REST APIs or message queues.
Configuration & Discovery – Managed through tools like Consul or Ocelot.
Technical Workflow (Flowchart)
+---------------------+
| Client App |
+---------+-----------+
|
v
+---------------------+
| API Gateway |
+---------+-----------+
|
+---------------+---------------+
| |
v v
+--------+ +------------+
| Orders | | Inventory |
|Service | | Service |
+--------+ +------------+
| |
v v
+---------+ +-----------+
| SQL DB | | SQL DB |
+---------+ +-----------+
Step 2: Creating a Solution Structure
Create a Visual Studio solution (or use CLI):
dotnet new sln -n MicroservicesDemo
mkdir Services
cd Services
dotnet new webapi -n OrderService
dotnet new webapi -n InventoryService
dotnet new webapi -n UserService
dotnet new webapi -n APIGateway
Then, add each project to the solution:
dotnet sln add ./Services/OrderService/OrderService.csproj
dotnet sln add ./Services/InventoryService/InventoryService.csproj
dotnet sln add ./Services/UserService/UserService.csproj
dotnet sln add ./Services/APIGateway/APIGateway.csproj
Step 3: Implementing a Microservice (Example: OrderService)
Startup.cs or Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("OrderDB")));
var app = builder.Build();
app.MapControllers();
app.Run();
OrderController.cs
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
private readonly OrderDbContext _context;
public OrderController(OrderDbContext context)
{
_context = context;
}
[HttpGet]
public IActionResult GetOrders() => Ok(_context.Orders.ToList());
[HttpPost]
public IActionResult CreateOrder([FromBody] Order order)
{
_context.Orders.Add(order);
_context.SaveChanges();
return Ok(order);
}
}
Order Model
public class Order
{
public int Id { get; set; }
public string CustomerName { get; set; } = string.Empty;
public decimal TotalAmount { get; set; }
public DateTime OrderDate { get; set; } = DateTime.UtcNow;
}
Step 4: Adding the API Gateway
Use Ocelot as a simple API Gateway.
Install Ocelot:
dotnet add package Ocelot
Add a configuration file ocelot.json:
{"Routes": [
{
"DownstreamPathTemplate": "/api/order",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{ "Host": "localhost", "Port": 5001 }
],
"UpstreamPathTemplate": "/order",
"UpstreamHttpMethod": [ "GET", "POST" ]
}],"GlobalConfiguration": { }}
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
builder.Services.AddOcelot();
var app = builder.Build();
await app.UseOcelot();
app.Run();
Now, hitting https://localhost:7000/order routes to the OrderService.
Step 5: Service-to-Service Communication
To communicate between services, you can use HttpClient or message queues like RabbitMQ.
Example using HttpClient (InventoryService → OrderService):
public class OrderApiClient
{
private readonly HttpClient _client;
public OrderApiClient(HttpClient client)
{
_client = client;
_client.BaseAddress = new Uri("https://localhost:5001/api/order/");
}
public async Task<List<Order>> GetOrdersAsync()
{
var response = await _client.GetAsync("");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<List<Order>>();
}
}
Step 6: Database Per Service
Each service has its own SQL Server database.
Example
This separation avoids schema conflicts and allows independent scaling.
Step 7: Logging and Monitoring
For centralized monitoring:
Use Serilog + Seq or Elastic Stack (ELK)
Log all requests/responses per service
Include correlation IDs for tracking cross-service requests
Example: Serilog Configuration
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
Step 8: CI/CD Pipeline (Jenkins Example)
Each microservice is deployed independently.
Jenkinsfile Example:
pipeline {
agent any
stages {
stage('Build') {
steps {
bat 'dotnet build ./Services/OrderService/OrderService.csproj -c Release'
}
}
stage('Publish') {
steps {
bat 'dotnet publish ./Services/OrderService -o ./publish/OrderService'
}
}
stage('Deploy') {
steps {
bat 'xcopy /Y /E ./publish/OrderService C:\\inetpub\\OrderService'
bat 'iisreset'
}
}
}
}
Step 9: Scaling and Load Balancing
To achieve zero downtime and scalability:
Host each service under IIS or Docker.
Use Application Request Routing (ARR) for load balancing.
Use horizontal scaling (multiple instances per service).
Step 10: Versioning and Backward Compatibility
Always maintain backward compatibility:
Best Practices
Keep each service small and focused.
Never share databases between services.
Implement retry logic for inter-service calls.
Secure communication with JWT tokens or OAuth2.
Automate builds and deployments.
Use centralized logging and distributed tracing (e.g., OpenTelemetry).
Real-World Example Scenario
Imagine an E-Commerce platform:
OrderService handles orders.
InventoryService manages stock.
UserService manages users and authentication.
PaymentService processes transactions.
API Gateway routes requests to respective services.
Each service can be deployed, scaled, or updated independently.
Summary
In this article, we explored how to implement microservices using ASP.NET Core step-by-step.
We learned about architecture design, API Gateway setup, service communication, database per service, CI/CD pipeline, and best practices.
Microservices give you freedom, scalability, and resilience, but they also bring complexity — so use them wisely where they truly fit your system’s needs.