Mục tiêu: Thiết kế API Gateway, hiểu Service Mesh, và implement distributed observability.
1. API Gateway
YARP (Yet Another Reverse Proxy) - Microsoft
csharp// .NET based reverse proxy, built by Microsoft
// Install: Yarp.ReverseProxy
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
app.MapReverseProxy();
json// appsettings.json
{
"ReverseProxy": {
"Routes": {
"orders-route": {
"ClusterId": "orders-cluster",
"Match": { "Path": "/api/orders/{**catch-all}" },
"Transforms": [
{ "PathRemovePrefix": "/api" }
]
},
"catalog-route": {
"ClusterId": "catalog-cluster",
"Match": { "Path": "/api/products/{**catch-all}" },
"Transforms": [
{ "PathRemovePrefix": "/api" }
]
}
},
"Clusters": {
"orders-cluster": {
"Destinations": {
"orders-service": { "Address": "https://orders-service:8080" }
},
"HealthCheck": {
"Active": {
"Enabled": true,
"Interval": "00:00:30",
"Path": "/health"
}
}
},
"catalog-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"catalog-1": { "Address": "https://catalog-1:8080" },
"catalog-2": { "Address": "https://catalog-2:8080" }
}
}
}
}
}
Gateway Patterns
csharp// 1. Rate Limiting tại Gateway
app.MapReverseProxy(proxyPipeline =>
{
proxyPipeline.UseRateLimiter();
proxyPipeline.UseAuthentication();
proxyPipeline.UseAuthorization();
});
// 2. API Composition / BFF (Backend for Frontend)
app.MapGet("/api/dashboard", async (
HttpClient ordersClient,
HttpClient catalogClient,
HttpClient analyticsClient) =>
{
// Aggregate data từ nhiều services
var ordersTask = ordersClient.GetFromJsonAsync<OrderSummary[]>("/orders/recent");
var catalogTask = catalogClient.GetFromJsonAsync<ProductSummary[]>("/products/top");
var analyticsTask = analyticsClient.GetFromJsonAsync<AnalyticsData>("/stats/today");
await Task.WhenAll(ordersTask, catalogTask, analyticsTask);
return new DashboardResponse
{
RecentOrders = ordersTask.Result!,
TopProducts = catalogTask.Result!,
TodayStats = analyticsTask.Result!
};
});
2. Service Mesh - Dapr
┌────────────────────────────────────────┐
│ Dapr Sidecar Pattern │
│ │
│ ┌───────────┐ ┌──────────────────┐ │
│ │ Your App │←──→│ Dapr Sidecar │ │
│ │ (.NET) │ │ (localhost) │ │
│ │ │ │ │ │
│ │ HTTP/gRPC │ │ - Service invoke │ │
│ │ :8080 │ │ - State store │ │
│ │ │ │ - Pub/Sub │ │
│ └───────────┘ │ - Secrets │ │
│ │ - Observability │ │
│ └──────────────────┘ │
└────────────────────────────────────────┘
csharp// Dapr .NET SDK
builder.Services.AddDaprClient();
// Service invocation (thay HTTP/gRPC direct)
app.MapGet("/api/orders", async (DaprClient dapr) =>
{
// Call "catalog-service" qua Dapr sidecar
var products = await dapr.InvokeMethodAsync<List<ProductDto>>(
HttpMethod.Get, "catalog-service", "api/products");
return products;
});
// State management
await daprClient.SaveStateAsync("statestore", "order:123", orderData);
var order = await daprClient.GetStateAsync<OrderData>("statestore", "order:123");
// Pub/Sub
await daprClient.PublishEventAsync("pubsub", "orders", new OrderCreated { ... });
3. Distributed Tracing - OpenTelemetry
csharp// Setup OpenTelemetry cho .NET
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService("order-service", serviceVersion: "1.0.0"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("MassTransit")
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://jaeger:4317");
}))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddProcessInstrumentation()
.AddOtlpExporter());
// Custom Activity (Span)
public class OrderService
{
private static readonly ActivitySource _activitySource = new("OrderService");
public async Task<Order> CreateOrderAsync(CreateOrderCommand cmd)
{
using var activity = _activitySource.StartActivity("CreateOrder");
activity?.SetTag("customer.id", cmd.CustomerId);
activity?.SetTag("items.count", cmd.Items.Count);
try
{
var order = Order.Create(cmd.CustomerId, cmd.Address);
activity?.SetTag("order.id", order.Id);
// Child span cho database operation
using var dbActivity = _activitySource.StartActivity("SaveToDatabase");
await _repository.AddAsync(order);
activity?.SetStatus(ActivityStatusCode.Ok);
return order;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}
}
Tracing Visualization
Trace: create-order (total: 250ms)
│
├── [order-service] POST /api/orders (250ms)
│ ├── [order-service] ValidateCommand (5ms)
│ ├── [catalog-service] GET /products/check-stock (45ms)
│ │ └── [catalog-service] DB Query (12ms)
│ ├── [order-service] CreateOrder (10ms)
│ ├── [order-service] SaveToDatabase (30ms)
│ └── [order-service] PublishEvent (15ms)
│ └── [rabbitmq] Publish OrderSubmitted (8ms)
│
└── [shipping-service] Handle OrderSubmitted (120ms) ← async
├── [shipping-service] CreateShipment (20ms)
└── [shipping-service] SaveToDatabase (15ms)
4. Centralized Logging - Serilog
csharp// Serilog + Seq/ELK
builder.Host.UseSerilog((context, config) =>
{
config
.ReadFrom.Configuration(context.Configuration)
.Enrich.WithProperty("ServiceName", "order-service")
.Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
.Enrich.WithCorrelationId()
.Enrich.FromLogContext()
.WriteTo.Console(new CompactJsonFormatter())
.WriteTo.Seq("http://seq:5341") // Seq server
// .WriteTo.Elasticsearch(...) // ELK stack
;
});
// Structured logging
logger.LogInformation(
"Order {OrderId} created for customer {CustomerId} with {ItemCount} items, total {Total}",
order.Id, order.CustomerId, order.Items.Count, order.Total);
// → JSON output:
// {
// "Timestamp": "2024-01-15T10:30:00Z",
// "Level": "Information",
// "MessageTemplate": "Order {OrderId} created...",
// "Properties": {
// "OrderId": "abc-123",
// "CustomerId": "cust-456",
// "ItemCount": 3,
// "Total": 150.00,
// "ServiceName": "order-service",
// "CorrelationId": "corr-789"
// }
// }
// Correlation ID middleware
public class CorrelationIdMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
?? Guid.NewGuid().ToString();
context.Response.Headers["X-Correlation-Id"] = correlationId;
using (LogContext.PushProperty("CorrelationId", correlationId))
{
await next(context);
}
}
}
5. Metrics & Alerting
csharp// Custom metrics
public class OrderMetrics
{
private static readonly Counter<long> _ordersCreated =
Meter.CreateCounter<long>("orders.created", "orders",
"Total orders created");
private static readonly Histogram<double> _orderProcessingTime =
Meter.CreateHistogram<double>("orders.processing_time", "ms",
"Order processing duration");
private static readonly UpDownCounter<int> _activeOrders =
Meter.CreateUpDownCounter<int>("orders.active", "orders",
"Currently active orders");
private static readonly Meter Meter = new("OrderService", "1.0.0");
public void OrderCreated(string category)
{
_ordersCreated.Add(1, new KeyValuePair<string, object?>("category", category));
}
public IDisposable MeasureProcessingTime()
{
var sw = Stopwatch.StartNew();
return new Timer(() => _orderProcessingTime.Record(sw.ElapsedMilliseconds));
}
}
6. Production Monitoring Setup
yaml# docker-compose.monitoring.yml
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
environment:
- COLLECTOR_OTLP_ENABLED=true
seq:
image: datalust/seq:latest
ports:
- "5341:5341" # Ingestion
- "8081:80" # UI
environment:
- ACCEPT_EULA=Y
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
Tổng Kết Session 18
| Concept | Key Takeaway |
|---|---|
| YARP | .NET reverse proxy, routing, load balancing, health checks |
| Dapr | Sidecar pattern, abstracts infrastructure |
| OpenTelemetry | Distributed tracing, metrics, cross-service correlation |
| Serilog + Seq | Structured logging, centralized search |
| Correlation ID | Track request across services |
| Metrics | Custom counters, histograms cho business metrics |
Bài tập: Setup YARP gateway routing đến 2 backend services. Add OpenTelemetry tracing + Serilog structured logging. Deploy Jaeger + Seq bằng Docker Compose.