Почему CatalogRepository вынесен в слой Infrastructure, а не в Application?

Ответ

CatalogRepository размещается в слое Infrastructure, потому что он содержит конкретную реализацию доступа к данным (например, использует EF Core, Dapper или внешний API). Это соответствует принципу Dependency Inversion (DIP) и принципам чистой архитектуры или Onion Architecture.

Почему это важно:

  1. Разделение ответственности: Слой Application содержит бизнес-логику и абстракции (интерфейсы репозиториев), а Infrastructure — их конкретные реализации.
  2. Тестируемость: Бизнес-логику в Application можно легко протестировать с помощью mock-объектов, подменяя реальный репозиторий.
  3. Гибкость: Технологию доступа к данным (например, смена ORM или переход на gRPC) можно изменить, не затрагивая ядро приложения.

Пример структуры:

// Application Layer (абстракция)
namespace MyApp.Application.Interfaces;
public interface ICatalogRepository
{
    Task<Product> GetByIdAsync(int id, CancellationToken cancellationToken);
}

// Infrastructure Layer (реализация)
namespace MyApp.Infrastructure.Persistence.Repositories;
public class CatalogRepository : ICatalogRepository
{
    private readonly ApplicationDbContext _context;
    public CatalogRepository(ApplicationDbContext context) => _context = context;

    public async Task<Product> GetByIdAsync(int id, CancellationToken cancellationToken)
    {
        return await _context.Products
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
    }
}

Таким образом, зависимость направлена от деталей (Infrastructure) к абстракциям (Application), что делает архитектуру устойчивой к изменениям.

Ответ 18+ 🔞

Давай разберём эту тему без заумных слов, как есть. Представь, что твоё приложение — это квартира.

Application слой (бизнес-логика) — это твоя чистая, отремонтированная жилая комната. Диван, телевизор, холодильник с пивом. Всё по фэншую. Тут ты знаешь, ЧТО тебе нужно: например, «принеси мне из хранилища банку огурцов». Ты просто объявляешь: «Эй, мироздание, мне нужен кто-то, кто умеет приносить огурцы!» — и пишешь на бумажке (интерфейс) IХранительОгурцов.

Infrastructure слой — это твой подвал, гараж и вообще всё хозяйство. Там стоит морозильная камера (база данных), лежат инструменты (EF Core, Dapper), и там же живёт конкретный мужик Вася, который знает, как открыть эту морозилку, порыться среди пельменей и найти те самые огурцы. Он — реализация твоего интерфейса. Он CatalogRepository.

Почему Вася (репозиторий) живёт в подвале (Infrastructure), а не в комнате?

  1. Чтобы не гадил в жилом помещении. Если Вася начнёт разбрасывать по комнате SQL-запросы, Connection String'и и прочие специфичные для базы штуки — в комнате будет бардак. Тебе же потом этот код читать! Всё низкоуровневое — в подвал.

  2. Чтобы можно было Васю заменить. Завтра ты купишь не морозилку, а огромный промышленный холодильник (скажем, переедешь с SQL Server на PostgreSQL или вообще на внешний API). Тебе нужно будет просто нанять нового Васю, который умеет работать с этим холодильником. Ты приведёшь его в подвал, дашь ту же самую инструкцию IХранительОгурцов — и он начнёт работать. А в твоей чистой комнате (Application) НИЧЕГО не изменится. Диван, телевизор и логика «принеси-покажи-съешь огурец» останутся нетронутыми. Это и есть Dependency Inversion — комната зависит от абстрактной бумажки-инструкции, а не от конкретного Васи.

  3. Чтобы тестировать комнату в одиночку. Хочешь проверить, правильно ли работает твоя логика поедания огурцов? Подсунешь в комнату не реального Васю, а его двойника-пустышку (mock), который будет по первой команде выдавать тестовый огурец. Не придётся ради каждого теста бегать в подвал и включать морозилку. Удобно, быстро.

Короче, на примере кода:

// Application (Комната) - Тут только объявление, ЧТО надо.
namespace MyApp.Application.Interfaces;
public interface ICatalogRepository // Бумажка с требованиями к Василию
{
    Task<Product> GetByIdAsync(int id, CancellationToken cancellationToken);
}

// Infrastructure (Подвал) - Тут живёт конкретный исполнитель.
namespace MyApp.Infrastructure.Persistence.Repositories;
public class CatalogRepository : ICatalogRepository // Сам Вася
{
    private readonly ApplicationDbContext _context; // Его морозилка (EF Core)

    public CatalogRepository(ApplicationDbContext context) => _context = context;

    public async Task<Product> GetByIdAsync(int id, CancellationToken cancellationToken)
    {
        // Он знает свои инструменты (AsNoTracking, FirstOrDefaultAsync)
        return await _context.Products
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
    }
}

Итог: Разделяя так слои, ты делаешь архитектуру устойчивой к ебучим изменениям. Захотел поменять базу данных, ORM или способ доступа — ты ковыряешься только в подвале (Infrastructure). Вся твоя ценная бизнес-логика в Application остаётся неприкосновенной, как комната за стеклянной витриной. И тестировать её — одно удовольствие.