Migrate
This commit is contained in:
373
microservices/.agent/skills/inter-service-communication/SKILL.md
Normal file
373
microservices/.agent/skills/inter-service-communication/SKILL.md
Normal file
@@ -0,0 +1,373 @@
|
||||
---
|
||||
name: inter-service-communication
|
||||
description: Giao tiếp liên dịch vụ. Use for Event Bus (RabbitMQ), Integration Events, HTTP Client với Polly, và gRPC patterns.
|
||||
compatibility: ".NET 8+, MassTransit, RabbitMQ, Polly, gRPC"
|
||||
metadata:
|
||||
author: Velik Ho
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# Inter-Service Communication / Giao Tiếp Liên Dịch Vụ
|
||||
|
||||
Patterns giao tiếp giữa các microservices trong GoodGo.
|
||||
|
||||
## When to Use This Skill / Khi Nào Sử Dụng
|
||||
|
||||
Use this skill when:
|
||||
- Publishing domain events to other services / Publish domain events đến services khác
|
||||
- Consuming integration events / Consume integration events
|
||||
- Making HTTP calls to other services / Gọi HTTP đến services khác
|
||||
- Implementing resilient communication / Triển khai giao tiếp có khả năng phục hồi
|
||||
|
||||
## Core Concepts / Khái Niệm Cốt Lõi
|
||||
|
||||
### Communication Patterns / Các Mẫu Giao Tiếp
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ COMMUNICATION PATTERNS │
|
||||
├───────────────────────────────┬─────────────────────────────┤
|
||||
│ SYNCHRONOUS │ ASYNCHRONOUS │
|
||||
│ (Request/Response) │ (Fire & Forget) │
|
||||
├───────────────────────────────┼─────────────────────────────┤
|
||||
│ • HTTP/REST APIs │ • Message Queue (RabbitMQ) │
|
||||
│ • gRPC │ • Event Bus │
|
||||
│ • Direct service calls │ • Pub/Sub │
|
||||
├───────────────────────────────┼─────────────────────────────┤
|
||||
│ Use for: Queries, UI │ Use for: Commands, Events │
|
||||
│ Pro: Immediate response │ Pro: Decoupled, resilient │
|
||||
│ Con: Tight coupling │ Con: Eventual consistency │
|
||||
└───────────────────────────────┴─────────────────────────────┘
|
||||
```
|
||||
|
||||
### Event Types / Các Loại Event
|
||||
|
||||
| Type | Scope | Transport | Example |
|
||||
|------|-------|-----------|---------|
|
||||
| **Domain Event** | Within aggregate | In-memory | OrderItemAdded |
|
||||
| **Integration Event** | Between services | Message broker | OrderCreatedIntegrationEvent |
|
||||
|
||||
### Anti-patterns to Avoid / Anti-patterns Cần Tránh
|
||||
|
||||
```
|
||||
❌ Synchronous HTTP Chains (Anti-pattern)
|
||||
|
||||
Service A ──HTTP──▶ Service B ──HTTP──▶ Service C ──HTTP──▶ DB
|
||||
│ │
|
||||
└── Single point of failure ──┘
|
||||
|
||||
✅ Async Event-Driven (Recommended)
|
||||
|
||||
Service A ──Publish──▶ Event Bus ──Subscribe──▶ Service B
|
||||
│
|
||||
└────Subscribe──▶ Service C
|
||||
```
|
||||
|
||||
## Key Patterns / Mẫu Chính
|
||||
|
||||
### Integration Event Definition
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// EN: Base interface for integration events.
|
||||
/// VI: Interface cơ sở cho integration events.
|
||||
/// </summary>
|
||||
public interface IIntegrationEvent
|
||||
{
|
||||
Guid Id { get; }
|
||||
DateTime OccurredOn { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Event when order is created.
|
||||
/// VI: Event khi order được tạo.
|
||||
/// </summary>
|
||||
public record OrderCreatedIntegrationEvent : IIntegrationEvent
|
||||
{
|
||||
public Guid Id { get; init; } = Guid.NewGuid();
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
|
||||
public Guid OrderId { get; init; }
|
||||
public string UserId { get; init; } = default!;
|
||||
public decimal TotalAmount { get; init; }
|
||||
public IReadOnlyList<OrderItemInfo> Items { get; init; } = Array.Empty<OrderItemInfo>();
|
||||
}
|
||||
|
||||
public record OrderItemInfo(Guid ProductId, int Quantity, decimal UnitPrice);
|
||||
```
|
||||
|
||||
### Event Publisher with MassTransit
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// EN: Publish integration events via MassTransit.
|
||||
/// VI: Publish integration events qua MassTransit.
|
||||
/// </summary>
|
||||
public class IntegrationEventPublisher : IIntegrationEventPublisher
|
||||
{
|
||||
private readonly IPublishEndpoint _publishEndpoint;
|
||||
private readonly ILogger<IntegrationEventPublisher> _logger;
|
||||
|
||||
public IntegrationEventPublisher(
|
||||
IPublishEndpoint publishEndpoint,
|
||||
ILogger<IntegrationEventPublisher> logger)
|
||||
{
|
||||
_publishEndpoint = publishEndpoint;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task PublishAsync<T>(T @event, CancellationToken ct = default)
|
||||
where T : class, IIntegrationEvent
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Publishing event {EventType} with Id {EventId}",
|
||||
typeof(T).Name,
|
||||
@event.Id);
|
||||
|
||||
await _publishEndpoint.Publish(@event, ct);
|
||||
}
|
||||
}
|
||||
|
||||
// EN: Usage in command handler / VI: Sử dụng trong command handler
|
||||
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderResult>
|
||||
{
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly IIntegrationEventPublisher _eventPublisher;
|
||||
|
||||
public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
|
||||
{
|
||||
var order = new Order(request.UserId, request.ShippingAddress);
|
||||
// ... add items
|
||||
|
||||
await _orderRepository.AddAsync(order, ct);
|
||||
await _orderRepository.UnitOfWork.SaveChangesAsync(ct);
|
||||
|
||||
// EN: Publish integration event
|
||||
// VI: Publish integration event
|
||||
await _eventPublisher.PublishAsync(new OrderCreatedIntegrationEvent
|
||||
{
|
||||
OrderId = order.Id,
|
||||
UserId = order.UserId,
|
||||
TotalAmount = order.TotalAmount,
|
||||
Items = order.OrderItems.Select(i => new OrderItemInfo(
|
||||
i.ProductId, i.Quantity, i.UnitPrice)).ToList()
|
||||
}, ct);
|
||||
|
||||
return new OrderResult(order.Id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event Consumer
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// EN: Consumer for OrderCreatedIntegrationEvent in Inventory Service.
|
||||
/// VI: Consumer cho OrderCreatedIntegrationEvent trong Inventory Service.
|
||||
/// </summary>
|
||||
public class OrderCreatedConsumer : IConsumer<OrderCreatedIntegrationEvent>
|
||||
{
|
||||
private readonly IInventoryService _inventoryService;
|
||||
private readonly ILogger<OrderCreatedConsumer> _logger;
|
||||
|
||||
public OrderCreatedConsumer(
|
||||
IInventoryService inventoryService,
|
||||
ILogger<OrderCreatedConsumer> logger)
|
||||
{
|
||||
_inventoryService = inventoryService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<OrderCreatedIntegrationEvent> context)
|
||||
{
|
||||
var @event = context.Message;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Received OrderCreatedEvent: {OrderId}",
|
||||
@event.OrderId);
|
||||
|
||||
try
|
||||
{
|
||||
// EN: Reserve inventory for order items
|
||||
// VI: Đặt trước inventory cho order items
|
||||
foreach (var item in @event.Items)
|
||||
{
|
||||
await _inventoryService.ReserveAsync(
|
||||
item.ProductId,
|
||||
item.Quantity,
|
||||
@event.OrderId,
|
||||
context.CancellationToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Inventory reserved for Order: {OrderId}",
|
||||
@event.OrderId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Failed to reserve inventory for Order: {OrderId}",
|
||||
@event.OrderId);
|
||||
throw; // EN: Will retry based on retry policy
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MassTransit Configuration
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// EN: Configure MassTransit with RabbitMQ.
|
||||
/// VI: Cấu hình MassTransit với RabbitMQ.
|
||||
/// </summary>
|
||||
|
||||
// Program.cs
|
||||
builder.Services.AddMassTransit(x =>
|
||||
{
|
||||
// EN: Register consumers
|
||||
// VI: Đăng ký consumers
|
||||
x.AddConsumer<OrderCreatedConsumer>();
|
||||
x.AddConsumer<ProductPriceChangedConsumer>();
|
||||
|
||||
x.UsingRabbitMq((context, cfg) =>
|
||||
{
|
||||
cfg.Host(builder.Configuration["RabbitMQ:Host"], "/", h =>
|
||||
{
|
||||
h.Username(builder.Configuration["RabbitMQ:Username"]!);
|
||||
h.Password(builder.Configuration["RabbitMQ:Password"]!);
|
||||
});
|
||||
|
||||
// EN: Configure retry policy
|
||||
// VI: Cấu hình retry policy
|
||||
cfg.UseMessageRetry(r => r.Intervals(
|
||||
TimeSpan.FromSeconds(1),
|
||||
TimeSpan.FromSeconds(5),
|
||||
TimeSpan.FromSeconds(15)));
|
||||
|
||||
// EN: Configure endpoints
|
||||
// VI: Cấu hình endpoints
|
||||
cfg.ConfigureEndpoints(context);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Resilient HTTP Client with Polly
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// EN: Configure resilient HTTP client for inter-service calls.
|
||||
/// VI: Cấu hình HTTP client có khả năng phục hồi cho gọi liên dịch vụ.
|
||||
/// </summary>
|
||||
|
||||
// Program.cs
|
||||
builder.Services.AddHttpClient<IIamServiceClient, IamServiceClient>(client =>
|
||||
{
|
||||
client.BaseAddress = new Uri(builder.Configuration["Services:Iam:BaseUrl"]!);
|
||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
})
|
||||
.AddResilienceHandler("iam-service", builder =>
|
||||
{
|
||||
// EN: Retry with exponential backoff
|
||||
builder.AddRetry(new HttpRetryStrategyOptions
|
||||
{
|
||||
MaxRetryAttempts = 3,
|
||||
Delay = TimeSpan.FromMilliseconds(500),
|
||||
BackoffType = DelayBackoffType.Exponential,
|
||||
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
|
||||
.HandleResult(r => (int)r.StatusCode >= 500)
|
||||
.Handle<HttpRequestException>()
|
||||
});
|
||||
|
||||
// EN: Circuit breaker
|
||||
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
|
||||
{
|
||||
FailureRatio = 0.5,
|
||||
SamplingDuration = TimeSpan.FromSeconds(30),
|
||||
BreakDuration = TimeSpan.FromSeconds(30)
|
||||
});
|
||||
|
||||
// EN: Timeout
|
||||
builder.AddTimeout(TimeSpan.FromSeconds(10));
|
||||
});
|
||||
```
|
||||
|
||||
## Common Mistakes / Lỗi Thường Gặp
|
||||
|
||||
### 1. Synchronous HTTP Chains
|
||||
|
||||
```csharp
|
||||
// ❌ BAD: Chain of HTTP calls
|
||||
public async Task ProcessOrder(Order order)
|
||||
{
|
||||
await _inventoryService.ReserveAsync(order); // HTTP
|
||||
await _paymentService.ChargeAsync(order); // HTTP
|
||||
await _shippingService.CreateShipmentAsync(order); // HTTP
|
||||
}
|
||||
|
||||
// ✅ GOOD: Event-driven, async
|
||||
public async Task ProcessOrder(Order order)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(new OrderCreatedEvent(order));
|
||||
// Each service subscribes and processes independently
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Missing Idempotency
|
||||
|
||||
```csharp
|
||||
// ❌ BAD: No idempotency check
|
||||
public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
|
||||
{
|
||||
await _service.CreateShipment(context.Message.OrderId);
|
||||
}
|
||||
|
||||
// ✅ GOOD: Idempotent processing
|
||||
public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
|
||||
{
|
||||
var eventId = context.Message.Id;
|
||||
if (await _processedEvents.ExistsAsync(eventId))
|
||||
return; // Already processed
|
||||
|
||||
await _service.CreateShipment(context.Message.OrderId);
|
||||
await _processedEvents.MarkProcessedAsync(eventId);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. No Retry Policy
|
||||
|
||||
```csharp
|
||||
// ❌ BAD: Raw HTTP call without resilience
|
||||
var response = await _httpClient.GetAsync("/api/users/123");
|
||||
|
||||
// ✅ GOOD: Use resilient HTTP client (configured at DI level)
|
||||
// Retries, circuit breaker, and timeout are handled automatically
|
||||
var response = await _resilientClient.GetAsync("/api/users/123");
|
||||
```
|
||||
|
||||
## Quick Reference / Tham Chiếu Nhanh
|
||||
|
||||
### When to Use What
|
||||
|
||||
| Scenario | Pattern | Transport |
|
||||
|----------|---------|-----------|
|
||||
| Get data for UI | Sync HTTP | REST/gRPC |
|
||||
| Notify state change | Async Event | RabbitMQ |
|
||||
| Long-running process | Async Event | RabbitMQ |
|
||||
| Real-time updates | Pub/Sub | RabbitMQ/SignalR |
|
||||
|
||||
### Message Broker vs Direct Call
|
||||
|
||||
| Aspect | Message Broker | Direct HTTP |
|
||||
|--------|----------------|-------------|
|
||||
| Coupling | Loose | Tight |
|
||||
| Failure handling | Built-in retry | Manual |
|
||||
| Scalability | High | Medium |
|
||||
| Debugging | Harder | Easier |
|
||||
|
||||
## Resources / Tài Nguyên
|
||||
|
||||
- [Detailed Examples](./references/REFERENCE.md) - Full code examples
|
||||
- [Error Handling](../error-handling-patterns/SKILL.md) - Polly patterns
|
||||
- [CQRS MediatR](../cqrs-mediatr/SKILL.md) - Domain events
|
||||
- [Docker Traefik](../docker-traefik/SKILL.md) - RabbitMQ container
|
||||
Reference in New Issue
Block a user