Микросервисы на C#: от теории к практике на .NET
Микросервисная архитектура стала стандартом для построения масштабируемых и отказоустойчивых приложений. Платформа .NET (C#) предоставляет все необходимые инструменты для создания таких систем: от встроенной поддержки контейнеризации до мощных фреймворков для межсервисного взаимодействия. В этой статье мы разберем ключевые принципы построения микросервисов на C# и рассмотрим практические примеры кода.
1. Основы микросервисной архитектуры на .NET
Микросервисы — это подход, при котором приложение разбивается на независимые, слабо связанные сервисы. Каждый сервис отвечает за свою бизнес-функцию и может разрабатываться, развертываться и масштабироваться независимо от других.
Ключевые принципы для .NET разработчика:
- Единая ответственность (Single Responsibility) — каждый микросервис решает одну задачу.
- Независимость данных — каждый сервис имеет свою базу данных (Database per Service).
- Слабая связность (Loose Coupling) — сервисы общаются через API, а не через общую память.
- Автономность — сервис может быть изменен и переразвернут без остановки всей системы.
Для создания нового проекта микросервиса на C# используйте команду:
dotnet new webapi -n UserService
dotnet new webapi -n OrderService
dotnet new webapi -n NotificationService
Каждый проект получает свой Program.cs, конфигурации и зависимости.
2. Межсервисное взаимодействие: REST и gRPC
В .NET есть два основных подхода к общению между микросервисами: синхронный (REST/gRPC) и асинхронный (через брокеры сообщений). Рассмотрим оба.
REST API с использованием HttpClient
Самый простой способ — использовать HttpClientFactory для создания типизированного клиента.
// В Program.cs сервиса OrderService
builder.Services.AddHttpClient<IUserServiceClient, UserServiceClient>(client =>
{
client.BaseAddress = new Uri("http://userservice:5000");
});
// UserServiceClient.cs
public class UserServiceClient : IUserServiceClient
{
private readonly HttpClient _httpClient;
public UserServiceClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<UserDto> GetUserAsync(int userId)
{
var response = await _httpClient.GetAsync($"/api/users/{userId}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<UserDto>();
}
}
Рекомендация: используйте Polly (библиотека для .NET) для реализации политик повторных попыток и Circuit Breaker.
gRPC для высокопроизводительных сценариев
gRPC работает поверх HTTP/2 и использует Protocol Buffers для сериализации. Это значительно быстрее JSON.
// Определение сервиса в user.proto
service UserGrpcService {
rpc GetUser (GetUserRequest) returns (UserResponse);
}
// Код сервера на C#
public class UserGrpcServiceImpl : UserGrpcService.UserGrpcServiceBase
{
public override Task<UserResponse> GetUser(GetUserRequest request, ServerCallContext context)
{
var user = _dbContext.Users.Find(request.UserId);
return Task.FromResult(new UserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email
});
}
}
3. Управление данными и транзакциями
В микросервисах каждая база данных принадлежит только одному сервису. Это исключает прямые SQL-запросы между сервисами. Для работы с данными в .NET стандартом является Entity Framework Core.
Пример конфигурации DbContext для микросервиса
public class OrderDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(o => o.Id);
entity.Property(o => o.Status).HasMaxLength(50);
});
}
}
// В Program.cs
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("OrderDb")));
Важно: для обеспечения согласованности данных между сервисами используйте сагу (Saga) — паттерн для управления распределенными транзакциями. Пример реализации на C# с использованием MassTransit:
public class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
public State Submitted { get; set; }
public State PaymentApproved { get; set; }
public State Completed { get; set; }
public Event<OrderSubmitted> OrderSubmitted { get; set; }
public Event<PaymentProcessed> PaymentProcessed { get; set; }
public OrderSaga()
{
Initially(
When(OrderSubmitted)
.Then(context => context.Saga.OrderId = context.Message.OrderId)
.TransitionTo(Submitted)
.Publish(context => new ProcessPayment(context.Message.OrderId))
);
During(Submitted,
When(PaymentProcessed)
.Then(context => context.Saga.PaymentId = context.Message.PaymentId)
.TransitionTo(PaymentApproved)
.Publish(context => new CompleteOrder(context.Message.OrderId))
);
}
}
4. Развертывание и мониторинг микросервисов
Современные микросервисы на C# обычно разворачиваются в Docker-контейнерах. Вот минимальный Dockerfile для .NET микросервиса:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["UserService/UserService.csproj", "UserService/"]
RUN dotnet restore "UserService/UserService.csproj"
COPY . .
WORKDIR "/src/UserService"
RUN dotnet build "UserService.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "UserService.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "UserService.dll"]
Для оркестрации используйте Kubernetes или <