Skip to main content

What are Microservices?

Microservices architecture is a design approach where an application is built as a collection of small, independent services that communicate over well-defined APIs. Each service is:
  • Independently deployable
  • Loosely coupled
  • Organized around business capabilities
  • Owned by a small team
  • Implemented with different technologies if needed

Microservices in AspNetRun

The AspNetRun project implements four core microservices, each representing a distinct business capability:

Service Characteristics

1. Catalog Service

Product Catalog Management

Responsibility: Manage product catalog, categories, and product informationTechnology: PostgreSQL with Marten (Document DB)Pattern: Vertical Slice ArchitectureAPI Endpoints:
  • GET /products - List all products with pagination
  • GET /products/{id} - Get product by ID
  • GET /products/category/{category} - Get products by category
  • POST /products - Create new product
  • PUT /products - Update product
  • DELETE /products/{id} - Delete product
File Structure:
Catalog.API/
├── Products/
│   ├── CreateProduct/
│   │   ├── CreateProductEndpoint.cs
│   │   ├── CreateProductHandler.cs
│   ├── GetProducts/
│   │   ├── GetProductsEndpoint.cs
│   │   ├── GetProductsHandler.cs
│   ├── UpdateProduct/
│   ├── DeleteProduct/
│   └── GetProductById/
├── Models/
│   └── Product.cs
└── Program.cs

2. Basket Service

Shopping Cart Management

Responsibility: Manage user shopping carts, apply discountsTechnology: Redis for distributed cachingPattern: Simple CQRSAPI Endpoints:
  • GET /basket/{userName} - Get user’s basket
  • POST /basket - Store/update basket
  • DELETE /basket/{userName} - Delete basket
  • POST /basket/checkout - Checkout basket
Key Features:
  • High-performance with Redis caching
  • gRPC integration with Discount service
  • Publishes integration events on checkout

3. Ordering Service

Order Processing

Responsibility: Process orders, maintain order history, manage order lifecycleTechnology: SQL Server with Entity Framework CorePattern: Clean Architecture + DDDAPI Endpoints:
  • GET /orders - List all orders with pagination
  • GET /orders/{id} - Get order by ID
  • GET /orders/customer/{customerId} - Get orders by customer
  • POST /orders - Create new order
  • PUT /orders - Update order
  • DELETE /orders/{id} - Delete order
Key Features:
  • Rich domain model with DDD patterns
  • Domain events for side effects
  • Subscribes to basket checkout events
  • Complex business logic and validation

4. Discount Service

Discount Management

Responsibility: Manage product discounts and promotional codesTechnology: SQLite with Entity Framework CoreProtocol: gRPC for high-performancegRPC Services:
  • GetDiscount - Get discount for product
  • CreateDiscount - Create new discount
  • UpdateDiscount - Update discount
  • DeleteDiscount - Delete discount

Database per Service Pattern

Polyglot Persistence: Each service uses the database technology best suited for its needs.
Each microservice has its own database, ensuring:
Services cannot directly access each other’s databases, enforcing loose coupling.
Each service can choose the most appropriate database technology:
  • Catalog: PostgreSQL with Marten for flexible document storage
  • Basket: Redis for high-performance caching
  • Ordering: SQL Server for ACID transactions
  • Discount: SQLite for lightweight storage
Each database can be scaled independently based on service needs.
Database issues in one service don’t affect others.

API Gateway Pattern

The project uses YARP (Yet Another Reverse Proxy) as the API Gateway: Location: src/ApiGateways/YarpApiGateway/appsettings.json

Benefits of API Gateway

Single Entry Point

Clients connect to one endpoint instead of multiple services

Request Routing

Routes requests to appropriate microservices

Load Balancing

Distributes traffic across service instances

Cross-Cutting Concerns

Handles authentication, CORS, rate limiting centrally

YARP Configuration Example

{
  "ReverseProxy": {
    "Routes": {
      "catalog-route": {
        "ClusterId": "catalog-cluster",
        "Match": {
          "Path": "/catalog-service/{**catch-all}"
        },
        "Transforms": [
          { "PathPattern": "{**catch-all}" }
        ]
      },
      "ordering-route": {
        "ClusterId": "ordering-cluster",
        "Match": {
          "Path": "/ordering-service/{**catch-all}"
        },
        "Transforms": [
          { "PathPattern": "{**catch-all}" }
        ]
      }
    },
    "Clusters": {
      "catalog-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "http://catalog.api:8080"
          }
        }
      },
      "ordering-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "http://ordering.api:8080"
          }
        }
      }
    }
  }
}

Service Communication

Synchronous Communication

HTTP/REST via API Gateway Most client-to-service communication uses REST APIs: gRPC for Service-to-Service Basket service calls Discount service via gRPC for performance:
src/Services/Basket/Basket.API/Basket/StoreBasket/StoreBasketHandler.cs:15-20
// Get discount from Discount.Grpc service
foreach (var item in command.Cart.Items)
{
    var coupon = await discountProto.GetDiscountAsync(
        new GetDiscountRequest { ProductName = item.ProductName }, 
        cancellationToken: cancellationToken);
    item.Price -= coupon.Amount;
}

Asynchronous Communication

Message Broker with RabbitMQ and MassTransit For eventual consistency and loose coupling, services communicate via events: Integration Event Example:
src/BuildingBlocks/BuildingBlocks.Messaging/Events/BasketCheckoutEvent.cs
namespace BuildingBlocks.Messaging.Events;

public record BasketCheckoutEvent : IntegrationEvent
{
    public string UserName { get; set; } = default!;
    public Guid CustomerId { get; set; }
    public decimal TotalPrice { get; set; }
    
    // Shipping Address
    public string FirstName { get; set; } = default!;
    public string LastName { get; set; } = default!;
    public string EmailAddress { get; set; } = default!;
    public string AddressLine { get; set; } = default!;
    public string Country { get; set; } = default!;
    public string State { get; set; } = default!;
    public string ZipCode { get; set; } = default!;
    
    // Payment
    public string CardName { get; set; } = default!;
    public string CardNumber { get; set; } = default!;
    public string Expiration { get; set; } = default!;
    public string CVV { get; set; } = default!;
    public int PaymentMethod { get; set; }
}
Event Handler in Ordering Service:
src/Services/Ordering/Ordering.Application/Orders/EventHandlers/Integration/BasketCheckoutEventHandler.cs
public class BasketCheckoutEventHandler
    (ISender sender, ILogger<BasketCheckoutEventHandler> logger)
    : IConsumer<BasketCheckoutEvent>
{
    public async Task Consume(ConsumeContext<BasketCheckoutEvent> context)
    {
        logger.LogInformation("Integration Event handled: {IntegrationEvent}", 
            context.Message.GetType().Name);

        var command = MapToCreateOrderCommand(context.Message);
        await sender.Send(command);
    }
}

Microservices Benefits

Independent Deployment

Each service can be deployed independently without affecting others

Technology Freedom

Services can use different technologies, frameworks, and databases

Scalability

Scale individual services based on demand

Fault Isolation

Failures in one service don’t bring down the entire system

Team Autonomy

Small teams can own and maintain services independently

Faster Development

Parallel development across teams

Microservices Challenges

Microservices add complexity. Consider these challenges:
Data consistency across services requires patterns like:
  • Eventual consistency
  • Saga pattern for distributed transactions
  • Event sourcing
Services need to discover and communicate with each other. Solutions:
  • DNS-based discovery
  • Service mesh (Consul, Istio)
  • API Gateway
Distributed tracing and centralized logging are essential:
  • OpenTelemetry
  • ELK Stack (Elasticsearch, Logstash, Kibana)
  • Application Insights
Inter-service communication introduces latency:
  • Use async communication where possible
  • Implement caching strategies
  • Consider service collocation for chatty services
Testing distributed systems is harder:
  • Integration tests
  • Contract testing
  • Chaos engineering

Best Practices Implemented

1. Service Independence

Each service has its own:
  • Codebase
  • Database
  • Deployment pipeline
  • Docker container

2. API Versioning

app.MapGet("/v1/products", async (ISender sender) => { })
   .WithName("GetProductsV1")
   .WithApiVersionSet("v1");

3. Health Checks

Each service exposes health check endpoints:
src/Services/Catalog/Catalog.API/Program.cs:28-29
builder.Services.AddHealthChecks()
    .AddNpgSql(builder.Configuration.GetConnectionString("Database")!);
src/Services/Catalog/Catalog.API/Program.cs:38-42
app.UseHealthChecks("/health",
    new HealthCheckOptions
    {
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });

4. Containerization

All services are containerized with Docker:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["Services/Catalog/Catalog.API/Catalog.API.csproj", "Services/Catalog/Catalog.API/"]
RUN dotnet restore
COPY . .
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", "Catalog.API.dll"]

5. Configuration Management

Services use:
  • appsettings.json for default configuration
  • Environment variables for environment-specific settings
  • Docker Compose for local development

Clean Architecture

Learn how Ordering service implements Clean Architecture

Vertical Slice

See how Catalog service uses Vertical Slice pattern

CQRS Pattern

Understand CQRS implementation across services

DDD Principles

Deep dive into Domain-Driven Design patterns

Build docs developers (and LLMs) love