374 lines
12 KiB
Markdown
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
|