Следующий шаг на пути к созданию вашего Android-приложения на основе чистой архитектуры

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

Краткое резюме

Прежде чем идти дальше, я настоятельно рекомендую прочитать часть 1 этой серии, в которой я объяснил, почему мы должны использовать чистую архитектуру и как реализовать внедрение зависимостей с помощью Dagger2.

Здесь я предпочитаю использовать сопрограммы вместо устаревшего RxJava для асинхронных операций. Мы создаем как репозиторий, так и варианты использования с сопрограммами. Для реализации сетевых запросов мы используем Retrofit с поддержкой сопрограмм.

Репозиторий и сценарии использования

Репозиторий

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

Если вы знакомы с принципами SOLID, вы должны знать, что модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций. Здесь у нас будет два слоя: данные и домен.

  • Домен: содержит бизнес-логику приложения. Это индивидуальный и сокровенный модуль. Это полный модуль Java.
  • Данные: это включает уровень домена. Он реализует интерфейс, предоставляемый уровнем домена, и распределяет данные в приложение.

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

Случаи применения

В течение долгого времени я чувствовал, что варианты использования были дополнительным бременем при работе над архитектурами. Я был глуп, чтобы так думать. Посмотрим почему.

Например, предположим, что вы предоставляете функцию doSomething в репозитории нескольким моделям представления (например, 5-6 ViewModels). Если что-то нужно будет изменить в возвращаемом типе этой функции в будущем, нам нужно будет обновить код во всех этих ViewModels. Это была проблема, которую можно было решить с помощью вариантов использования.

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

Вместо того, чтобы обращаться к репозиторию напрямую из модуля приложения, мы создадим сценарий использования для каждого взаимодействия с репозиторием. Мы используем это взаимодействие из нашего модуля приложения (как упоминалось ранее, 5-6 ViewModels) для доступа к репозиторию. Таким образом, если вы внесете какие-либо изменения в источники данных, вы сможете обработать их в одной точке.

Шаблон репозитория

Чтобы создать шаблон репозитория на уровне архитектуры, мы должны создать два пакета - data и domain в главном каталоге приложения. Посмотри:

Первым шагом является создание интерфейса с именем Repository внутри уровня домена и его класса реализации с именем RepositoryImpl под уровнем данных:

Это простой шаблон репозитория, который удовлетворяет принципам SOLID, на которых основана чистая архитектура. Следующая часть - создание источника данных. В этой статье я включил только удаленный источник данных. Если вас интересует, как создать несколько источников данных (например, удаленных и локальных) и управлять ими, я настоятельно рекомендую прочитать следующую статью:



Как я уже сказал, мы используем сопрограммы для выполнения асинхронных операций и Retrofit для выполнения сетевого запроса. Для сериализации данных мы используем Gson. Чтобы добавить эти библиотеки в свой проект, добавьте следующие строки под узлом зависимостей файла gradle уровня приложения:

Теперь мы должны создать интерфейс службы API под слоем данных, который действует как удаленный источник данных. Внутри этого класса у нас будут suspend функции, которые запускают сетевые запросы с помощью Retrofit. Посмотри:

interface SampleApiService {
}

Структура пакета выглядит так:

Внедрение зависимости

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

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

Теперь мы можем внедрить SampleApiService в реализацию репозитория, чтобы запустить соответствующую службу. Посмотри:

class RepositoryImpl @Inject constructor(
    val sampleApiService: SampleApiService) : Repository {
    
}

Случаи применения

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

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

По сути, мы создадим два общих варианта использования - один с параметрами, а другой без параметров. Мы создадим их в пакете base > usecasetypes. Посмотри:

  • P - Тип параметра, передаваемого в функцию выполнения варианта использования.
  • R - Тип результата, который ожидается в качестве возвращаемого типа.

Чтобы лучше это понять, давайте создадим простой класс и расширим его с помощью BaseUseCaseWitOuthParams. Имейте в виду, что варианты использования создаются на уровне домена. Посмотри:

Внедрение зависимости

Единственная цель варианта использования - взаимодействие с репозиторием, что означает, что нам нужно внедрить репозиторий в конструктор варианта использования. Перед этим нам нужно создать функцию в модуле данных в каталоге di, который был создан в части 1 этой серии с типом возвращаемого значения Repository. Посмотри:

Теперь мы можем вставить объект repository в SimpleUseCaseTest, как показано ниже:

Теперь мы закончили настройку репозитория и варианты использования с внедрением зависимостей.

Бонус

Репозиторий GitHub

Импортируйте проект и перейдите в ветку articles/repository_usecases, чтобы увидеть код, связанный с этой статьей:



Чтобы узнать больше о расширенных компонентах разработки и архитектуры Android, прочтите следующие статьи:

Это все. Надеюсь, вы узнали что-то полезное. Спасибо за прочтение.