Какой он, код надежного приложения?

1
6129

Перевод статьи Daniel Oliveira о том, как писать код надежного приложения, используя подход Роберта К. Мартина.

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

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

Архитектура программного обеспечения предлагает модели и правила для определения структур (например, классов, интерфейсов и структур) в системе и того, как они взаимодействуют друг с другом. Эти правила способствуют повторному использованию и разделению задач этих элементов. Это позволяет легко изменять детали реализации, такие как СУБД или интерфейсная библиотека. Рефакторинг и исправление ошибок имеют минимальное влияние. А добавление новых функций становится простым.

В этой статье я объясню модель архитектуры, предложенную в 2012 году Робертом К. Мартином, дядей Бобом. Он является автором такой классики как «Чистый код» и «Идеальный программист». В октябре этого года он выпустит еще одну книгу « Чистая архитектура».

Модель имеет то же название, что и книга, и она построена на простых понятиях:

Разделите состав системы на отдельные слои с четко определенными ролями. И изолируйте отношения между сущностями в разных слоях. Нет ничего нового в разделении вашего надежного приложения на слои. Но я выбрал этот подход, поскольку он был самым простым, чтобы понять и выполнить. И это делает тестирование варинатов использование предельно простым.
Мы просто должны убедиться, что Interactors работают правильно, и мы готовы продолжать. Не волнуйтесь, если слово «Interactors» кажется вам незнакомым, мы скоро узнаем о них.

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

Сущности

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

Use cases — прецеденты

Прецеденты представлены в диаграмме как бизнес правила надежного приложения. Они представляют все варианты его использования. Каждый элемент этого слоя обеспечивает интерфейс к внешнему слою и действует как хаб, который взаимодействует с другими частями системы. Они отвечают за полное выполнение прецедентов и обычно называются Interactors.
В нашем примере прецендентами являтся incrementing или decrementing нашего counter:

Обратите внимание, что функция ChangeCounterInteractor принимает параметр типа CounterGateway. Мы обсудим его позже. Но пока можно сказать, что Gateways — это то, что стоит между прецендатами и следующим слоем.

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

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

Обратите внимание, что компонент не использует экземпляр Counter для представления его значения, а вместо него CounterViewData. Мы сделали это изменение, чтобы отделить представление логики от бизнес-данных. Примером этого является логика выставки счетчика на основе режима просмотра (римские или индусско-арабские цифры). Реализация CounterViewData:

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

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

Инструменты, используемые вашей системой для взаимодействия с внешним миром, составляют самый внешний слой. Обычно мы не пишем код в этом слое, который содержит такие библиотеки, как React / Redux, API-интерфейсы браузера и т. д.

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

Разделение на слои имеет две главные цели. Одна из них — четко определить обязанности каждой части системы. Другая — убедиться, что каждый из них выполняет свою роль независимо друг от друга, настолько насколько возможно. Чтобы это случилось, существует правило, которое определяет то, как элементы должны зависеть друг от друга:

Элемент не должен зависеть от какого-либо элемента, который находится в другом слое.

Например, элемент в слое «Use cases» не может знать ничего о каком-либо классе или модуле, связанном с GUI или постоянством данных. Аналогично, Сущность не может знать в каких Use cases она используется.
Это правило может вызвать у вас вопросы. Например, возьмите use case. Он запускается в результате взаимодействия пользователя с интерфейсом. Его выполнение включает обновление в некоторых постоянных хранилищах данных, таких как база данных. Как Interactor может выполнять соответствующие вызовы в процедурах обновления, не зависимо от адаптера интерфейса, который отвечает за сохранение данных?


Ответ находится в элементе, который мы упоминали ранее: Gateways. Они отвечают за установление интерфейсов, необходимых для работы Use cases. После того как интерфейс установлен, Адаптеры интерфейса должны выполнить свою часть договора, как показано на диаграмме выше. У нас есть интерфейс CounterGateway и реализация с использованием Redux:

Возможно, это вам не понадобится

Конечно, это приложение-пример было несколько сложнее для надежного приложения счетчика increment / decrement. И я хотел бы пояснить, что вам не нужно все это для небольшого проекта или прототипа. Но поверьте мне, по мере того, как ваше приложение станет больше, вы захотите максимизировать повторное использование и управляемость. Хорошая архитектура программного обеспечения делает проекты устойчивыми к прогрессу.

Хорошо … И что?

В этой статье мы раскрыли подход к развязке сущностей наших систем. Это упрощает их управляемость и расширение. Например, для создания того же надежного приложения с использованием Vue.js нам нужно будет только переписать компоненты CounterPage и CounterWidget. Исходный код примера приложения приведен здесь.

Другие материалы по теме написания кода надежного приложения:

6 простых советов по написанию чистого кода