[Из песочницы] Чистая архитектура

От переводчика: данная статья написана Дядюшкой Бобом в августе 2012 года, но, на мой взгляд, вполне актуальна до сих пор.

23a0de4d93d747c89f1e216077c2d604.jpg

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

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

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

Правило Зависимостей


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

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

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

Сущности


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

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

Сценарии


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

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

Мы, однако, ожидаем, что изменения в работе приложения повлияет на Cценарии. Если будут какие-либо изменения в поведении приложения, то они несомненно затронут код в данном слое.

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


Программное обеспечение в этом слое представляет собой набор адаптеров, которые преобразуют данные из формата наиболее удобным для Сценариев и Сущностей, в формат наиболее удобный для дальнейшего использования, например в БД. Именно это слой, например, будет полностью содержать архитектуру MVC. Модели являются скорее всего структурами данных, которые передаются от контроллеров к Сценариям, а затем обратно из Сценариев к Представлениям.

Точно так же, данные преобразуются, в этом слое, из формы наиболее удобным для Сценариев и Сущностей, в форму, наиболее удобной для постоянного хранения, например в базе данных. Код, находящийся внутри этого круга не должен знать что-либо о БД. Если БД — это SQL база данных, то любые SQL-инструкции не должны быть использованы на этом уровне.

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


Наружный слой обычно состоит из фреймворков, БД, UI и т.д. Как правило, в этом слое не пишется много кода, за исключением кода, который общается с внутренними кругами.

Это слой скопления деталей. Интернет — деталь, БД — деталь, мы держим эти штуки снаружи для уменьшения их влияния.

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


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

Пересечение границ.


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

Обычно мы решаем это кажущееся противоречие с помощью Принципа Инверсии Зависимостей.

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

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

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


Обычно данные, которые пересекают границы — это просто структуры данных. Вы можете использовать базовые структуры или, если хотите, Data Transfer Objects (DTO — один из шаблонов проектирования, используется для передачи данных между подсистемами приложения). Или данные могут быть просто аргументами вызова функций. Или вы можете упаковать его в хэш-таблицу или в объект. Важно, чтобы передаваемые структуры данных были изолированными при передаче через границы. Мы не хотим жульничать и передавать Сущность или строки БД. Мы не хотим, чтобы структуры данных имели какие-либо зависимости, нарушающие Правило Зависимостей.

Например, многие фреймворки (ORM) в ответ на запрос к БД возвращают данные в удобном формате. Мы могли бы назвать это RowStructure. Мы не хотим передавать эту структуру через границу. Это было бы нарушением Правила Зависимостей поскольку в этом случае внутренний круг получает информацию о внешнем круге.

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

Заключение


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

P.S. Данная статья переведена как подготовка к более поздней и чуть более практически ориентированной статье о чистой архитектуре на Go. Интересно?

© Habrahabr.ru