--- 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 /// /// EN: Base interface for integration events. /// VI: Interface cơ sở cho integration events. /// public interface IIntegrationEvent { Guid Id { get; } DateTime OccurredOn { get; } } /// /// EN: Event when order is created. /// VI: Event khi order được tạo. /// 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 Items { get; init; } = Array.Empty(); } public record OrderItemInfo(Guid ProductId, int Quantity, decimal UnitPrice); ``` ### Event Publisher with MassTransit ```csharp /// /// EN: Publish integration events via MassTransit. /// VI: Publish integration events qua MassTransit. /// public class IntegrationEventPublisher : IIntegrationEventPublisher { private readonly IPublishEndpoint _publishEndpoint; private readonly ILogger _logger; public IntegrationEventPublisher( IPublishEndpoint publishEndpoint, ILogger logger) { _publishEndpoint = publishEndpoint; _logger = logger; } public async Task PublishAsync(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 { private readonly IOrderRepository _orderRepository; private readonly IIntegrationEventPublisher _eventPublisher; public async Task 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 /// /// EN: Consumer for OrderCreatedIntegrationEvent in Inventory Service. /// VI: Consumer cho OrderCreatedIntegrationEvent trong Inventory Service. /// public class OrderCreatedConsumer : IConsumer { private readonly IInventoryService _inventoryService; private readonly ILogger _logger; public OrderCreatedConsumer( IInventoryService inventoryService, ILogger logger) { _inventoryService = inventoryService; _logger = logger; } public async Task Consume(ConsumeContext 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 /// /// EN: Configure MassTransit with RabbitMQ. /// VI: Cấu hình MassTransit với RabbitMQ. /// // Program.cs builder.Services.AddMassTransit(x => { // EN: Register consumers // VI: Đăng ký consumers x.AddConsumer(); x.AddConsumer(); 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 /// /// 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ụ. /// // Program.cs builder.Services.AddHttpClient(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() .HandleResult(r => (int)r.StatusCode >= 500) .Handle() }); // 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 context) { await _service.CreateShipment(context.Message.OrderId); } // ✅ GOOD: Idempotent processing public async Task Consume(ConsumeContext 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