clean architecture что это

Чистая архитектура

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

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

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

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

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

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

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

Сущности

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

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

Сценарии

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

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

Источник

Clean Architecture глазами Python-разработчика

Привет! Меня зовут Евгений, я Python-разработчик. Последние полтора года наша команда стала активно применять принципы Clean Architecture, уходя от классической модели MVC. И сегодня я расскажу о том, как мы к этому пришли, что нам это дает, и почему прямой перенос подходов из других ЯП не всегда является хорошим решением.

Python является моим основным инструментом разработки уже более семи лет. Когда у меня спрашивают, что мне больше всего в нем нравится, я отвечаю, что это его великолепная читаемость. Первое знакомство началось с прочтения книги «Программируем коллективный разум». Меня интересовали алгоритмы, изложенные в ней, но все примеры были на еще не знакомом мне тогда языке. Это было не обычно (Python тогда еще не был мейнстримом в машинном обучении), листинги чаще писались на псевдокоде или с использованием диаграмм. Но после быстрого введения в язык, я по достоинству оценил его лаконичность: все было легко и понятно, ничего лишнего и отвлекающего внимание, только самая суть описываемого процесса. Основная заслуга этого — удивительный дизайн языка, тот самый интуитивно понятный синтаксический сахар. Эта выразительность всегда ценилась в сообществе. Чего только стоит «import this», обязательно присутствующий на первых страницах любого учебника: он как невидимый надзирающий смотрит, постоянно оценивает твои действия. На форумах стоило новичку использовать как-нибудь CamelCase в названии переменной в листинге, так сразу ракурс обсуждения смещался в сторону идиоматичности предложенного кода с отсылками на PEP8.

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

Тем не менее, Python, хоть и мощный, но всего лишь инструмент, который позволяет писать выразительный самодокументируемый код, но не гарантирует этого, как не гарантирует этого и соблюдение PEP8. Когда наш, казалось бы, простой интернет-магазин на Django начинает приносить деньги и, как следствие, накачиваться фичами, в один прекрасный момент мы понимаем, что он не такой уж и простой, а внесение даже элементарных изменений требует все больших и больших усилий, а главное, что эта тенденция все нарастает. Что случилось, и когда все пошло не так?

Читайте также:  при каком значении шума условия труда относятся к опасным условиям труда в дб

Плохой код

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

Чистая архитектура

Избежать данной проблемы должна выбранная архитектура приложения, и мы не первые, кто с этим столкнулся: в сообществе Java давно идет дискуссия о создании оптимальной конструкции приложения.

Еще в 2000-ом году Роберт Мартин (так же известный как Дядюшка Боб) в статье «Принципы дизайна и проектирования» собрал воедино пять принципов проектирования ООП приложений под запоминающейся аббревиатурой SOLID. Данные принципы были хорошо приняты сообществом и вышли далеко за рамки экосистемы Java. Тем не менее они носят весьма абстрактный характер. Позже было несколько попыток выработать общий дизайн приложения, базирующийся на SOLID-принципах. К ним относится: «Гексагональная архитектура», «Порты и адаптеры», «Луковичная архитектура» и у всех у них много общего хоть и разные детали реализации. А в 2012 году вышла статья того же Роберта Мартина, где он предложил свой вариант под названием «Чистая архитектура».

По версии Дядюшки Боба, архитектура — это в первую очередь «границы и барьеры», необходимо четко понимать потребности и ограничивать программные интерфейсы для того, чтобы не потерять контроль за приложением. Для этого программа делится на слои. Обращаясь из одного слоя к другому, можно передавать только данные (в качестве данных могут выступать простые структуры и DTO объекты) — это правило границ. Еще одна наиболее часто цитируемая фраза, о том, что «приложение должно кричать» — означает, что главным в приложении является не используемый фреймворк или технология хранения данных, а то, что собственно это приложение делает, какую функцию оно выполняет — бизнес-логика приложения. Поэтому слои имеют не линейную структуру, а обладают иерархией. Отсюда еще два правила:

Эти три правила и есть суть Clean Architecture:

Роберт Мартин рассматривает предложенную им схему, состоящую из четырех слоев. В рамках данной статьи я не стану еще раз ее разбирать. Переадресую лишь заинтересовавшихся к оригинальной статье, а также к разбору на хабре. Также рекомендую отличную статью «Заблуждения Clean Architecture».

Реализация на Python

Это теория, примеры же практического применения можно найти в оригинальной статье, докладах и книге Роберта Мартина. Они опираются на несколько распространенных шаблонов проектирования из мира Java: Adapter, Gateway, Interactor, Fasade, Repository, DTO и др.

Ну а что же Python? Как я уже говорил, в Python-сообществе ценится лаконичность.То, что прижилось у других, далеко не факт, что приживется у нас. Первый раз я обратился к данной теме три года назад, тогда материалов по теме использования Clean Architecture в Python встречалось не много, но первой же ссылкой в Гугле был проект Леонардо Джордани: автор подробно расписывает процесс создания API для сайта по поиску недвижимости по методике TDD, применяя Clean Architecture.
К сожалению, несмотря на скрупулезное объяснение и следование всем канонам Дядюшки Боба, данный пример скорее отпугивает.

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

Есть и более серьезная проблема, если не читать статью, а сразу начать изучать проект, то в нем достаточно сложно разобраться. Рассмотрим реализацию бизнес-логики:

Класс RoomListUseCase, реализующий бизнес-логику (не очень похоже на бизнес-логику, правда?) проекта, инициализируется объектом repo. Но что такое repo? Конечно, из контекста мы можем понять, что repo реализует шаблон Repository для доступа к данным, если посмотрим тело RoomListUseCase, то поймем, что он должен иметь один метод list, на вход которого подается список фильтров, что на выходе — не понятно, нужно смотреть в ResponseSuccess. А если сценарий будет более сложный, с множественным обращением к источнику данных? Получается, чтобы понять, что такое repo, можно только обратившись к реализации. Но где она находится? Она лежит в отдельном модуле, который никак не связан с RoomListUseCase. Таким образом, чтобы понять, что происходит, нужно подняться на верхний уровень (уровень фреймворка) и посмотреть, что же подается на вход класса при создании объекта.

Можно подумать, что я перечисляю недостатки динамической типизации, но это не совсем так. Именно динамическая типизация позволяет писать выразительный и компактный код. На ум приходит аналогия с микросервисами, когда мы распиливаем монолит на микросервисы, конструкция приобретает дополнительную жесткость, так как внутри микросервиса может твориться все, что угодно (ЯП, фреймворки, архитектура), но он обязан соответствовать объявленному интерфейсу. Так и тут: когда мы раздели наш проект на слои, связи между слоями должны соответствовать контракту, при этом внутри слоя контракт не является обязательным. Иначе, в голове нужно держать достаточно большой контекст. Помните, я говорил, что проблема плохого кода заключается в зависимостях, так вот, без явного интерфейса мы опять скатываемся туда, от чего хотели уйти — к отсутствию явных причинно-следственных связей.

В данном примере интерфейс repo является неотъемлемой частью интерфейса RoomListUseCase, как и метод execute — так работает инверсия зависимостей. Фактически мы можем распространять пакет с бизнес-логикой отдельно от самого приложения, так как он, не имеет зависимостей внутри проекта. Когда работаем со слоем бизнес-логики мы не обязаны помнить о существовании других слоев. Но, чтобы его использовать, необходимо реализовать нужные интерфейсы, а repo один из них.

В общем, в тот раз я отказался от Clean Architecture в новом проекте, опять применив классический MVC. Но, набив очередную порцию шишек, вернулся к этой идее через год, когда, мы наконец, стали запускать сервисы на Python 3.5+. Как известно, он принес аннотации типов и дата-классы: два мощных инструмента описания интерфейсов. Опираясь на них, я набросал прототип сервиса, и результат уже был намного лучше: слои перестали рассыпаться, не смотря на то, что кода все еще было много, особенно при интеграции с фреймворком. Но этого было достаточно, чтобы начать применять данный подход в небольших проектах. Постепенно начали появляться фреймворки, ориентированные на максимальное использование аннотации типов: apistar (сейчас starlette), moltenframework. Сейчас распространена связка pydantic/FastAPI, и интеграция с такими фреймворками стала намного проще. Вот так бы выглядел вышеуказанный пример restomatic/services.py:

RoomListUseCase — класс, реализующий бизнес-логику проекта. Не стоит обращать внимание на то, что все, что делает метод show_rooms это обращение к RoomStorage (данный пример придумал не я). В реальной жизни здесь также может быть расчет скидки, ранжирование списка на основе рекламных объявлений и т.д. Тем не менее модуль является самодостаточным. Если мы захотим воспользоваться данным сценарием в другом проекте, нам придется реализовать RoomStorage. И что для этого нужно, здесь отлично видно прямо из модуля. В отличие от прошлого примера, такой слой является самодостаточным, и при изменении не обязательно держать в голове весь контекст. Из несистемных зависимостей только pydantic, почему, станет понятно в модуле подключения фреймворка. Отсутствие зависимостей, еще один способ повышения читаемости кода, не дополнительного контекста, даже начинающий разработчик сможет понять, что делает данный модуль.

Сценарий бизнес-логики не обязательно должен быть классом, ниже пример аналогичного сценария в виде функции:

А вот как выглядит подключение к фреймворку:

С помощью функции get_use_case в FastAPI реализуется паттерн Dependency Injection. Нам не нужно заботиться о сериализации данных, всю работу выполняет FastAPI в связке с pydantic. К сожалению, не всегда формат данных бизнес-логики подходит для прямой трансляции в рест

Читайте также:  что делать если блютуз колонка не подключается к ноутбуку

Источник

Подробный гайд по разработке Android-приложений с помощью Clean Architecture

Данный туториал поможет вам разобраться в очень полезном подходе по разработке приложений Clean Architecture.

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

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

Что такое Clean Architecture?

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

Как правило, в Clean Architecture код разделен на несколько уровней, по структуре схожей со структурой обычного лука, с одним правилом зависимости: внутренний уровень не должен зависеть от каких-либо внешних уровней. Это означает, что зависимости должны указываться внутри каждого уровня, чтобы не было зависимостей между уровнями (слоями).

Далее приведена визуализация вышесказанного:

Clean Architecture, делает ваш код:

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

Что это значит для Android?

Как правило, ваше приложение имеет произвольное количество уровней (слоев), однако если вам не нужна бизнес-логика Enterprise, то скорее всего у вас будет только 3 уровня:

Уровень реализации – это место где описывается основная структура приложения. Сюда входит любое содержимое Android такое, как: создание операций и фрагментов, отправка намерений, и другой структурный код наподобие сетевого кода и кода базы данных.

Целью уровня интерфейса является обеспечение взаимодействия/коммуникации между уровнем реализации и уровнем бизнес-логики.

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

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

Почему преобразование является обязательным?

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

Еще одним примером может быть следующее: давайте скажем, что объект Cursor, принадлежит ContentProvider во внешнем уровне базы данных. Значит что внешний уровень, в первую очередь преобразует его в бизнес-модель внутреннего уровня, а затем уже отдаст его на обработку соответствующему уровню бизнес-логики.

Внизу статьи я добавлю больше ресурсов для изучения данного вопроса. Сейчас же мы уже знаем об основных принципах подхода Clean Architecture, а значит давайте «замараем» руки кодом. Далее я покажу вам как создать рабочий функционал с использованием Clean Architecture.

Как начать создание Чистых приложений?

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

Первые шаги по написанию новых прецедентов

Этот раздел будет объяснять весь необходимый вам код, для создания своих прецедентов с помощью подхода Clean Architecture, так скажем поверх шаблона, приведенного в предыдущем разделе. Прецедент (или Use Case) – это просто некоторый изолированный функционал приложения. Прецедент может быть запущен или не может быть запущен пользователем (например, по нажатию пользователя).

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

Структура

Общая структура Android-приложения выглядит, как показано ниже:

Внешний уровень

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

Интерфейс пользователя (UI) – Это то, куда вы помещаете все ваши Операции, Фрагменты, Адаптеры и любой другой Android-код, связанный с интерфейсом пользователя.

Хранилище – Отдельный код для базы данных, который реализует интерфейс наших Интеракторов, используемых для доступа к базе данных и для хранения данных. Например, сюда включается Поставщик контента или ORM-ы такие, как DBFlow.

Сеть – Вещи подобные Retrofit отправляются сюда.

Средний уровень

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

Представители (presenter) – представители обрабатывают события от UI (например, клик пользователя), и чаще всего работают как callback-и из внутренних уровней (Interactors).

Конвертеры – Преобразуют объекты, которые ответственны за конвертацию моделей внутреннего уровня в модели внешнего уровня и в обратном порядке.

Внутренний уровень

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

Классы и объекты данного уровня никак не оповещаются, что будут запущены именно в Android-приложении, поэтому их легко можно перенести на любую JVM.

Interactors – классы, которые фактически содержат код вашей бизнес-логики. Они запускаются в фоновом режиме и передают события верхнему уровню с помощью callback-ов. Их также называют Прецедентами (UseCases) в некоторых проектах, возможно, такое называние им больше подходит. Наличие множества небольших Interactor-классов в ваших проектах для решения определенных задач считается нормой. Это полностью соответствует принципу единственной ответственности и, как мне кажется, проще для восприятия и понимания.

Модели – это ваши бизнес-модели, которыми вы управляете в своей бизнес-логике.

Репозитории (repositories ) – данный пакет включает в себя только те интерфейсы, которые реализованы с помощью базы данных или каких-либо других внешних уровней. Эти интерфейсы используются Interactor-классы для доступа и хранения данных. Это также называется паттерн Repository.

Исполнитель (executor) – данный пакет содержит код для запуска Interactor-классов в фоновом режиме с помощью рабочего потока-исполнителя. Чаще всего вам не придется изменять этот пакет.

Простой пример

В этом примере, нашим прецедентом будет: «Приветствие пользователя сообщением, когда приложение запущено и данное сообщение помещено в базу данных.» Данный пример будет наглядной демонстрацией того, как создать следующие пакеты, необходимые для корректной работы нашего прецедента:

Первые два пункта относятся к внешнему уровню, в то время как последний относится к внутреннему/основному уровню.

Пакет представления ответственен за все, что связано с отображением вещей на экране, он содержит весь стек шаблона проектирования MVP. Это означает, что он содержит в себе как UI, так и Presenter-пакеты, даже если они относятся к разным уровням.

Читайте также:  dpsp epilier что за фирма

Отлично – меньше слов, больше кода!

Создание нового Interactor-а (внутренний/основной уровень)

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

Итак, давайте начнем создание Interactor-а. Interactor – это то место, где располагается основная логика работы нашего прецедента. Все Interactor-ы запускаются в фоновом потоке, поэтому не должно быть никакого воздействия на производительность интерфейса пользователя. Давайте создадим новый Interactor с приятным названием «WelcomingInteractor».

Callback отвечает за общение с интерфейсом пользователя (UI) в основном потоке, мы помещаем его в интерфейс Interactor-а, поэтому нет необходимости в подобном названии «WelcomingInteractorCallback», чтобы отличать его от других callback-ов. Теперь реализуем логику получения сообщения. Давайте скажем, что у нас есть интерфейс MessageRepository, в котором будет наше сообщение приветствия.

Далее реализуем этот интерфейс вместе с нашей бизнес-логикой. Важно, чтобы реализация наследовала AbstractInteractor, который отвечает за запуск нашего интерфейса в фоновом потоке.

Что же, взглянем на зависимости, создаваемые нашим Interactor:Этот фрагмент кода, пытается получить сообщение, затем переслать его или же отправить сообщение об ошибке интерфейсу пользователя, чтобы он отобразил сообщение или ошибку. Для этого мы уведомляем интерфейс пользователя с помощью нашего callback-а, который по факту и будет Presenter-ом. Собственно, в этом и заключается суть всей нашей бизнес-логики. Все что нам остается – это построить структурные зависимости.

Как вы можете заметить, здесь нет ни одного упоминания о каком-либо Android-коде. Это и есть главное преимущество данного подхода. Также вы можете увидеть, что пункт: «Независимость от фреймворков» все также соблюдается. Кроме того, нам не нужно отдельно определять интерфейс пользователя или базу данных, мы просто вызываем методы интерфейса, которые кто-то, где-то на внешнем уровне реализует. Следовательно, мы независим от UI и независим от Базы данных.

Тестирование нашего Interactor-а

На данный момент мы можем запустить и начать тестирование нашего Interactor-а без запуска эмулятора. Поэтому давайте напишем простой Junit-тест, чтобы убедиться, что все работает:

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

Создание уровня представления

Код представления относится ко внешнему уровню подхода Clean Architecture. Уровень представления состоит из структурно зависимого кода, который отвечает за отображение интерфейса пользователя, собственно, пользователю. Мы будем использовать класс MainActivity для отображения нашего приветствующего сообщения пользователю, когда приложение возобновляет свою работу.

Давайте начнем с создания интерфейса нашего Presenter и Отображения (View). Единственное, что должно делать наше отображение – это отображать приветствующее сообщение:

Итак, как и где мы запускаем наш Interactor, когда приложение возобновляет работу? Все, что не имеет строгой привязки к отображению, должно помещаться в класс Presenter. Это помогает достичь принципа Разделения ответственности и предотвратить классы Операций от чрезмерного увеличения размера кода. Сюда включается весь код, который работает с Interactor-ми.

В нашем классе MainActivity мы переопределяем метод onResume():

Все Presenter-объекты реализуют метод resume(), при наследовании BasePresenter.

Примечание: Самые внимательные читатели могли заметить, что я добавил Android-методы жизненного цикла в интерфейс BasePresenter в качестве вспомогательных методов, хотя сам Presenter находится на более низком уровне. Наш Presenter должен знать все на уровне UI, к примеру, что что-то на этом уровне имеет жизненный цикл. Тем не менее, здесь я не указываю конкретное событие, так как каждый UI для конкретного пользователя может отрабатывать разные события, в зависимости от действий пользователя. Представьте, я назвал его onUIShow() вместо onResume(). Теперь все хорошо, верно? 🙂

Мы запускаем Interactor внутри класса MainPresenter в методе resume():

Метод execute() просто выполняет метод run() объекта WelcomingInteractorImpl в фоновом потоке. Метод run() вы можете увидеть в разделе Создание нового Interactor.

Вы также могли заметить, что поведение Interactor-а схоже с поведением класса AsyncTask. Так как вы предоставляете все необходимое для его запуска и выполнения. Тут вы можете спросить, а почему мы не используем AsyncTask? Да потому что это Android-код, и вам нужен будет эмулятор для его запуска и тестирования.

Мы предоставляем несколько вещей нашему Interactor-у:

Примечание: Поскольку существует множество вещей, которые необходимо связывать каждый раз с Interactor-ом, то будет полезен следующий фреймворк для внедрения зависимостей: Dagger 2 (и подобные ему). Но я его использую здесь не для того чтобы что-то упростить. Свою структуру вы вольны сами выбирать, и то какие фреймворки использовать также ваше право.

Что же касается this, то MainPresenter класса MainActivity действительно реализует callback-интерфейс:

И далее то, как мы слушаем события от Interactor-а. Следующий код взят из MainPresenter:

Небольшие фрагменты View в этом куске кода – это и есть наш класс MainActivity, который реализует данный интерфейс:

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

И вот этого всего будет вполне достаточно для уровня представления.

Создание уровня хранения

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

Для сложных данных вы можете использовать ContentProviders или такие ORM-инструменты, как: DBFlow. Если вам необходимо получать данные из Интернета, то Retrofit поможет вам. Если же вам необходимо простое хранилище по принципу ключ-значение, то вы можете использовать SharedPreferences. Запомните, вы должны использовать верный инструмент для работы.

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

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

Краткий итог

Пример из этой статьи вы можете найти в данном git-репозитории. Обобщенная версия порядка вызова классов выглядит следующим образом:

Очень важно запомнить порядок контроля:

Внешний — Средний — Основной — Внешний — Основной — Средний — Внешний

Очень часто внешний уровень задействуется несколько раз в течении одного случая использования. Во время использования приложения вам необходимо что-то отображать, что-то хранить и получать к чему-то доступ из сети, в нашем порядке контроля внешний уровень задействуется, как минимум, три раза.

Заключение

Для меня подход Clean Architecture является лучшим способом разработки приложений. Разделенный код помогает сфокусироваться на определенных задачах, без большого нагромождения кода, то есть вы не страдаете лишней функциональной избыточностью. В конце концов, я думаю, что этот подход соответствует принципам SOLID, однако к нему придется немного привыкнуть, чтобы использовать весь его потенциал. Именно по этой причине я и написал все это, чтобы помочь людям лучше понять Clean Architecture, за счет пошаговых примеров.

Также я создал и предоставил доступ к исходному коду приложения «Отслеживание затрат», используя подход Clean Architecture для того, чтобы показать, как будет выглядеть код в реальном приложении. Ничего инновационного в данном приложении нет, но я считаю, что этого будет достаточно, чтобы пояснить все то, о чем было написано в данной статье. Кроме того, данное приложение содержит более сложные примеры, с которыми вы можете самостоятельно ознакомиться. А найти его вы сможете здесь.

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

Дополнительная информация

Этот гайд является расширением данной прекрасной статьи. Разница заключается в том, что я использовал обычную Java в своих примерах, и вовсе не для того, чтобы как-то усложнить примеры. Если вам приятнее наблюдать примеры подхода Clean Architecture на RxJava, то можете посмотреть их здесь.

Источник

Сказочный портал