Что такое чистая архитектура (Clean Architecture)?

Ответ

Чистая архитектура (Clean Architecture) — это архитектурный подход, предложенный Робертом Мартином (Дядя Боб), который ставит бизнес-логику и правила предметной области в центр системы, делая их независимыми от внешних деталей: баз данных, фреймворков, UI или внешних сервисов.

Основной принцип — правило зависимостей (Dependency Rule):

Зависимости исходного кода должны быть направлены внутрь, к центру. Внешние слои (механизмы доставки, инфраструктура) зависят от внутренних слоев (бизнес-правила), но не наоборот.

Типичные слои (концентрические круги):

  1. Entities (Сущности): Ядро. Содержат критичные бизнес-правила и объекты предметной области. Это POCO-классы, не знающие ни о чем внешнем.
  2. Use Cases (Сценарии использования): Содержат специфичную логику приложения. Они оркестрируют поток данных от/к сущностям, координируя работу репозиториев и других интерфейсов.
  3. Interface Adapters (Адаптеры интерфейсов): Преобразуют данные между форматами, удобными для Use Cases и внешним миром (например, MVC-контроллеры, ViewModels, репозитории как абстракции).
  4. Frameworks & Drivers (Фреймворки и драйверы): Внешний слой. Базы данных, веб-фреймворки (ASP.NET Core), UI, файловые системы. Сюда подключаются конкретные реализации.

Пример структуры проекта и кода:

MyApp.Domain/          // Слой Entities (ядерные бизнес-правила)
    Entities/
        Order.cs
        Product.cs
    Interfaces/
        IRepository.cs // Абстракция объявлена здесь!

MyApp.Application/     // Слой Use Cases
    UseCases/
        CreateOrderUseCase.cs
    Interfaces/
        IEmailService.cs // Абстракция объявлена здесь!

MyApp.Infrastructure/  // Слой Frameworks & Drivers (конкретные реализации)
    Persistence/
        OrderRepository.cs // : IRepository (зависит от Domain!)
    Services/
        SmtpEmailService.cs // : IEmailService (зависит от Application!)

MyApp.WebApi/         // Слой Interface Adapters & Frameworks
    Controllers/
        OrderController.cs // Использует CreateOrderUseCase

Код сущности (Domain):

// MyApp.Domain/Entities/Order.cs
namespace MyApp.Domain.Entities;

public class Order // НИКАКИХ ссылок на EF, API и т.д.
{
    public int Id { get; private set; }
    public decimal TotalAmount { get; private set; }
    public OrderStatus Status { get; private set; } = OrderStatus.Pending;

    public void ApplyDiscount(decimal discount)
    {
        if (discount <= 0) throw new ArgumentException("Discount must be positive.");
        if (discount >= TotalAmount) throw new InvalidOperationException("Discount exceeds total.");
        // Бизнес-правило!
        TotalAmount -= discount;
    }

    public void MarkAsPaid() => Status = OrderStatus.Paid; // Еще одно правило
}

Преимущества:

  • Независимость: Бизнес-логика не зависит от выбора базы данных, веб-фреймворка или UI.
  • Тестируемость: Ядро можно тестировать изолированно, с моками всех внешних зависимостей.
  • Гибкость: Замена внешних компонентов (например, миграция с SQL Server на PostgreSQL) требует изменений только во внешнем слое.
  • Долгосрочная поддержка: Четкое разделение ответственности облегчает понимание и развитие кодовой базы.

Недостаток: Большой объем шаблонного кода (boilerplate) и повышенная сложность для маленьких проектов.

Ответ 18+ 🔞

А, чистая архитектура, говоришь? Ну это когда дядя Боб, тот самый, с бородой, который как будто всю жизнь в подвале с кофе и кодом сидит, взял и сказал: "А давайте-ка, сука, не будем мешать бизнес-правила с тем, как мы в базу данных тыкаемся".

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

Главный закон, который нельзя нарушать, иначе пиздец: Все зависимости смотрят внутрь. То есть внешние слои могут смотреть на внутренние и зависеть от них, а внутренние — нихуя. Центр — независимый, как Швейцария, блядь. Если твоя сущность Order вдруг узнала про DbContext из Entity Framework — ты проебала всю архитектуру, можно начинать сначала.

Слои, как луковица, только плачешь не от неё, а от количества кода:

  1. Сущности (Entities): Это ядро, самые важные поцы. Просто классы с данными и правилами. Order, Product, User. Они тупые как пробка и этим прекрасны.
  2. Сценарии использования (Use Cases): Это уже прикладная логика. Типа "Создать заказ", "Отправить уведомление". Они знают про сущности и про то, какие интерфейсы репозиториев им нужны (но не сами репозитории! только интерфейсы!). Они как менеджеры среднего звена — оркестрируют процесс.
  3. Адаптеры интерфейсов (Interface Adapters): Тут живут всякие контроллеры, вью-модели и, внимание, абстракции репозиториев и сервисов. То есть не сама реализация, которая лезет в SQL, а интерфейс IOrderRepository. Этот слой переводит с языка HTTP-запросов на язык Use Cases и обратно.
  4. Фреймворки и Драйверы (Frameworks & Drivers): Внешний мир, полный говна и палок. Конкретная реализация OrderRepository на Entity Framework, какой-нибудь SmtpEmailService, сам ASP.NET Core, база данных, файловая система. Всё, что может поменяться завтра, если начальство чихнёт.

Вот смотри, как это в папках выглядит, чтобы не ебнуть мозг:

MyApp.Domain/          // Слой 1: Сущности. Царство небесное.
    Entities/          // Тут наши короли, Order и Product.
    Interfaces/        // А тут абстракции, которые они дозволяют использовать (типа IRepository).

MyApp.Application/     // Слой 2: Use Cases. Исполнительная власть.
    UseCases/          // Тут чуваки, которые знают КАК что-то сделать.
    Interfaces/        // А тут абстракции для внешних сервисов (типа IEmailService).

MyApp.Infrastructure/  // Слой 4: Реализации. Рабочий класс, пашет в поту.
    Persistence/       // Конкретный репозиторий, который тащит данные из SQL.
    Services/          // Конкретная рассылка писем через SMTP.

MyApp.WebApi/         // Слой 3 + 4: Адаптеры и движок.
    Controllers/       // Тут сидят ребята, которые ловят HTTP и кричат Use Case'ам.

А вот как выглядит сущность, которая ни о чём не догадывается:

// MyApp.Domain/Entities/Order.cs
namespace MyApp.Domain.Entities;

public class Order // Видишь? Никаких using на EF, никаких атрибутов [Key]!
{
    public int Id { get; private set; }
    public decimal TotalAmount { get; private set; }
    public OrderStatus Status { get; private set; } = OrderStatus.Pending;

    // А вот бизнес-правило, ёпта! Оно здесь живёт!
    public void ApplyDiscount(decimal discount)
    {
        if (discount <= 0) throw new ArgumentException("Скидка должна быть положительной, мудак!");
        if (discount >= TotalAmount) throw new InvalidOperationException("Да ты что, скидка больше чека!");
        TotalAmount -= discount; // Всё, правило выполнилось.
    }

    public void MarkAsPaid() => Status = OrderStatus.Paid; // И ещё одно.
}

Зачем этот цирк?

  • Независимость: Захотел поменять базу с MySQL на PostgreSQL? Иди в Infrastructure, перепиши реализацию репозитория, и ядро приложения даже не чихнёт.
  • Тестируемость: Хочешь протестировать логику применения скидки? Да пожалуйста, создаёшь объект Order в тесте и вызываешь метод. Никаких моков баз данных, ничего. Чистая логика.
  • Долгая жизнь: Когда через год новый разработчик придет, он посмотрит в Domain и сразу поймёт, как работает бизнес. Ему не надо для этого копаться в миграциях EF или конфигах API.

Минусы, потому что без них никуда: Это овердохуища кода, Карл. Для маленького проекта, где надо сделать "кнопку-базу-отчёт", это как стрелять из пушки по воробьям. Ты половину времени будешь писать эти интерфейсы, реализации и настраивать DI-контейнер, вместо того чтобы делать фичи. Но если проект серьёзный и жить ему долго — то это, блядь, спасение.

Видео-ответы