Микросервисы на C#: архитектура и практика продвинутого уровня

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Микросервисы на C#: архитектура и практика продвинутого уровня

Переход от монолита к микросервисной архитектуре на платформе .NET — это не просто техническое решение, а стратегический шаг к масштабируемости и отказоустойчивости. Однако на продвинутом уровне разработчики сталкиваются с нетривиальными проблемами: распределенные транзакции, согласованность данных и сложная оркестрация. В этой статье мы разберем архитектурные паттерны, которые помогут построить надежную микросервисную экосистему на C#, и рассмотрим практические примеры кода.

1. Паттерн Saga: управление распределенными транзакциями

В микросервисах традиционные ACID-транзакции невозможны. Паттерн Saga решает проблему согласованности данных, разбивая глобальную операцию на последовательность локальных транзакций с компенсирующими действиями.

Хореография vs. Оркестрация:

  • Хореография — каждый сервис публикует события и реагирует на них. Подходит для простых сценариев.
  • Оркестрация — центральный координатор (Orchestrator) управляет шагами. Рекомендуется для сложных бизнес-процессов.

Пример реализации оркестратора на C# с использованием MassTransit:

public class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
    public State Submitted { get; set; }
    public State PaymentPending { get; set; }
    public State Completed { get; set; }

    public Event<OrderSubmitted> OrderSubmitted { get; set; }
    public Event<PaymentProcessed> PaymentProcessed { get; set; }
    public Event<PaymentFailed> PaymentFailed { get; set; }

    public OrderSaga()
    {
        InstanceState(x => x.CurrentState);

        Initially(
            When(OrderSubmitted)
                .Then(context => context.Saga.OrderId = context.Message.OrderId)
                .TransitionTo(Submitted)
                .Publish(context => new ProcessPayment
                {
                    OrderId = context.Saga.OrderId,
                    Amount = context.Message.Amount
                })
        );

        During(Submitted,
            When(PaymentProcessed)
                .TransitionTo(Completed)
                .Finalize(),
            When(PaymentFailed)
                .Then(context => Console.WriteLine("Компенсация: отмена заказа"))
                .TransitionTo(Final)
        );
    }
}

Этот код демонстрирует, как оркестратор управляет жизненным циклом заказа: при успешной оплате сага завершается, при ошибке — запускается компенсация.

2. CQRS и Event Sourcing: разделение ответственности

Паттерн CQRS (Command Query Responsibility Segregation) разделяет команды (запись) и запросы (чтение) на разные модели. В сочетании с Event Sourcing (ES) он обеспечивает полное аудирование и гибкость.

Архитектура CQRS+ES:

  • Команды — изменяют состояние через события.
  • События — хранятся в Event Store (например, EventStoreDB).
  • Проекции — строят read-модели для быстрых запросов.

Пример команды и обработчика:

public class CreateOrderCommand : ICommand
{
    public Guid OrderId { get; set; }
    public decimal Amount { get; set; }
}

public class CreateOrderHandler : ICommandHandler<CreateOrderCommand>
{
    private readonly IEventStore _eventStore;

    public async Task Handle(CreateOrderCommand command)
    {
        var orderCreated = new OrderCreatedEvent
        {
            OrderId = command.OrderId,
            Amount = command.Amount,
            Timestamp = DateTime.UtcNow
        };

        await _eventStore.AppendEventAsync(
            $"order-{command.OrderId}", 
            orderCreated
        );
    }
}

Read-модель строится отдельно. Например, для получения списка заказов:

public class OrderReadModel
{
    public Guid Id { get; set; }
    public decimal TotalAmount { get; set; }
    public string Status { get; set; }
}

public class OrderQueryService
{
    private readonly IDbConnection _readDb;

    public async Task<IEnumerable<OrderReadModel>> GetOrdersAsync()
    {
        const string sql = "SELECT Id, TotalAmount, Status FROM Orders";
        return await _readDb.QueryAsync<OrderReadModel>(sql);
    }
}

Такой подход позволяет оптимизировать чтение под конкретные сценарии, не затрагивая модель записи.

3. Продвинутая коммуникация: брокеры сообщений и Resiliency

Синхронное взаимодействие (HTTP/gRPC) в микросервисах создает жесткие связи. На продвинутом уровне предпочтение отдается асинхронной коммуникации через брокеры сообщений (RabbitMQ, Kafka).

Паттерн Outbox: Гарантирует доставку сообщений при сохранении данных в БД.

public class OrderService
{
    private readonly AppDbContext _db;
    private readonly IMessageBus _bus;

    public async Task CreateOrderAsync(Order order)
    {
        // 1. Сохраняем заказ и сообщение в одной транзакции
        using var transaction = await _db.Database.BeginTransactionAsync();
        
        _db.Orders.Add(order);
        _db.OutboxMessages.Add(new OutboxMessage
        {
            Type = "OrderCreated",
            Payload = JsonSerializer.Serialize(order),
            CreatedAt = DateTime.UtcNow
        });

        await _db.SaveChangesAsync();
        await transaction.CommitAsync();

        // 2. Отдельный процесс отправляет сообщения из Outbox
    }
}

Resiliency с Polly: Обработка временных сбоев.

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

var circuitBreaker = Policy
    .Handle<TimeoutException>()
    .CircuitBreakerAsync(5, TimeSpan.FromMinutes(1));

await retryPolicy.WrapAsync(circuitBreaker)
    .ExecuteAsync(() => _paymentService.ProcessPaymentAsync(order));

Эти техники критически важны для production-систем, где отказ одного сервиса не должен парализовать всю систему.

4. Мониторинг и Observability в .NET микросервисах

Без централизованного мониторинга микросервисная архитектура превращается в "черный ящик". Продвинутые команды используют OpenTelemetry для сбора метрик, трейсов и логов.

Настройка OpenTelemetry в ASP

Блоги

Рекомендаций по книгам