Статья об использовании реактивного доступа к базам данных из корутин. Spring все упрощает, но это плохо сказывается на понимании реальных процессов работы приложения. Для демонстрации был выбран фреймворк KTor (просто потому, что мне нравится смотреть на то, что делает JetBrains), который интенсивно использует корутины — чтобы задача сочетания Reactive Streams и этих самых корутин добавила интереса. В процессе работы пришло понимание, что происходящее — явный пример преобразования непонятного многим реактивного потока в понятное императивное программирование, на котором мы собаку съели. Я люблю реактивные цепочки, но почему бы не порадовать тех, кто любит армейский порядок? Реактивные приложения завоевали сердца и посадили нервы многих разработчиков, причем эти множества заметно пересекаются. Посадили бы еще больше, если бы не усилия сообществ, адаптирующих чистый поток разума от создателей спецификаций в удобоваримые библиотеки. Так произошло со спецификацией R2DBC и фреймворком Spring (Boot) — разработчику виден уже привычный Spring Data API с уже привычными реактивными типами данных. Однако есть причины не использовать Spring: не хочется Spring и хочется чего-то нового. Ну, есть еще унаследованный код, но в этом случае вряд ли придется столкнуться с реактивным доступом к данным.  В этом случае придется посмотреть на R2DBC без прикрас. И он будет ожидаемо сильно отличаться от того, что нам предлагают в готовом фреймворке — так же, как JDBC отличается от Spring Data JPA. Плюс реактивность. И реактивность по спецификации Reactive Streams. А у нас на слуху корутины. Которые вроде как будущее и все равно под них переписывать. Корутины запускать можно и вручную из main метода, но попробуем представить, что параллельность и конкурентость нам реально нужна — то есть high load (скажем, один запрос в час!) и мы совершенно серьезно решили писать это без Spring. А вот у нас, оказывается, есть легковесный аналог, целиком написанный на Kotlin, да еще от создателей самого языка, да еще и на корутинах, о которых мы грезим. По аналогии со всеми знакомым проектом, заготовку для сборки можно сгенерировать с веб-страницы (или через плагин для IntelliJ IDEA).  Идем на https://start.ktor.io и выбираем модули: Видно, что готовых модулей для загрузки довольно много и они интересные — всякие OAuth, JWT, LDAP для ограничения доступа, например.  Загружаем проект и открываем для редактирования. Неудивительно, что я открыл его в IntelliJ IDEA Community Edition.  Я буду использовать встраиваемую базу данных H2, реактивные драйверы подключим в build.gradle как реактивный пул соединений как для облегчения работы с чистым Reactive Streams, интерфейс которого предоставляет R2DBC драйвер, воспользуемся Reactor и его расширениями для Kotlin, так как пул соединений все равно его требует: Косметическая деталь: заставим компилироваться проект в байткод JVM 1.8. Может быть, когда-то это сделают в native и можно будет бросаться в докер контейнер без Java. Но не сейчас и не об этом. подключим в build.gradle последней инструкцией Запускаемый файл — Application.kt, там и будем писать код. Обнаруживаем код, который я разбавил своими метками и комментариями, но не добавлял никакой логики: Чтобы обеспечить горячую подмену классов после перекомпиляции, найдем файл настройки application.conf и в раздел ktor.deployment сразу после строк port добавим путь к классам. Из официальной документации неочевидно, но это именно путь: Интересный момент — приложение перегружает классы не сразу после компиляции, как devtools, а только после следующего обращения к нему. Я сначала подумал, что перегрузка не работает и перезапускал приложение.  На метке initDatabase опишем соединение с базой (в памяти) и создадим пул для нее. Надо еще добавить аннотацию @ExperimentalTime на модуль — я использовал экспериментальный API. Все хорошо, но хочется (хотя и нет смысла) прогреть пул соединений после старта. Логичнее было бы инициализировать саму базу, но это не так важно сейчас. Добавим обработчик события старта: Вот наша первая корутина — launch запустит ее. Пул реактивный, все операции реактивные, по классике — нельзя ждать, надо подписываться. Не тут-то было! Если мы "ждем" (awaitFirst из расширений корутин Kotlin), то поток не блокируется на время ожидания. Вот и ответ на важный вопрос: как же сцепить Reactor-типы Mono и Flux с результатами работы с основанной на другом принципе корутиной? Подождать реактивно. Чем это отличается от "подождать не-реактивно"? Прелесть в том, что в коде — не отличается. Ждем — а мы все равно реактивные. Это почти прямо прописывается в руководствах по корутинам, однако для разработчика, пишущего на Reactor или RxJava это звучит как прямое нарушение принципов реактивного программирования. Так что мы видим два взаимоисключающих утверждения — "жди!" и "не вздумай ждать!". Эта дилемма решается простым приоритетом корутин, которые пришли позже и уже знали про такой вопрос, поэтому и решили его где-то в своих внутреностях. Так что еще раз — для RxJava и Reactor профи — ждем! Теперь приложение можно запустить и увидеть, как прогревается пул и даже идут запросы к базе. Для чистоты надо добавить еще обработчик на остановку приложения для закрытия пула, но это я опущу в статье ради читабельности. Никаких CRUD делать не будем, чтобы не захламлять статью, а вернем данные из системной таблицы, которая в H2 уже есть: TABLES. Сделаем дата-класс для строк в том же файле: Я добавил typealias в начало файла, чтобы IDE не сходила с ума на конфликтующих именах И теперь готов вытаскивать данные из базы. Вот он, почти незамутненный R2DBC: Тут хорошо видно, что знакомый по Reactor реактивный поток с цепочками map/flatMap уступает место императивному подходу. Можно было бы закрутить все на map/flatMap по привычке, кто уже прочувствовал реактивщину, однако это усложнит код. Посмотрите на код finally — закрытие соединения (а оно обязательно) тоже является реактивной операцией и выглядеть это в операторе doFinally будет ужасно. Кстати, в официальной документации README по пулу соединений закрытие показано как conn.close(), что не работает ввиду отсутствия подписчика. В общем — можете поэкспериментировать, я — уже. Добавлю, что как только вы переходите в тело map вы теряете возможность сделать await, так как область корутины не видна. На закрытие вызываем awaitFirstOrNull() так как возвращается Mono, никогда не посылающий onNext() .
 Собираем все результаты во список, так как корутины не умеют собирать цепочку событий — это же, грубо говоря, функции с одним возвращаемым значением. R2DBC требует от нас функцию-отображение (map) для перевода данных в объекты. Я выделил ее в companion object своего дата-класса: Осталось вспомнить, как жутко было работать с чистым JDBC и применить опыт к реактивной инкарнации. Тут мы увидим map который на самом деле не часть Reactor, ReactiveStream или даже Java Stream, а независимая функция R2DBC. Все хорошо, можно запустить запрос : Если JSON некрасивый, то он настраивается в секции gson приложения (setPrettyPrinting). Ну и для красоты (раз уж мы не подключали стандартный модуль метрик) добавим точку для просмотра статистики пула. Движки шаблонов нам излишни, раз у нас средства языка позволяют: Конечно, при желании это можно отдать через HTML DSL. Подружить корутины с реактиными потоками можно, но необходимо переключаться между реактивным и императивным стилем — желательно пореже и стараться придерживаться одного стиля. Не весной лишь единой! Реактивный доступ к базам данных не такой красивый, как после макияжа Spring Data JPA, но пользоваться можно.  Java back-end