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

12 KiB

name, description, compatibility, metadata
name description compatibility metadata
inter-service-communication Giao tiếp liên dịch vụ. Use for Event Bus (RabbitMQ), Integration Events, HTTP Client với Polly, và gRPC patterns. .NET 8+, MassTransit, RabbitMQ, Polly, gRPC
author version
Velik Ho 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

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

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

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

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

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

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

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

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