Чистая архитектура в Kotlin и Jetpack Compose

Используя чистую архитектуру, вы можете разрабатывать приложения с очень низкой степенью связанности и независимыми от деталей технической реализации, таких как базы данных и фреймворки. Таким образом, приложение становится простым в обслуживании и гибким для изменений. Это также становится внутренне проверяемым. Здесь я покажу, как я структурирую свои проекты чистой архитектуры. На этот раз мы собираемся создать Android-приложение todo с помощью Jetpack Compose. Мы проиллюстрируем только один пример использования списка задач, полученных из API. Давайте начнем.

Структура пакета проекта принимает следующий вид:

├── Core
├── Data
├── Domain
└── Presentation

Начнем со слоя Domain.

Этот уровень описывает ЧТО делает ваш проект/приложение. Позвольте мне объяснить, многие приложения построены и структурированы таким образом, что вы не можете понять, что делает приложение, просто взглянув на структуру папок. Используя аналогию здания с домом, вы можете быстро определить, как будет выглядеть здание и его функциональность, просмотрев план этажа и высоту здания.

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

├── Core
├── Data
├── Presentation
└── Domain
    ├── Model
    │   ├── Todo.kt
    │   └── User.kt
    ├── Repository
    │   ├── TodoRepository.kt
    │   └── UserRepository.kt
    └── UseCase
        ├── Todo
        │   ├── GetTodos.kt
        │   ├── GetTodo.kt
        │   ├── DeleteTodo.kt
        │   ├── UpdateTodo.kt
        │   └── CreateTodo.kt
        └── User
            ├── GetUsers.kt
            ├── GetUser.kt
            ├── DeleteUser.kt
            ├── UpdateUser.kt
            └── CreateUser.kt
  1. Модель. Модель обычно представляет реальный объект, связанный с проблемой. В этой папке мы обычно храним классы для представления объектов. например Todo, пользователь и т. д.
  2. Репозиторий: контейнер для всех интерфейсов репозитория. Репозиторий — это центральное место для хранения всех операций, связанных с моделью. В этом случае интерфейс репозитория Todo будет описывать методы репозитория. Фактическая реализация репозитория будет храниться на уровне Data.
  3. UseCases:контейнер для перечисления всех функций нашего приложения. например Получить, Удалить, Создать, Обновить

Уровень ПРЕЗЕНТАЦИЯ будет содержать весь код, связанный с потребителем, относительно КАК приложение будет взаимодействовать с внешним миром. Уровень представления может быть веб-формами, интерфейсом командной строки, конечными точками API и т. д. В этом случае это будут экраны для списка задач и сопровождающей его модели представления.

├── Core
├── Data
├── Domain
└── Presentation
    └── Todo
        └── TodoList
            ├── TodoListViewModel.kt
            └── TodoListView.kt

Уровень DATA будет хранить весь код, связанный с внешними зависимостями, в том виде, в котором он КАК реализован:

├── Core
├── Domain
├── Presentation
├── Data
    ├── Repository
    │   ├── TodoRepositoryImpl.kt
    │   ├── TodoAPIDataSourceImpl.kt
    │   └── TodoDBDataSourceImpl.kt
    └── DataSource
        ├── API
        │   ├── TodoAPIDataSource.kt.
        │   └── Entity
        │       ├── TodoAPIEntity.kt
        │       └── UserAPIEntity.kt
        └── DB
            ├── TodoDBDataSource.kt.
            └── Entity
                ├── TodoDBEntity.kt
                └── UserDBEntity.kt
  1. Репозиторий: реализации репозитория
  2. Источник данных: все интерфейсы источника данных и сущности. Сущность представляет собой отдельный экземпляр объекта домена, сохраненный в базе данных в виде записи. У него есть некоторые атрибуты, которые мы представляем в виде столбцов в наших таблицах БД или конечных точках API. Мы не можем контролировать, как данные моделируются во внешнем источнике данных, поэтому эти сущности необходимо отображать из сущностей в модели предметной области в реализациях.

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

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

Давайте теперь напишем интерфейс для TodoDatasource. Нам нужен один, чтобы обеспечить поведение любого источника данных (API, БД и т. д.).

interface TodoDataSource {    
    suspend fun getTodos(): List<Todo>
}

У нас достаточно, чтобы написать реализацию этого интерфейса, и мы назовем ее TodoAPIImpl:

Примечание. функция getTodos этого репозитория возвращает список задач. Итак, нам нужно сопоставить TodoAPIEntity -> Todo:

Прежде чем мы напишем наш TodoRepositoryImpl, давайте напишем интерфейс для него на уровне домена.

Теперь мы видим, что TodoRepositoryImpl может принимать любой источник данных в качестве зависимости, что отлично подходит для замены источников данных.

Теперь, когда у нас есть наш репозиторий todo, мы можем написать сценарий использования GetTodos.

а затем, в свою очередь, мы можем написать модель представления нашей презентации и представление

Итак, резюмируя: