Ответ
Чистая архитектура (Clean Architecture) — это архитектурный подход, предложенный Робертом Мартином (Дядя Боб), который ставит бизнес-логику и правила предметной области в центр системы, делая их независимыми от внешних деталей: баз данных, фреймворков, UI или внешних сервисов.
Основной принцип — правило зависимостей (Dependency Rule):
Зависимости исходного кода должны быть направлены внутрь, к центру. Внешние слои (механизмы доставки, инфраструктура) зависят от внутренних слоев (бизнес-правила), но не наоборот.
Типичные слои (концентрические круги):
- Entities (Сущности): Ядро. Содержат критичные бизнес-правила и объекты предметной области. Это POCO-классы, не знающие ни о чем внешнем.
- Use Cases (Сценарии использования): Содержат специфичную логику приложения. Они оркестрируют поток данных от/к сущностям, координируя работу репозиториев и других интерфейсов.
- Interface Adapters (Адаптеры интерфейсов): Преобразуют данные между форматами, удобными для Use Cases и внешним миром (например, MVC-контроллеры, ViewModels, репозитории как абстракции).
- 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 — ты проебала всю архитектуру, можно начинать сначала.
Слои, как луковица, только плачешь не от неё, а от количества кода:
- Сущности (Entities): Это ядро, самые важные поцы. Просто классы с данными и правилами.
Order,Product,User. Они тупые как пробка и этим прекрасны. - Сценарии использования (Use Cases): Это уже прикладная логика. Типа "Создать заказ", "Отправить уведомление". Они знают про сущности и про то, какие интерфейсы репозиториев им нужны (но не сами репозитории! только интерфейсы!). Они как менеджеры среднего звена — оркестрируют процесс.
- Адаптеры интерфейсов (Interface Adapters): Тут живут всякие контроллеры, вью-модели и, внимание, абстракции репозиториев и сервисов. То есть не сама реализация, которая лезет в SQL, а интерфейс
IOrderRepository. Этот слой переводит с языка HTTP-запросов на язык Use Cases и обратно. - Фреймворки и Драйверы (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-контейнер, вместо того чтобы делать фичи. Но если проект серьёзный и жить ему долго — то это, блядь, спасение.
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶