Микросервисы
June 12, 2023

Чистая архитектура (от дяди Боба)

За последние несколько лет мы увидели целый ряд идей, касающихся архитектуры систем. К ним относятся:

Хотя все эти архитектуры несколько различаются в деталях, они очень похожи. Все они преследуют одну и ту же цель, которая заключается в разделении проблем. Все они достигают такого разделения, разделяя программное обеспечение на слои. У каждого есть по крайней мере один уровень для бизнес-правил, а другой - для интерфейсов.

Каждая из этих архитектур создает системы, которые:

  1. Не зависят от фреймворков. Архитектура не зависит от наличия какой-либо библиотеки многофункционального программного обеспечения. Это позволяет вам использовать такие фреймворки в качестве инструментов, вместо того, чтобы втискивать вашу систему в их ограниченные ограничения.
  2. Тестируемые. Бизнес-правила могут быть протестированы без пользовательского интерфейса, базы данных, веб-сервера или любого другого внешнего элемента.
  3. Независимы от пользовательского интерфейса. Пользовательский интерфейс можно легко изменить, не изменяя остальную часть системы. Например, веб-интерфейс можно заменить консольным интерфейсом без изменения бизнес-правил.
  4. Независимы от базы данных. Вы можете заменить Oracle или SQL Server на Mongo, BigTable, CouchDB или что-то еще. Ваши бизнес-правила не привязаны к базе данных.
  5. Независимы от любого внешнего агента. На самом деле ваши бизнес-правила просто ничего не знают о внешнем мире.

Диаграмма в верхней части этой статьи представляет собой попытку объединить все эти архитектуры в единую действенную идею.

Правило зависимости

Концентрические круги представляют различные области программного обеспечения. В общем, чем дальше вы продвигаетесь, тем более высоким уровнем становится программное обеспечение. Внешние круги - это механизмы (реализация). Внутренние круги - это политика.

Основным правилом, которое заставляет эту архитектуру работать, является правило зависимостей. Это правило гласит, что зависимости исходного кода могут указывать только внутрь. Ничто во внутреннем круге не может вообще ничего знать о чем-то во внешнем круге. В частности, имя чего-либо, объявленного во внешнем круге, не должно упоминаться кодом во внутреннем круге. Это включает в себя функции, классы. переменные или любой другой именованный программный объект.

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

Сущности (Entities)

Сущности инкапсулируют бизнес-правила в масштабах предприятия. Сущность может быть объектом с методами или набором структур данных и функций. Это не имеет значения, если сущности могут использоваться многими различными приложениями на предприятии.

Если у вас нет предприятия и вы просто пишете одно приложение, то эти сущности являются бизнес-объектами приложения. Они содержат самые общие и высокоуровневые правила. Они с наименьшей вероятностью изменятся, когда что-то изменится извне. Например, вы не ожидаете, что эти объекты будут затронуты изменением навигации по страницам или безопасности. Никакие операционные изменения в каком-либо конкретном приложении не должны влиять на уровень сущности.

Варианты использования (Usecases)

Программное обеспечение на этом уровне содержит бизнес-правила, специфичные для конкретного приложения. Он инкапсулирует и реализует все варианты использования системы. Эти варианты использования организуют поток данных к объектам и от них и направляют эти объекты на использование своих корпоративных бизнес-правил для достижения целей варианта использования.

Изменения в этом слое не должны влиять на сущности. Также на этот уровень не должны влиять внешние изменения (изменение реализации), такие как база данных, пользовательский интерфейс или какие-либо общие фреймворки. Этот уровень изолирован от подобных проблем.

Однако мы ожидаем, что изменения в работе приложения повлияют на варианты использования и, следовательно, на программное обеспечение на этом уровне. Если детали варианта использования изменятся, то некоторый код на этом уровне, безусловно, будет затронут.

Интерфейсные адаптеры

Программное обеспечение на этом уровне представляет собой набор адаптеров, которые преобразуют данные из формата, наиболее удобного для вариантов использования и сущностей, в формат, наиболее удобный для какого-либо внешнего агента, такого как база данных или интернет. Например, именно этот слой будет полностью содержать архитектуру MVC графического интерфейса пользователя. Все представители (presenters), представления (views) и контроллеры (controllers) принадлежат этому слою. Модели, представляют собой просто структуры данных, которые передаются от контроллеров к вариантам использования, а затем обратно от вариантов использования к представителям (presenters) и представлениям (views).

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

Также на этом уровне находится любой другой адаптер, необходимый для преобразования данных из некоторого внешнего формата, такой как внешняя служба, во внутренний формат, используемый вариантами использования и сущностями.

Фреймворки и драйверы

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

На этом уровне хранятся все детали. Веб - это деталь. База данных - это деталь. Мы держим эти вещи снаружи, где они могут нанести небольшой вред.

Только четыре круга?

Нет, круги схематичны. Вы можете обнаружить, что вам нужно больше, чем четыре. Нет такого правила, которое гласило бы, что у вас всегда должны быть только эти четыре. Однако правило Зависимости применяется всегда. Зависимости исходного кода всегда указывают внутрь. По мере продвижения внутрь уровень абстракции повышается. Самый внешний круг - это конкретная деталь низкого уровня. По мере продвижения внутрь программное обеспечение становится все более абстрактным и инкапсулирует политики более высокого уровня. Самый внутренний круг является наиболее общим.

Пересекая границы

В правом нижнем углу диаграммы приведен пример того, как мы пересекаем границы круга. На нем показаны Контроллеры (Controllers) и Представители (Presenters), взаимодействующие с Вариантами использования (Usecases) на следующем уровне. Обратите внимание на поток управления. Он начинается в контроллере, проходит через вариант использования, а затем завершается выполнением в представителе. Обратите внимание также на зависимости исходного кода. Каждый из них указывает внутрь, на варианты использования.

Обычно мы разрешаем это очевидное противоречие, используя принцип инверсии зависимостей. Например, на таком языке, как Java, мы бы расположили интерфейсы и отношения наследования таким образом, чтобы зависимости исходного кода противодействовали потоку управления только в правильных точках границы.

Например, рассмотрим, что вариант использования должен вызывать представителя (presenter). Однако этот вызов не должен быть прямым, потому что это нарушило бы Правило зависимости: никакое имя во внешнем круге не может быть упомянуто во внутренним круге. Итак, у нас есть вариант использования, вызывающий интерфейс (показанный здесь как порт вывода варианта использования) во внутреннем круге, и пусть представитель (presenter) во внешнем круге реализует его.

Тот же метод используется при пересечении любых границ во всех архитектурах. Мы используем преимущества динамического полиморфизма для создания зависимостей исходного кода, которые противостоят потоку управления, чтобы мы могли соответствовать правилу зависимостей независимо от того, в каком направлении движется поток управления.

Какие данные пересекают границы

Обычно данные, которые пересекают границы, представляют собой простые структуры данных. Вы можете использовать базовые структуры или простые объекты передачи данных, если хотите. Или данные могут быть просто аргументами в вызовах функций. Или вы можете упаковать его в хэш-карту или сконструировать его в объект. Важно то, что изолированные, простые структуры данных передаются через границы. Мы не хотим обманывать и передавать сущности или строки базы данных. Мы не хотим, чтобы структуры данных имели какую-либо зависимость, которая нарушает правило зависимости.

Например, многие фреймворки баз данных возвращают удобный формат данных в ответ на запрос. Назовём это RowStructure. Нам не стоит передавать эту структуру строк внутрь через границу, так как это бы нарушило правило зависимости, потому что в этом случае внутренний круг будет что-то знать о внешнем круге.

Поэтому, когда мы передаем данные через границу, они всегда находятся в формате, наиболее удобном для внутреннего круга.

Заключение

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

Источник: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html