Источник: Nuances of Programming
Даже до того, как я начал специализироваться на Android, меня, как разработчика, всегда восхищал хорошо структурированный, чистый и понятный в целом код.
“Задача архитектуры программного обеспечения — минимизация человеческих ресурсов, необходимых для создания и обслуживания требуемой системы”
Однако не всегда просто написать такой код, который легко тестировать и поддерживать, который облегчает всей команде совместную работу.
Теоретически обосновал достижение этих целей Robert Martin (он же Uncle Bob). Он написал три книги о применении «чистого» подхода при разработке программного обеспечения (ПО). Одна из этих книг называется «Чистая архитектура, профессиональное руководство по структуре и дизайну программного обеспечения (ПО) », она и явилась источником вдохновения при создании этой статьи.
Кто-то скажет, это так, но меня это не касается, ведь в моем приложении уже есть архитектура MVVM?
Что ж, возможно, Clean Architecture может показаться излишней в том случае, если вы работаете над простым проектом. Но как быть, если нужно разделить модули, протестировать их изолированно и помочь всей команде в работе над отдельными контейнерами кода? Подход Clean Architecture освобождает разработчиков от дотошного изучения программного кода, пытаясь понять функции и логику функционирования.
Немного теории
Вероятно, вы много раз видели эту послойную диаграмму. Правда, мне она не очень помогла понять, как преобразовать эти слои в подготовленном проекте Android. Но сначала разберемся с теорией и определениями:
· Сущности (Entities) : инкапсулируют наиболее важные правила функционирования корпоративного уровня. Сущности могут быть объектом с методами или набором из структур данных и функций.
· Сценарии использования (Use cases) : организуют поток данных к объектам и от них.
· Контроллеры, сетевые шлюзы, презентеры (Controllers, Gateways, Presenters ): все это набор адаптеров, которые наиболее удобным способом преобразуют данные из сценариев использования и формата объектов для передачи в верхний слой (обычно пользовательский интерфейс).
· UI, External Interfaces, DB, Web, Devices : самый внешний слой архитектуры, обычно состоящий из таких элементов, как пользовательские и внешние интерфейсы, базы данных и веб-фреймворки.
После прочтения этих определений я всегда оказывался в замешательстве и не был готов реализовать «чистый» подход в своих Android проектах.
Прагматичный подход
Типичный проект Android обычно требует разделения понятий между пользовательским интерфейсом, логикой функционирования и моделью данных. Поэтому, учитывая «теорию», я решил разделить свой проект на три модуля:
· Домен : содержит определения логики функционирования приложения, модели данных сервера, абстрактное определение репозиториев и определение сценариев использования. Это простой, чистый модуль kotlin (независимый от Android).
· Данные: содержит реализацию абстрактных определений доменного слоя. Может быть повторно использован любым приложением без модификаций. Он содержит репозитории и реализации источников данных, определение базы данных и ее DAO, определения сетевых API, некоторые средства преобразования для конвертации моделей сетевого API в модели базы данных и наоборот.
· Приложение (или слой представления): он зависит от Android и содержит фрагменты, модели представления, адаптеры, действия и т.д. Он также содержит указатель служб для управления зависимостями, но при желании вы можете использовать Hilt.
Практическая часть — небольшое приложение для книжного каталога
Чтобы применить на практике все эти “абстрактные” понятия, я разработал простое приложение, которое демонстрирует список книг, написанных дядей Бобом, и которое дает пользователям возможность помечать некоторые из них как “избранные”.
Модуль домена
Чтобы получить список книг, я использовал API Google книги . Это API возвращает список книг, отфильтрованных по параметру строки запроса:
API находится здесь .
В доменном слое мы определяем модель данных, сценарии использования и абстрактное определение репозитария книг. API возвращает список книг или томов с определенной информацией, такой как названия, авторы и ссылки на изображения.
data class Volume(val id: String, val volumeInfo: VolumeInfo)
Простая сущность класса данных:
Абстрактное определение репозитария книг
class GetBooksUseCase (private val booksRepository: BooksRepository) {
suspend operator fun invoke (author: String) = booksRepository.getRemoteBooks(author)
}
Сценарии использования “Get books”
Модуль домена
Чтобы получить список книг, я использовал API Google книги . Это API возвращает список книг, отфильтрованных по параметру строки запроса:
Ссылка здесь .
В доменном слое мы определяем модель данных, сценарии использования и абстрактное определение репозитария книг. API возвращает список книг или томов с определенной информацией, такой как названия, авторы и ссылки на изображения.
Абстрактное определение репозитария книг:
Сценарии использования “Get books”:
class GetBooksUseCase (private val booksRepository: BooksRepository) {
suspend operator fun invoke (author: String) = booksRepository.getRemoteBooks(author)
}
Слой данных
Как уже отмечалось, слой данных должен реализовывать абстрактное определение слоя домена, поэтому нам нужно поместить в этот слой конкретную реализацию репозитория. Для этого мы можем определить два источника данных: «локальный» для обеспечения устойчивости и «удаленный» для извлечения данных из API.
Поскольку мы определили источник данных для управления постоянством (persistence), на этом уровне нам также необходимо определить базу данных (можно использовать Room) и ее объекты. Кроме того, рекомендуется создать несколько модулей (mappers) для сопоставления ответа API с соответствующим объектом базы данных. Помните, нам нужно, чтобы доменный слой был независим от слоя данных, поэтому мы не можем напрямую аннотировать объект доменного тома ( Volume ) с помощью аннотации @Entity room . Нам определенно нужен еще один класс BookEntity , и мы определим маппер (mapper) между Volume и BookEntity .
Слой презентации или приложения
В этом слое нам нужен фрагмент для отображения списка книг. Мы можем сохранить наш любимый подход MVVM . Модель представления принимает сценарии использования в своих конструкторах и вызывает соответствующий сценарий использования соответственно действиям пользователя (get books, bookmark, unbookmark).
Каждый сценариё использования вызывает соответствующий метод в репозитории:
Теперь посмотрим, как мы выполнили связь между слоями:
Как видите, каждый слой обменивается данными только с ближними к нему, сохраняя независимость внутренних слоев от нижних. Таким образом, легче правильно определять тесты в каждом модуле, а разделение проблем поможет разработчикам совместно работать над различными модулями этого проекта.
Полный проект можно найти на GitHub .
Читайте также:
Перевод статьи Nicola Gallazzi : Clean Architecture in Android — A Beginner Approach