Найти в Дзене
TechLead Insights

Чистая архитектура: избавляемся от хаоса с помощью DDD и SOLID

Оглавление

Clean Architecture: простыми словами

В Clean Architecture, как учит Роберт Мартин (он же Uncle Bob), работает чёткое правило: внешний код всегда зависит от внутреннего, но не наоборот. Всё просто — если у тебя есть сервисы или репозитории, они должны взаимодействовать с бизнес-логикой через абстракции (интерфейсы), которые определяет ядро приложения. Это значит, что доменная или прикладная логика сами формулируют, что им нужно (например, интерфейс получения пользователя), а вот как это будет реализовано — не их забота, этим занимается инфраструктура.

Кратко про слои:

  • Домен (Domain/Entities): тут живёт твоя предметная область, бизнес-правила и сущности. Никаких технических деталей, только чистая логика.
  • Приложение (Application/Use Cases): здесь оркестрируются сценарии использования (use-cases), работающие с доменом и абстракциями для работы с внешним миром.
  • Инфраструктура (Infrastructure): наружный слой, где происходят все «грязные» вещи: доступ к БД, интеграция с файловыми системами, внешними API и т.п.
  • Презентация (UI): здесь у тебя контроллеры, Web API, десктоп/мобильный фронт и прочее, всё, что общается с пользователем.

Куда класть интерфейсы?

Есть два подхода:

  1. В Application-слой — так рекомендует сам Uncle Bob, и это рабочий стандарт. Тут интерфейсы объявляются там, где нужны для use-case'а, а домен остаётся вообще не в курсе о том, что у тебя есть какая-то база или хранилище.
  2. В Domain-слой — если ты строго следуешь DDD и в предметной области есть понятие репозитория для агрегатов, можешь описывать интерфейс прямо в домене. Так домен остаётся «чистым», но явно подразумевает, что кто-то должен реализовать хранение.

Классика — интерфейсы, реализуемые инфраструктурой, — часть ядра, а не внешнего слоя. Главное — зависимости должны идти внутрь.

Принципы зависимостей: Dependency Rule и DIP

Всё это базируется на Dependency Inversion Principle (DIP) — той самой «D» из SOLID. В книге у Мартина формулировка такая: «Все зависимости в коде должны быть направлены внутрь, к политике более высокого уровня». То есть бизнес-логика не зависит от деталей, например, базы данных, а наоборот — детали реализуют абстракции, определённые в бизнес-логике.

Как это работает на практике:

Вместо того чтобы класс домена знал, как работать с SQL или MongoDB, он видит только интерфейс, скажем, IRepository. А вот реализация (например, SqlRepository) — уже знает все детали, и лежит в Infrastructure. При запуске приложения мы скармливаем DI-контейнеру:

services.AddScoped<IUserRepository, UserRepository>();

и всё, магия — везде в приложении будет именно эта реализация, а код ядра останется нетронутым при любом рефакторинге инфраструктуры.

Важно: не путай DIP с IoC!

  • DIP — это правило про зависимости от абстракций, а не деталей.
  • IoC — шире, это про то, что объекты не создают свои зависимости сами, а получают их снаружи (через DI, например).

SOLID-принципы и Clean Architecture

Clean Architecture по сути реализует все принципы SOLID на уровне всей системы, а не только классов:

SRP (Single Responsibility): Каждый слой отвечает только за своё — бизнес-логика живёт своей жизнью, детали хранения — своей.

OCP (Open/Closed): Чтобы добавить новый способ хранения данных — реализуй новый класс, интерфейсы трогать не надо.

LSP (Liskov Substitution): Все реализации интерфейса можно взаимозаменять — хоть база, хоть in-memory-реализация.

ISP (Interface Segregation): Лучше много маленьких интерфейсов, чем один монстр на все случаи.

DIP (Dependency Inversion): Уже обсудили выше, именно на нём всё строится.

Архитектура выстраивается так, что изменения в одной части не затрагивают другие: хочешь сменить СУБД — меняешь только инфраструктурный слой, всё остальное не трогаешь.

Domain-Driven Design и Clean Architecture

DDD и Clean Architecture отлично сочетаются: DDD заставляет центрировать проект вокруг домена, а Clean Architecture гарантирует, что домен защищён от деталей.

В DDD принято, что домен — самая стабильная и важная часть системы. Он не должен зависеть от технических подробностей — и Clean Architecture этому идеально способствует.

Интерфейсы на доменном языке:

Если твой репозиторий называется IOrderRepository, он работает с сущностями Order, а не с какими-то абстрактными строками или SQL-командами. Такой подход делает архитектуру более читабельной и поддерживаемой.

Application Layer в DDD:

По Эвансу, Application Layer — тонкая прослойка, которая координирует действия домена. Здесь удобно определять нужные интерфейсы, например, для рассылки писем или интеграции с внешними сервисами.

Обычно это выглядит так:

  • Отдельный проект Domain — вся бизнес-логика и сущности.
  • Application — сценарии, DTO, сервисы.
  • Infrastructure — все реализации и интеграции.
  • Web/API — контроллеры, UI.

Главное — все внешние зависимости подключаются через абстракции, и эти абстракции находятся внутри ядра, а не снаружи.

В итоге:

DDD помогает грамотно выделять домен и строить архитектуру вокруг него, Clean Architecture — гарантирует, что этот домен будет надёжно защищён от технических изменений. Система становится гибкой, понятной и легко тестируемой. Любые изменения — хоть в бизнес-логике, хоть в инфраструктуре — происходят локально и не разрушают всё остальное.