EngineeringSenior DeveloperTechnical Sessions

Session 15: Microservices Fundamentals & Communication Patterns

Mục tiêu: Hiểu microservices principles, decomposition strategies, và communication patterns (sync/async).


1. Microservices Principles

┌────────────────────────────────────────────────────────────┐
│                  Microservices Principles                    │
├────────────────────────────────────────────────────────────┤
│ 1. Single Responsibility: 1 service = 1 business capability│
│ 2. Autonomy: deploy, scale, fail independently             │
│ 3. Decentralized Data: mỗi service có DB riêng             │
│ 4. Smart Endpoints, Dumb Pipes: logic trong service        │
│ 5. Design for Failure: circuit breaker, retry, fallback    │
│ 6. Infrastructure Automation: CI/CD, containers            │
│ 7. Evolutionary Design: thay thế > refactor                │
└────────────────────────────────────────────────────────────┘

Decomposition Strategies

1. By Business Capability (phổ biến nhất):
   eCommerce → Orders, Catalog, Payment, Shipping, Notification

2. By Subdomain (DDD):
   Core: Orders (competitive advantage)
   Supporting: Catalog (cần nhưng không unique)
   Generic: Notification (có thể outsource)

3. By Verb/Use Case:
   Search Service, Checkout Service, Reporting Service

4. By Data Ownership:
   Customer Service (owns customer data)
   Product Service (owns product data)

2. Synchronous Communication

REST

csharp
// Order Service gọi Catalog Service
public class CatalogServiceClient(HttpClient httpClient) : ICatalogServiceClient
{
    public async Task<ProductDto?> GetProductAsync(Guid productId, CancellationToken ct)
    {
        var response = await httpClient.GetAsync($"/api/products/{productId}", ct);
        if (response.StatusCode == HttpStatusCode.NotFound) return null;
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<ProductDto>(ct);
    }
}

// Register với IHttpClientFactory
builder.Services.AddHttpClient<ICatalogServiceClient, CatalogServiceClient>(client =>
{
    client.BaseAddress = new Uri("https://catalog-service");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
})
.AddStandardResilienceHandler(); // .NET 8: built-in Polly

gRPC (Service-to-Service)

protobuf
// catalog.proto
syntax = "proto3";
package catalog;

service CatalogService {
  rpc GetProduct (GetProductRequest) returns (ProductResponse);
  rpc GetProducts (GetProductsRequest) returns (stream ProductResponse);
  rpc CheckStock (CheckStockRequest) returns (StockResponse);
}

message GetProductRequest { string product_id = 1; }
message ProductResponse {
  string id = 1;
  string name = 2;
  double price = 3;
  int32 stock = 4;
}
csharp
// .NET gRPC Server
public class CatalogGrpcService(CatalogDbContext db) : CatalogService.CatalogServiceBase
{
    public override async Task<ProductResponse> GetProduct(
        GetProductRequest request, ServerCallContext context)
    {
        var product = await db.Products.FindAsync(Guid.Parse(request.ProductId));
        if (product is null)
            throw new RpcException(new Status(StatusCode.NotFound, "Product not found"));

        return new ProductResponse
        {
            Id = product.Id.ToString(),
            Name = product.Name,
            Price = (double)product.Price,
            Stock = product.Stock
        };
    }
}

// .NET gRPC Client
builder.Services.AddGrpcClient<CatalogService.CatalogServiceClient>(options =>
{
    options.Address = new Uri("https://catalog-service:5001");
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
    ServerCertificateCustomValidationCallback = (_, _, _, _) => true // Dev only!
});

REST vs gRPC

FeatureRESTgRPC
ProtocolHTTP/1.1, JSONHTTP/2, Protobuf
Performance~100μs overhead~10μs overhead
StreamingSSE (one-way)Bidirectional
Browser✅ Native⚠️ grpc-web
ContractOpenAPI (loose).proto (strict)
Use casePublic APIsService-to-service

3. Asynchronous Communication

Message Queue (Point-to-Point)

Producer → ┌────────────┐ → Consumer
           │   Queue    │
           │ ──────────│    1 message → 1 consumer
           └────────────┘

Pub/Sub (One-to-Many)

Publisher → ┌────────────┐ → Subscriber A
            │   Topic    │ → Subscriber B
            │            │ → Subscriber C
            └────────────┘
              1 message → N subscribers

Event Streaming (Kafka-style)

Producer → ┌────────────────────────────────┐
           │   Topic Partition              │
           │ [msg1][msg2][msg3][msg4][msg5] │
           └──┬─────────────────────────────┘
              │
    Consumer Group A: reads from offset 3
    Consumer Group B: reads from offset 1 (different pace)
    → Messages RETAINED (replay possible)

4. Resilience Patterns (Polly / .NET 8 Resilience)

Circuit Breaker

csharp
// .NET 8 built-in resilience
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>(client =>
{
    client.BaseAddress = new Uri("https://catalog-service");
})
.AddResilienceHandler("catalog", builder =>
{
    // Retry: 3 attempts with exponential backoff
    builder.AddRetry(new HttpRetryStrategyOptions
    {
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromMilliseconds(500),
        BackoffType = DelayBackoffType.Exponential,
        ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
            .HandleResult(r => r.StatusCode == HttpStatusCode.ServiceUnavailable)
            .Handle<HttpRequestException>()
    });

    // Circuit Breaker: open after 5 failures
    builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        SamplingDuration = TimeSpan.FromSeconds(30),
        FailureRatio = 0.5,
        MinimumThroughput = 5,
        BreakDuration = TimeSpan.FromSeconds(30)
    });

    // Timeout
    builder.AddTimeout(TimeSpan.FromSeconds(5));
});

// Circuit Breaker states:
// CLOSED  → requests flowing normally
// OPEN    → requests immediately rejected (fail fast)
// HALF-OPEN → allow 1 test request to check if service recovered

Health Checks

csharp
// Producer service
builder.Services.AddHealthChecks()
    .AddSqlServer(connectionString, name: "database")
    .AddRedis(redisConnection, name: "cache")
    .AddRabbitMQ(rabbitConnection, name: "messaging")
    .AddUrlGroup(new Uri("https://catalog-service/health"), name: "catalog-service");

app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false // Chỉ check app running
});

5. Service Discovery

csharp
// Option 1: DNS-based (Kubernetes / Docker Compose)
// Service name = DNS name
builder.Services.AddHttpClient("catalog", client =>
{
    client.BaseAddress = new Uri("http://catalog-service:8080"); // DNS resolve
});

// Option 2: Azure Service Discovery (.NET 9 / Aspire)
builder.Services.AddServiceDiscovery();
builder.Services.AddHttpClient<ICatalogClient>(client =>
{
    client.BaseAddress = new Uri("https+http://catalog"); // Resolved by Aspire
});

// Option 3: Configuration-based
builder.Services.AddHttpClient<ICatalogClient>(client =>
{
    client.BaseAddress = new Uri(
        builder.Configuration["Services:Catalog:BaseUrl"]!);
});

6. Ví Dụ: Logistics Tracking System

┌──────────┐    ┌───────────┐    ┌──────────────┐
│ Order    │───→│ Routing   │───→│ Tracking     │
│ Service  │    │ Service   │    │ Service      │
│          │    │           │    │              │
│ Tạo đơn │    │ Tính route│    │ GPS realtime │
│ hàng     │    │ tối ưu    │    │ tracking     │
└──────┬───┘    └─────┬─────┘    └──────┬───────┘
       │              │                  │
       │    Events    │     Events      │
       └──────────────┴─────────────────┘
                      │
              ┌───────▼────────┐
              │ Notification   │
              │ Service        │
              │                │
              │ SMS/Email/Push │
              └────────────────┘

Communication:
  Order → Routing: gRPC (sync, cần response)
  Order → Notification: Event (async, fire-and-forget)
  Tracking → All: Event Streaming (continuous GPS data)

Tổng Kết Session 15

ConceptKey Takeaway
DecompositionBy business capability (phổ biến nhất)
Sync: RESTPublic APIs, simple CRUD
Sync: gRPCService-to-service, high performance
Async: QueuePoint-to-point, guaranteed delivery
Async: Pub/SubOne-to-many, loose coupling
Circuit BreakerFail fast khi downstream unavailable
Health ChecksLiveness + readiness + dependency checks

Bài tập: Tạo 2 .NET microservices (Orders, Catalog) communicate qua gRPC. Add circuit breaker + retry. Implement health checks cho cả 2 services.