Интересная «Request-Based» архитектура на примере Блокнота с открытым исходным кодом

от автора

Привет, меня зовут Саша и я Android-разработчик 🙂
(Это моя первая статья здесь и я буду рад вашей поддержке)

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

Часто источниками данных являются: человек, сервер и устройство (User, Server, Platform).

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

Подобно тому как вызовы функций вкладываются друг в друга:

// псевдокод fun main() {     val result = abs(min(x1, x2) + max(y1, sum(y2 + 42))) }

Можно написать вот такой код запрос-ответов:

// псевдокод fun main() {     ToUser.GetChoice(MainMenu) { choice ->         when (choice) {             MainMenu.AddNote -> {                 editNote()             }              MainMenu.ReadNotes -> {                 ToPlatform.GetNotes() { notes ->                     readNotes(notes)                 }             }              MainMenu.SearchNotes -> {                 searchNotes()             }              MainMenu.Trash -> {                 ToPlatform.GetNotes() { removedNotes ->                     openTrash(removedNotes)                 }             }         }     } }

Я решил сделать экспериментальное приложение по моей задумке и смотри что из этого получилось 🙂

Для начала я определился со стеком и целями:

✅ Стек:

Android, Kotlin, Compose, Coroutines, Room

✅ Цели:

  • Сделать приложение на Compose

  • Попрактиковаться в создании приложений на Compose

  • Написать приложение в стиле запрос-ответов (реализовать идею)

  • Написать приложение максимально просто

  • Это должно быть полноценное, функциональное приложение

  • Использовать код в качестве демонстрации, как домашнее тестовое задание

Затем написал реализацию:

Это варианты действий главного экрана, корзины и списка заметок:

enum class MainMenu(override val text: String) : IMenuItem {     AddNote("Написать заметку"),     ReadNotes("Читать заметки"),     SearchNotes("Искать заметки"),     Trash("Корзина"), }  enum class TrashMenu(override val text: String) : IMenuItem {     ReadNotes("Читать заметки"),     SearchNotes("Искать заметки"),     EmptyTrash("Очистить корзину"), }  sealed class NotesChoice() : IChoice {     data class Remove(val note: Note) : NotesChoice()     data class Select(val note: Note) : NotesChoice() }

Вся логика приложения сводится к такому дерево-графу запросов (и к нему можно прикрутить любую UI реализацию, навигацию, платформу, сервер-клиента):

Точка входа: main()

fun main() {     ToUser.GetChoice(         title = "Блокнот",         items = MainMenu.entries,         canBack = false     ).request { choice ->          when (choice) {             MainMenu.AddNote -> {                 editNote()             }              MainMenu.ReadNotes -> {                 ToPlatform.GetNotes().request { notes ->                     readNotes(notes)                 }             }              MainMenu.SearchNotes -> {                 searchNotes()             }              MainMenu.Trash -> {                 ToPlatform.GetNotes(removed = true).request { removedNotes ->                     openTrash(removedNotes)                 }             }         }     } }  fun openTrash(removedNotes: List<Note>) {   // ... }  fun searchNotes(removed: Boolean = false) {     ToUser.GetString(         title = "Поиск заметок",         label = "Искать строку",         actionName = "Найти",     ).request { response ->         when (response) {             is Back -> {                 User.requestPrevious()             }              is String -> {                 ToPlatform.GetNotes(query = response, removed = removed).request { notes ->                     readNotes(notes, "Заметки по запросу \n\n\"$response\"")                 }             }         }     } }  fun editNote(initial: Note? = null) {     // ... }  fun readNotes(notes: List<Note>, title: String = "Заметки") {     ToUser.GetChoice(         title = title,         items = notes.toMutableStateList(),     ).request { response ->         debug {             println("choice: $response")         }          when (response) {             is Back -> User.requestPrevious()             is NotesChoice.Remove -> {                 response.note.removed = true                 ToPlatform.UpdateNote(response.note)                     .request {                         User.request.pop()                         ToPlatform.GetNotes().request { notes ->                             readNotes(notes, title)                         }                     }             }              is NotesChoice.Select -> editNote(response.note)         }     } }

Чтобы было понятнее добавил вводные:

✅ Вводные для читателя кода проекта на github:

  • handleRequestsToPlatform() — обработка запросов к платформе

  • handleRequestsToUser() — обработка запросов к пользователю

Клиенты — делаем к ним запросы и получаем от них ответы:

  • User — пользователь как клиент

  • Platform — платформа/OS как клиент

  • Backend — бэк как клиент

ToUser — запросы к пользователю:

  • PostMessage — передать сообщение пользователю

  • GetChoice — получить выбор из вариантов

  • GetString — получить текст

Вся работа корутин производится в едином скоупе приложения.

✅ Дополнительно мне удалось:

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

  • использовать минимум фреймворков (без фреймворков DI, Flow, доп. навигации, вьюмоделей)

В результате получился легковесный и полнофункциональный Блокнот с простой и наглядной бизнес-логикой.

Залил код проекта на GitHub

https://github.com/e16din/LikeNotesApp

Если чего-то не достает для ясности, задавайте мне вопросы и я с удовольствием на них отвечу 🙂

Ну и «велкам ту зе клаб» — трогайте, пробуйте на вкус, ставьте звезды проекту, делитесь с друзьями и пишите в комментариях свои мысли.

По секрету: я в открытом поиске 🙂

P.S. Пока писал статью пересмотрел код и могу сказать: мне нравится то что получилось 🙂

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

Какую архитектуру и подходы вы используете своих проектах?

100%Пишу просто, в процедурном стиле1
100%Clean Architecture1
100%MVI1
100%MVVM1
0%MVP0
0%MVC0
0%Свой вариант в комментариях0

Проголосовал 1 пользователь. Воздержался 1 пользователь.

ссылка на оригинал статьи https://habr.com/ru/articles/942762/