Files
pos-system/microservices/.agent/skills/inter-service-communication/SKILL.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

374 lines
12 KiB
Markdown

---
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