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