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 требует большего количества классов и шаблонного кода на старте, но эта инвестиция окупается при долгосрочной поддержке, тестировании и необходимости замены технологического стека.
Clean Architecture на Практике: Разбор Слоев, Зависимостей и Реальных Примеров Кода
Практическое руководство по реализации Clean Architecture с детальным разбором каждого слоя: Entities, Use Cases, Interface Adapters и Frameworks. Статья включает примеры кода на псевдокоде, объясняет направление зависимостей и дает ответы на частые вопросы по валидации и организации кода.
304
2
Комментарии (8)