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

521 lines
15 KiB
Markdown

# Inter-Service Communication - Detailed Reference
Detailed code examples cho giao tiếp liên dịch vụ trong GoodGo.
## Table of Contents
1. [MassTransit Setup](#masstransit-setup)
2. [Integration Events](#integration-events)
3. [Event Consumers](#event-consumers)
4. [HTTP Client Patterns](#http-client-patterns)
5. [Outbox Pattern](#outbox-pattern)
6. [gRPC Setup](#grpc-setup)
---
## MassTransit Setup
### Complete MassTransit Configuration
```csharp
/// <summary>
/// EN: Configure MassTransit with RabbitMQ for production.
/// VI: Cấu hình MassTransit với RabbitMQ cho production.
/// </summary>
// Program.cs
builder.Services.AddMassTransit(x =>
{
// EN: Register all consumers from assembly
// VI: Đăng ký tất cả consumers từ assembly
x.AddConsumers(typeof(Program).Assembly);
// EN: Configure saga if needed
// x.AddSagaStateMachine<OrderStateMachine, OrderState>()
// .EntityFrameworkRepository(...);
x.UsingRabbitMq((context, cfg) =>
{
var rabbitConfig = builder.Configuration.GetSection("RabbitMQ");
cfg.Host(rabbitConfig["Host"], rabbitConfig["VirtualHost"] ?? "/", h =>
{
h.Username(rabbitConfig["Username"]!);
h.Password(rabbitConfig["Password"]!);
});
// EN: Global retry policy
// VI: Retry policy toàn cục
cfg.UseMessageRetry(r =>
{
r.Incremental(3, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
r.Ignore<ValidationException>();
});
// EN: Dead letter queue for failed messages
// VI: Dead letter queue cho messages thất bại
cfg.UseDelayedRedelivery(r => r.Intervals(
TimeSpan.FromMinutes(5),
TimeSpan.FromMinutes(15),
TimeSpan.FromMinutes(30)));
// EN: Configure endpoints
// VI: Cấu hình endpoints
cfg.ConfigureEndpoints(context, new KebabCaseEndpointNameFormatter("goodgo", false));
});
});
```
### appsettings.json
```json
{
"RabbitMQ": {
"Host": "localhost",
"VirtualHost": "/",
"Username": "guest",
"Password": "guest"
}
}
```
---
## Integration Events
### Event Definitions
```csharp
/// <summary>
/// EN: Integration event base record.
/// VI: Integration event record cơ sở.
/// </summary>
public abstract record IntegrationEvent
{
public Guid Id { get; init; } = Guid.NewGuid();
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
public string CorrelationId { get; init; } = string.Empty;
}
/// <summary>
/// EN: Event when order status changes.
/// VI: Event khi trạng thái order thay đổi.
/// </summary>
public record OrderStatusChangedIntegrationEvent : IntegrationEvent
{
public Guid OrderId { get; init; }
public string PreviousStatus { get; init; } = default!;
public string NewStatus { get; init; } = default!;
public string UserId { get; init; } = default!;
}
/// <summary>
/// EN: Event when product price changes.
/// VI: Event khi giá sản phẩm thay đổi.
/// </summary>
public record ProductPriceChangedIntegrationEvent : IntegrationEvent
{
public Guid ProductId { get; init; }
public decimal OldPrice { get; init; }
public decimal NewPrice { get; init; }
}
/// <summary>
/// EN: Event when payment is completed.
/// VI: Event khi thanh toán hoàn thành.
/// </summary>
public record PaymentCompletedIntegrationEvent : IntegrationEvent
{
public Guid PaymentId { get; init; }
public Guid OrderId { get; init; }
public decimal Amount { get; init; }
public string PaymentMethod { get; init; } = default!;
}
```
### Event Publisher Service
```csharp
/// <summary>
/// EN: Service to publish integration events.
/// VI: Service publish integration events.
/// </summary>
public interface IEventPublisher
{
Task PublishAsync<T>(T @event, CancellationToken ct = default) where T : class;
}
public class MassTransitEventPublisher : IEventPublisher
{
private readonly IPublishEndpoint _publishEndpoint;
private readonly ILogger<MassTransitEventPublisher> _logger;
public MassTransitEventPublisher(
IPublishEndpoint publishEndpoint,
ILogger<MassTransitEventPublisher> logger)
{
_publishEndpoint = publishEndpoint;
_logger = logger;
}
public async Task PublishAsync<T>(T @event, CancellationToken ct = default)
where T : class
{
try
{
await _publishEndpoint.Publish(@event, ct);
_logger.LogInformation(
"Published event {EventType}: {@Event}",
typeof(T).Name, @event);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to publish event {EventType}: {@Event}",
typeof(T).Name, @event);
throw;
}
}
}
```
---
## Event Consumers
### Consumer with Idempotency
```csharp
/// <summary>
/// EN: Idempotent consumer for payment completed events.
/// VI: Consumer idempotent cho payment completed events.
/// </summary>
public class PaymentCompletedConsumer : IConsumer<PaymentCompletedIntegrationEvent>
{
private readonly IOrderService _orderService;
private readonly IIdempotencyService _idempotency;
private readonly ILogger<PaymentCompletedConsumer> _logger;
public PaymentCompletedConsumer(
IOrderService orderService,
IIdempotencyService idempotency,
ILogger<PaymentCompletedConsumer> logger)
{
_orderService = orderService;
_idempotency = idempotency;
_logger = logger;
}
public async Task Consume(ConsumeContext<PaymentCompletedIntegrationEvent> context)
{
var @event = context.Message;
// EN: Check if already processed
// VI: Kiểm tra đã xử lý chưa
if (await _idempotency.HasBeenProcessedAsync(@event.Id))
{
_logger.LogInformation(
"Event {EventId} already processed, skipping",
@event.Id);
return;
}
try
{
_logger.LogInformation(
"Processing PaymentCompleted for Order {OrderId}",
@event.OrderId);
await _orderService.ConfirmPaymentAsync(
@event.OrderId,
@event.PaymentId,
@event.Amount,
context.CancellationToken);
// EN: Mark as processed
// VI: Đánh dấu đã xử lý
await _idempotency.MarkAsProcessedAsync(@event.Id);
_logger.LogInformation(
"Successfully processed PaymentCompleted for Order {OrderId}",
@event.OrderId);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to process PaymentCompleted for Order {OrderId}",
@event.OrderId);
throw; // EN: Will trigger retry
}
}
}
```
### Consumer Definition (Advanced Config)
```csharp
/// <summary>
/// EN: Consumer definition with custom retry and concurrency.
/// VI: Consumer definition với retry và concurrency tùy chỉnh.
/// </summary>
public class PaymentCompletedConsumerDefinition
: ConsumerDefinition<PaymentCompletedConsumer>
{
public PaymentCompletedConsumerDefinition()
{
// EN: Endpoint name
// VI: Tên endpoint
EndpointName = "order-service-payment-completed";
// EN: Prefetch count for concurrency
// VI: Prefetch count cho concurrency
ConcurrentMessageLimit = 10;
}
protected override void ConfigureConsumer(
IReceiveEndpointConfigurator endpointConfigurator,
IConsumerConfigurator<PaymentCompletedConsumer> consumerConfigurator,
IRegistrationContext context)
{
// EN: Custom retry for this consumer
// VI: Retry tùy chỉnh cho consumer này
endpointConfigurator.UseMessageRetry(r =>
{
r.Intervals(
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(30),
TimeSpan.FromMinutes(5));
r.Ignore<OrderNotFoundException>();
});
// EN: Circuit breaker
// VI: Circuit breaker
endpointConfigurator.UseCircuitBreaker(cb =>
{
cb.TrackingPeriod = TimeSpan.FromMinutes(1);
cb.TripThreshold = 15;
cb.ActiveThreshold = 10;
cb.ResetInterval = TimeSpan.FromMinutes(5);
});
}
}
```
---
## HTTP Client Patterns
### Typed HTTP Client
```csharp
/// <summary>
/// EN: Typed HTTP client for IAM service.
/// VI: Typed HTTP client cho IAM service.
/// </summary>
public interface IIamServiceClient
{
Task<UserInfoDto?> GetUserAsync(string userId, CancellationToken ct = default);
Task<bool> ValidateTokenAsync(string token, CancellationToken ct = default);
}
public class IamServiceClient : IIamServiceClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<IamServiceClient> _logger;
public IamServiceClient(
HttpClient httpClient,
ILogger<IamServiceClient> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<UserInfoDto?> GetUserAsync(string userId, CancellationToken ct = default)
{
try
{
var response = await _httpClient.GetAsync($"/api/v1/users/{userId}", ct);
if (response.StatusCode == HttpStatusCode.NotFound)
return null;
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ApiResponse<UserInfoDto>>(ct);
return result?.Data;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Failed to get user {UserId} from IAM service", userId);
throw;
}
}
public async Task<bool> ValidateTokenAsync(string token, CancellationToken ct = default)
{
try
{
var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/auth/validate");
request.Content = JsonContent.Create(new { Token = token });
var response = await _httpClient.SendAsync(request, ct);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException ex)
{
_logger.LogWarning(ex, "Token validation failed");
return false;
}
}
}
```
### HTTP Client Registration with Polly
```csharp
/// <summary>
/// EN: Register resilient HTTP clients.
/// VI: Đăng ký HTTP clients có khả năng phục hồi.
/// </summary>
builder.Services.AddHttpClient<IIamServiceClient, IamServiceClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Services:Iam:BaseUrl"]!);
client.Timeout = TimeSpan.FromSeconds(30);
})
.AddResilienceHandler("iam-client", builder =>
{
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(300),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(r => r.StatusCode >= HttpStatusCode.InternalServerError)
.Handle<HttpRequestException>()
.Handle<TimeoutRejectedException>()
});
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.3,
SamplingDuration = TimeSpan.FromSeconds(60),
MinimumThroughput = 10,
BreakDuration = TimeSpan.FromSeconds(30)
});
builder.AddTimeout(TimeSpan.FromSeconds(10));
});
```
---
## Outbox Pattern
### Outbox Entity
```csharp
/// <summary>
/// EN: Outbox message entity for reliable event publishing.
/// VI: Outbox message entity cho event publishing đáng tin cậy.
/// </summary>
public class OutboxMessage
{
public Guid Id { get; set; }
public string Type { get; set; } = default!;
public string Payload { get; set; } = default!;
public DateTime OccurredOn { get; set; }
public DateTime? ProcessedOn { get; set; }
public string? Error { get; set; }
public int RetryCount { get; set; }
}
// EN: DbContext configuration
// VI: Cấu hình DbContext
modelBuilder.Entity<OutboxMessage>(entity =>
{
entity.ToTable("OutboxMessages");
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.ProcessedOn);
});
```
### Outbox Processor
```csharp
/// <summary>
/// EN: Background service to process outbox messages.
/// VI: Background service xử lý outbox messages.
/// </summary>
public class OutboxProcessor : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<OutboxProcessor> _logger;
private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(5);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await ProcessPendingMessagesAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing outbox messages");
}
await Task.Delay(_pollingInterval, stoppingToken);
}
}
private async Task ProcessPendingMessagesAsync(CancellationToken ct)
{
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var publisher = scope.ServiceProvider.GetRequiredService<IPublishEndpoint>();
var messages = await dbContext.OutboxMessages
.Where(m => m.ProcessedOn == null && m.RetryCount < 3)
.OrderBy(m => m.OccurredOn)
.Take(100)
.ToListAsync(ct);
foreach (var message in messages)
{
try
{
var eventType = Type.GetType(message.Type)!;
var @event = JsonSerializer.Deserialize(message.Payload, eventType)!;
await publisher.Publish(@event, eventType, ct);
message.ProcessedOn = DateTime.UtcNow;
_logger.LogDebug("Processed outbox message {Id}", message.Id);
}
catch (Exception ex)
{
message.RetryCount++;
message.Error = ex.Message;
_logger.LogWarning(ex, "Failed to process outbox message {Id}", message.Id);
}
}
await dbContext.SaveChangesAsync(ct);
}
}
```
---
## Resources / Tài Nguyên
- [MassTransit Documentation](https://masstransit.io/)
- [RabbitMQ Documentation](https://www.rabbitmq.com/documentation.html)
- [Polly Documentation](https://github.com/App-vNext/Polly)
- [gRPC for .NET](https://docs.microsoft.com/en-us/aspnet/core/grpc/)