- PVSM.RU - https://www.pvsm.ru -

Архитектура Android-приложений… Правильный путь?

От переводчика: Некоторые термины, которые использует автор, не имеют общепринятого перевода (ну, или я его не знаю:), поэтому я решил оставить большинство на языке оригинала — они всё равно понятны и для тех, кто пишет под android, но не знает английский.
Куда писать об ошибках и неточностях, вы знаете.

За последние несколько месяцев, а также после дискуссий на Tuenti [1] с коллегами вроде @pedro_g_s [2] и @flipper83 [3] (кстати говоря, 2 крутых Android-разработчика), я решил, что имеет смысл написать заметку о проектировании Android-приложений.

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

Приступая к работе

Мы знаем, что написание качественного программного обеспечение — сложное и многогранное занятие: программа должна не только соответствовать установленным требованиям, но и быть надёжной, удобной в сопровождении, тестируемой и достаточно гибкой для добавления или изменения функций. И здесь появляется понятие “стройной архитектуры”, которое неплохо бы держать в уме при разработке любого приложения.

Идея проста: стройная архитектура основывается на группе методов, реализующих системы, которые являются:

  • Независимыми от фреймворков.
  • Тестируемыми.
  • Независимыми от UI.
  • Независимыми от БД.
  • Независимыми от любой внешней службы.

Круговая диаграмма

Кругов необязательно должно быть 4 (как на диаграмме), это просто схема; важно учитывать Правило Зависимостей: код должен иметь зависимости только во внутренние круги и не должен иметь никакого понятия, что происходит во внешних кругах.

Вот небольшой словарь терминов, необходимых для лучшего понимания данного подхода:

  • Entities (Сущности): это бизнес-логика приложения.
  • Use Cases (Методы использования): эти методы организуют поток данных в Entities и из них. Также их называют Interactors (Посредниками).
  • Interface Adapters (Интерфейс-Адаптеры): этот набор адаптеров преобразовывает данные из формата, удобного для Методов использования и Сущностей. К этим адаптерами принадлежат Presenter-ы и Controller-ы.
  • Frameworks and Drivers: место скопления деталей: UI, инструменты, фреймворки, БД и т. д.
    Для лучшего понимания обратитесь к данной статье [4] или к данному видео [5].

Наш сценарий

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

Android-архитектура

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

Стоит отметить, что каждый слой использует свою модель данных, таким образом можно достигнуть необходимой независимости (вы можете увидеть в коде, для выполнения трансформации данных небходим преобразователь данных (data mapper). Это вынужденная плата за то, чтобы модели внутри приложения не пересекались). Вот схема, как это выглядит:

3 слоя

Примечание: я не использовал внешних библиотек (кроме gson для парсинга json и junit, mockito, robolectric и espresso для тестирования) для того, чтобы пример был более наглядным. В любом случае, не стесняйтесь использовать ORM для хранения информации или любой dependency injection framework, да и вообще инструменты или библиотеки, которые делают вашу жизнь легче (помните: изобретать велосипед заново — не лучшая идея).

Presentation Layer (Слой представления)

Здесь логика связывается с Views (Представлениями) и происходят анимации. Это не что иное, как Model View Presenter (то есть, MVP [6]), но вы можете использовать любой другой паттерн вроде MVC или MVVM. Я не буду вдаваться в детали, но fragments и activities — это всего лишь views, там нету никакой логики кроме логики UI и рендеринга этого самого отображения. Presenter-ы на этом слое связываются с interactors (посредниками), что предполагает работу в новом потоке (не в UI-потоке), и передачи через коллбэки информации, которая будет отображена во view.

image

Если вы хотите увидеть крутой пример эффективного Android UI [7], который использует MVP и MVVM, взгляните на пример реализации от моего друга Pedro Gómez.

Domain Layer (Слой бизнес-логики)

Вся логика реализована в этом слое. Рассматривая проект, вы увидите здесь реализацию interactor-ов (Use Cases — методы использования).

Этот слой — модуль на чистой джаве без никаких Android-зависимостей. Все внешние компоненты используют интерфейсы для связи с бизнес-объектами.

domain layer

Data Layer (Слой данных)

Все данные, необходимые для приложения, поставляются из этого слоя через реализацию UserRepository (интерфейс находится в domain layer — слое бизнес-логики), который использует Repository Pattern [8] со стратегией, которая, через фабрику, выбирает различные источники данных, в зависимости от определенных условий.

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

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

repository implementation

Примечание: что касается кода, помня, что код — это учебный пример, я реализовал очень простой, даже примитивный кэш, используя хранения shared preferences. Помните: НЕ СТОИТ ИЗОБРЕТАТЬ ВЕЛОСИПЕД, если существуют библиотеки, хорошо решающие поставленную задачу.

Обработка ошибок

Это большая тема, в которой всегда есть, что обсудить (и здесь автор предлагает делиться своими решениями). Что до моей реализации, я использовал коллбэки, которые, на случай, если что-то случается, скажем, в хранилище данных, имеют 2 метода: onResponse() и onError(). Последний инкапсулирует исключения в wrapper class (класс-обертку) под названием “ErrorBundle”: Такой подход сопряжен с некоторыми трудностями, потому что бывают цепочки обратных вызовов один за другим, пока ошибка не выходит на presentation layer, чтобы отобразиться. Читаемость кода из-за этого может быть немного нарушена.

С другой стороны, я реализовал систему event bus, которая бросает события, если что-то не так, но такое решение похоже на использование GOTO [9], и, по-моему, иногда, если не управлять событиями предельно внимательно, можно заблудиться в цепи событий, особенно когда их одновременно бросается несколько.

Тестирование

Что касается тестирования, я применил несколько решений, в зависимости от слоя.

  • Presentation Layer: существующий android инструментал и espresso для интеграции и функционального тестирования.
  • Domain Layer: JUnit + mockito использовались для юнит-тестов.
  • Data Layer: Robolectric (так как этот слой имеет android-зависимости) + junit + для интеграции и юнит-тестов.

Покажите мне код

Думаю, в этом месте вам интересно посмотреть на код? Что ж, вот ссылка на github [10], где можно найти, что же у меня получилось. Что стоит упомянуть о структуре папок так это то, что разные слои представлены разными модулями:

  • presentation: это android-модуль для presentation layer.
  • domain: java-модуль без android-зависимостей.
  • data: android-модуль, откуда поступают все данные.
  • data-test: тесты для data layer. Из-за определённых ограничений, при использовании Robolectric мне пришлось использовать отдельный java-модуль.

Заключение

Дядя Боб сказал: “Архитекрута — это намерения, а не фреймворки”, и я полностью с ним согласен. Конечно, есть разные способы реализовать какую-либо вещь, и я уверен, что вы, как и я, каждый день сталкиваетесь с трудностями в этой области, но используя данные приёмы, вы сможете быть уверены, что ваше приложение будет:

  • Простым в поддержке.
  • Простым в тестировании.
  • Составлять единое целое,
  • Будучи разделённым.

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

Ссылки и источники

  1. Код: https://github.com/android10/Android-CleanArchitecture [10]
  2. The clean architecture от Дяди Боба [4]
  3. Архитекрута — это намерения, а не фреймворки [11]
  4. Model View Presenter [6]
  5. Repository Pattern от Martin Fowler [8]
  6. Презентация по Android Design Patterns [12]

Автор: Bringoff

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android-development/83038

Ссылки в тексте:

[1] Tuenti: http://corporate.tuenti.com/en/dev/blog

[2] @pedro_g_s: https://twitter.com/pedro_g_s

[3] @flipper83: https://twitter.com/flipper83

[4] данной статье: http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

[5] данному видео: http://vimeo.com/43612849

[6] MVP: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter

[7] эффективного Android UI: https://github.com/pedrovgs/EffectiveAndroidUI/

[8] Repository Pattern: http://martinfowler.com/eaaCatalog/repository.html

[9] GOTO: http://www.drdobbs.com/jvm/programming-with-reason-why-is-goto-bad/228200966

[10] вот ссылка на github: https://github.com/android10/Android-CleanArchitecture

[11] Архитекрута — это намерения, а не фреймворки: http://www.infoq.com/news/2013/07/architecture_intent_frameworks

[12] Презентация по Android Design Patterns: http://www.slideshare.net/PedroVicenteGmezSnch/

[13] Источник: http://habrahabr.ru/post/250659/