Какие шаблоны проектирования используешь для backend?

«Какие шаблоны проектирования используешь для backend?» — вопрос из категории Паттерны, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Выбор паттернов зависит от масштаба и сложности приложения. Вот ключевые, которые я применяю на практике:

  1. Repository & Unit of Work. Абстрагируют доступ к данным, что упрощает тестирование и смену источника данных (например, с EF Core на Dapper).

    public interface IRepository<T> where T : class
    {
        Task<T?> GetByIdAsync(int id);
        void Add(T entity);
        void Remove(T entity);
    }
    
    public interface IUnitOfWork : IDisposable
    {
        IRepository<User> Users { get; }
        IRepository<Order> Orders { get; }
        Task<int> SaveChangesAsync(CancellationToken ct = default);
    }
  2. Dependency Injection (Внедрение зависимостей). Фундаментальный паттерн в ASP.NET Core для управления зависимостями и повышения тестируемости.

    // Регистрация в Startup.cs / Program.cs
    services.AddScoped<IUserService, UserService>();
    services.AddSingleton<ICacheService, RedisCacheService>();
  3. CQRS (Command Query Responsibility Segregation). Разделение моделей для операций записи (Commands) и чтения (Queries). Особенно полезен в высоконагруженных системах, где требования к чтению и записи разные.

    • Command: Изменяет состояние системы (CreateOrderCommand).
    • Query: Возвращает данные без изменения состояния (GetUserOrdersQuery). Часто реализуется с помощью библиотеки MediatR.
  4. Mediator. Позволяет уменьшить связанность между компонентами. Компоненты общаются не напрямую, а через посредника.

    // С MediatR
    public record CreateUserCommand(string Email, string Name) : IRequest<int>;
    
    public class CreateUserHandler : IRequestHandler<CreateUserCommand, int>
    {
        private readonly AppDbContext _context;
        public CreateUserHandler(AppDbContext context) => _context = context;
    
        public async Task<int> Handle(CreateUserCommand request, CancellationToken ct)
        {
            var user = new User { Email = request.Email, Name = request.Name };
            _context.Users.Add(user);
            await _context.SaveChangesAsync(ct);
            return user.Id;
        }
    }
  5. Strategy. Используется, когда необходимо выбирать алгоритм во время выполнения.

    public interface IPaymentStrategy
    {
        Task<PaymentResult> ProcessAsync(decimal amount);
    }
    
    public class PaymentProcessor
    {
        private readonly IPaymentStrategy _strategy;
        public PaymentProcessor(IPaymentStrategy strategy) => _strategy = strategy;
        public Task<PaymentResult> ExecutePayment(decimal amount) => _strategy.ProcessAsync(amount);
    }
    // Внедряем конкретную стратегию: CreditCardStrategy, PayPalStrategy и т.д.
  6. Decorator. Для динамического добавления поведения объекту (логирование, кэширование, валидация) без изменения его класса.

    public class LoggingUserServiceDecorator : IUserService
    {
        private readonly IUserService _innerService;
        private readonly ILogger _logger;
        public LoggingUserServiceDecorator(IUserService innerService, ILogger logger)
        {
            _innerService = innerService;
            _logger = logger;
        }
    
        public async Task<User> GetUserAsync(int id)
        {
            _logger.LogInformation("Fetching user {UserId}", id);
            return await _innerService.GetUserAsync(id);
        }
    }

Для простого CRUD-приложения часто достаточно Repository + DI. Для сложных, масштабируемых систем добавляются CQRS, Mediator и стратегии.