Clean Architecture на Практике: Разбор Слоев, Зависимостей и Реальных Примеров Кода

Практическое руководство по реализации Clean Architecture с детальным разбором каждого слоя: Entities, Use Cases, Interface Adapters и Frameworks. Статья включает примеры кода на псевдокоде, объясняет направление зависимостей и дает ответы на частые вопросы по валидации и организации кода.
Clean Architecture (Чистая Архитектура), популяризированная Робертом Мартином (Uncle Bob), предлагает модель построения систем, независимых от фреймворков, UI, баз данных и внешних агентов. Ее цель — создание гибких, тестируемых и устойчивых к изменениям приложений. Однако за кажущейся простотой диаграмм с концентрическими кругами скрывается масса вопросов по реализации. Как правильно организовать слои? Куда вставлять валидацию? Как работать с внешними сервисами? Давайте разберем архитектуру слой за слоем, подкрепляя теорию фрагментами кода.

В центре Clean Architecture находятся Entities (Сущности) — это бизнес-объекты с правилами, которые являются наиболее фундаментальными и наименее подверженными изменениям. Например, класс `BankAccount` с методами `Deposit()` и `Withdraw()`, которые инкапсулируют правила минимального баланса. Вокруг них — слой Use Cases (Сценарии использования). Здесь содержится вся бизнес-логика приложения. Каждый use case — это отдельный класс, orchestrating поток данных от и к сущностям. Например, `TransferMoneyUseCase`. Он принимает `TransferMoneyCommand` (простой DTO), валидирует его (базовую валидацию), использует репозиторий для загрузки сущностей `Account`, вызывает их методы и снова сохраняет через репозиторий. Ключевой принцип: зависимости направлены внутрь. Use Cases зависят от абстракций репозиториев (интерфейсов), но не от их реализации.

Следующий круг — Interface Adapters (Адаптеры интерфейсов). Этот слой преобразует данные из формата, удобного для use cases, в формат, удобный для внешних агентов (БД, веб). Сюда входят Controllers, Presenters, Gateways. Например, `BankAccountController` принимает HTTP-запрос, мапит его в `TransferMoneyCommand`, вызывает `TransferMoneyUseCase` и возвращает ответ, преобразованный через Presenter в JSON. Здесь же находятся реализации репозиториев, например, `SqlAccountRepository`, который знает, как сохранить сущность `Account` в конкретную реляционную БД. Важно: все зависимости здесь направлены внутрь, к use cases. Контроллер зависит от use case, репозиторий реализует интерфейс, объявленный в доменном слое.

Внешний круг — Frameworks & Drivers (Фреймворки и драйверы). Это инфраструктурные детали: сама база данных, веб-фреймворк (Spring, ASP.NET Core), библиотеки для работы с сообщениями. Этот слой «подключается» к приложению через адаптеры. Код в этом слое самый изменчивый, и его изоляция — главная победа Clean Architecture.

Рассмотрим практический пример на псевдокоде, близком к C#/Java:

// 1. Domain Layer (Entities)
public class Order {
 public Guid Id { get; private set; }
 public Money Total { get; private set; }
 public void ApplyDiscount(Voucher voucher) { ... } // Бизнес-правило
}

// 2. Application Layer (Use Cases & Interfaces)
public interface IOrderRepository {
 Task GetByIdAsync(Guid id);
 Task SaveAsync(Order order);
}

public class ApplyDiscountUseCase {
 private readonly IOrderRepository _repo;
 public ApplyDiscountUseCase(IOrderRepository repo) { _repo = repo; } // Dependency Injection
 public async Task Execute(ApplyDiscountCommand command) {
 var order = await _repo.GetByIdAsync(command.OrderId);
 order.ApplyDiscount(command.Voucher);
 await _repo.SaveAsync(order);
 }
}

// 3. Interface Adapters Layer
public class OrderController : ControllerBase {
 private readonly ApplyDiscountUseCase _useCase;
 public OrderController(ApplyDiscountUseCase useCase) { _useCase = useCase; }
 [HttpPost("discount")]
 public async Task ApplyDiscount([FromBody] ApplyDiscountRequest request) {
 var command = new ApplyDiscountCommand(request.OrderId, request.VoucherCode);
 await _useCase.Execute(command);
 return Ok();
 }
}

public class SqlOrderRepository : IOrderRepository {
 private readonly AppDbContext _db;
 public SqlOrderRepository(AppDbContext db) { _db = db; }
 public async Task GetByIdAsync(Guid id) {
 // Маппинг из ORM-сущности в доменную Order
 var entity = await _db.Orders.FindAsync(id);
 return OrderMapper.ToDomain(entity);
 }
 public async Task SaveAsync(Order order) { ... }
}

// 4. Frameworks Layer (Startup.cs, Program.cs, конфигурация БД)

Основные сложности, с которыми сталкиваются разработчики: где размещать кросс-резающие задачи (логирование, кэширование, валидацию команд)? Ответ — использовать декораторы (паттерн Decorator) для use cases или механизмы interceptors вашего фреймворка. Где проводить валидацию данных? Простую синтаксическую валидацию (обязательное поле, формат email) — в DTO запроса (с помощью атрибутов или FluentValidation). Сложную бизнес-валидацию (достаточно ли средств на счету) — внутри сущностей или use cases. Clean Architecture требует большего количества классов и шаблонного кода на старте, но эта инвестиция окупается при долгосрочной поддержке, тестировании и необходимости замены технологического стека.
304 2

Комментарии (8)

avatar
rci66kyuje95 31.03.2026
кода? Иногда ради чистоты приходится создавать десятки лишних классов.
avatar
x96rm9frv 01.04.2026
Не согласен, что Clean Architecture всегда оправдана. Для небольших проектов это overengineering и лишняя сложность.
avatar
q75jgbk 01.04.2026
Интересно, как вы решаете проблему
avatar
ieljgckrno1 01.04.2026
Статья хорошая, но не хватает сравнения с другими подходами, например, с Hexagonal Architecture. В чём ключевые отличия?
avatar
j0gl00g5 02.04.2026
Спасибо за примеры кода! Диаграммы — это хорошо, но без реальных классов сложно представить поток данных между слоями.
avatar
kuqcumc7 02.04.2026
Отличная статья! Как раз искал практические примеры разделения слоёв, теорию знаю, а с реализацией бывают сложности.
avatar
on4oxkbu6 02.04.2026
Хорошо, что затронули тему валидации. А куда бы вы посоветовали выносить DTO? В слой Use Case или всё же в контроллер?
avatar
t7bspavkbx 04.04.2026
Ждал разбора работы с внешними API. Часто они ломают всю чистоту архитектуры своими неидеальными контрактами.
Вы просмотрели все комментарии