Готов ли мультиплатформенный Kotlin для создания полностековых (веб-)сервисов? Как такая разработка воспринимается с точки зрения тех, кто уже имеет опыт работы с Kotlin? Поделюсь моим опытом по созданию веб-UI для JVM-микросервиса при помощи Kotlin Multiplatform.
Не буду здесь вдаваться в детали о том, с какой целью применяется микросервисный подход, а также не стану углубляться в теорию микросервисов. Начнём этот пост с допущения, что вы хотите улучшить микросервисный ландшафт, имеющийся у вас в настоящий момент, либо собираетесь мигрировать на микросервисную систему, чтобы улучшить удобство использования и/или администрирования – предоставив для этого веб-UI. Идеально, если при этом вы уже знакомы с Kotlin.
Наша команда занималась разработкой гигантских приложений Java EE для бэкенда – это был один из недавних проектов, растянувшийся на несколько лет. За годы работы мы концептуально разработали, как дробить эти приложения на несколько микросервисов, а также как переносить бизнес целиком в облако. В частности, мы занимались: технологическими и организационными преобразованиями, изучением и практикой новых технологий и технологических подходов, параллельной обработкой в старых и новых условиях, притом, что сам бизнес постоянно рос и менялся.
В этих приложениях был предусмотрен пользовательский веб-интерфейс, сделанный по технологии JSF и решавший несколько задач по администрированию. В частности, через него мы считывали состояния и управляли ими, инициировали действия, извлекали данные для анализа или отладки, т.д. У такого подхода были важные достоинства.
Прежде всего, он шёл на пользу не только разработчику, который мог «в лёгкую» обращаться с действующими приложениями, но и пользователю, которому было не менее удобно. К актуальному состоянию базы данных можно было обращаться, ничего не зная о базе данных. В том числе, можно было не допускать к управлению доступом к базе данных никого кроме разработчиков. События или синхронизационные действия можно было выводить на экран или инициировать без какой-либо технической подготовки. Поэтому наша цель была такой: в закрепляющемся микросервисном ландшафте эти возможности, предоставляемые через веб-UI, должны каким-то образом сохраниться.
Прежде, чем продолжить, разберём некоторые концепции.
Прежде всего, вопрос: нужен ли вообще UI нашему (микро)сервису? Как всегда, смотря по ситуации. Например, если вашему сервису не требуется что-либо «показывать», например, при управлении персистентностью или внутренним состоянием, либо если не окупятся усилия и время, потраченные на разработку UI (достаточно будет простой конечной точки API), то, вероятно, стоит обойтись без UI. Другой момент: есть ли у вас нужные ресурсы и знания для разработки UI?
Рассмотрим пример: обращённый к пользователю монолитный веб-UI (Angular, React, т.п.), подкреплённый несколькими конечными точками на бекенде. Такие клиенты называются «полнофункциональными» или «толстыми».
Можно вместо одного толстого UI, можно сделать несколько самостоятельных компонентов UI, сопряжённых вместе. Организовать и оркестровать разработку этих компонентов можно по-разному. Например, сделать одиночный репозиторий или несколько независимых, разрабатывать всё одной командой или несколькими. В данном случае принято говорить о «микрофронтендах».
При подходе с созданием самодостаточной системы мы изолируем сам микросервис от возможностей его UI. В таком случае и UI, и сам сервис обычно выполняются в одном и том же процессе и развёртываются единым блоком. Именно этого подхода мы будем придерживаться в рамках данной статьи, так как он идеально сочетается с Kotlin Multiplatform.
Можно создать микросервис и UI к нему, воспользовавшись классическим подходом: держать наш код для JVM в одном пакете, а весь материал для UI – в другом, изолировав их друг от друга. Разделяться между этими пакетами ничего не будет, но сетевая конфигурация у них будет одинаковая. Такой подход хорош, например, в случае с сервисом на Spring-Kotlin, предоставляющим UI на Vue.js. Вывод клиентской части вставляется в classpath серверной части и предоставляется при помощи REST-контроллера. Работает хорошо, но как улучшить такую структуру?
Суть в том, что разработчик словно живёт в двух разных мирах. С одной стороны, есть код серверной части с собственной экосистемой, с другой – код клиентской части с совершенно иной экосистемой. Например, модели данных, конечные точки, константы, функции, тесты, библиотеки, инструменты и т.д. разделяются на две примерно равные группы, и обе группы требуется поддерживать, причём, на разных языках – даже если они находятся в одном и том же репозитории. В долгосрочной перспективе это может раздражать… меня в самом деле раздражает. Вот как можно улучшить ситуацию, воспользовавшись мультиплатформенными подходами.
Kotlin Multiplatform позволяет извлекать общий код – разделяемый между клиентской и серверной частью – и служит мостом между двумя мирами. Вы определяете общие внутренние модели, конечные точки, константы, функции, тесты, т.д. в центральном пакете или модуле, а ваши сервер и клиент всегда остаются синхронизированы, совместно работая с общей базой. Код становится разделяем между платформами. Ещё одно достоинство: работая с Kotlin Multiplatform, можно не отказываться от Kotlin!
Поддержка мультиплатформенного программирования – одно из ключевых достоинств Kotlin. Так экономится время на написани и поддержку одного и того же кода для разных платформ, и в то же время сохраняется гибкость и прочие преимущества, присущие нативному программированию.
Kotlin отлично подходит для разработки JVM-приложений, и уже очень хорошо известен в этом сообществе. Он применяется как для разработки сервисов с нуля, так и для переписывания унаследованного кода Java, интеграции нового кода на Kotlin в существующую базу кода на Java. Притом, что Kotlin очень хорош для разработки серверной части, концепция использовать Kotlin для разработки на клиенте сравнительно нова. Компания JetBrains поддерживает несколько Kotlin-обёрток, в частности, для React, Mocha или компонентов, работающих со стилями. В сообществе известны и иные решения. В Gradle предлагается несколько способов управления и объединения такого кода в пакеты при помощи webpack и для интеграции с npm при помощи yarn.
Kotlin/JS позволяет транспилировать код Kotlin, стандартную библиотеку Kotlin и любые совместимые зависимости в JavaScript.
Сервис, написанный для этого поста, я выложил на Github: https://github.com/jbilandzija/kotlin-multiplatform-sample
Здесь я использую очень простой бэкенд, основанный на Ktor, а также очень простой фронтенд, где применяется KotlinJS и React. Если вы ещё не слышали о Ktor, расскажу: это отличный инструмент из числа микрофреймворков для JVM, позволяющий создавать легковесные сервисы. Ktor отлично интегрируется с Kotlin и корутинами! Если вы предпочитаете работать с Spring или другим фрейморком, который вам нравится – да, можно и так.
Сервис состоит из трёх наборов исходников (не считая тестов): jvmMain, jsMain и commonMain. Эти наборы исходников (модули) определяются в главном файле Gradle. Существуют и иные варианты конфигурации. Мы подразделим код на разные модули и скомпилируем их, соответственно, под JVM и JS. У нас будут выделенные зависимости Ktor/Server и выделенные зависимости KotlinJS/Client.
«Традиционно» клиентская часть реализутся на том или ином JS-фреймворке, далее компилируется и складывается в общую папку со сборками Gradle, а далее каким-либо образом предоставляется для использования в серверной части. В качестве альтернативы можно было бы использовать какой-нибудь движок-шаблонизатор. В данном примере код получается сравнительно переплетённым, то есть, и клиент, и сервер могут совместно использовать код, лежащий в одной и той же папке. Это достигается при помощи обёрток Kotlin – в принципе, это библиотеки предметно-ориентированного языка на основе Kotlin. Мы пишем HTML, CSS и JavaScript на типобезопасном Kotlin.
После компиляции получаем два артефакта. Чтобы скомбинировать их оба в единый толстый JAR-архив, воспользуюсь плагином Shadow. С этим одиночным JAR можно обращаться так же, как и с любым другим (микро)сервисным JAR, который мог бы быть у нас и без встроенного UI. Сделаем из него контейнерный образ и загрузим на нашу любимую Platform-as-a-Service или подобный носитель.
В данном примере я пользуюсь React, в том числе, функциями React по управлению состоянием и жизненным циклом. Для этого используются компоненты функций, обёрнутые в DSL для Kotlin. Если эта тема для вас нова, можете познакомиться с ней здесь.
Мой пример структурирован в виде двух функциональных компонентов, InputComponent и OutputComponent. В структуре компонента InputComponent определяются внешний вид и поведение раздела Input. В компоненте OutputComponent отображается серверный вывод.
Именно от вас и вашей команды зависит, как структурировать компоненты. Я придерживался такого подхода: сверху определяем переменные и обработчики, далее идёт структура и вложенные настройки CSS. См. пример.
Тестирование Kotlin Multiplatform – тема, заслуживающая отдельного поста, она выходит за рамки проводимого здесь прототипирования. Для тестирования можно воспользоваться kotlin-test-js или kotest.io.
Модуль kotlin-test-js – это реализация обычных тестовых утверждений и аннотаций, прямо из коробки обеспечивающая поддержку тестировочных фреймворков Jasmine, Mocha и Jest. В качестве эксперимента она позволяет подключить их к вашему собственному фреймворку для модульного тестирования.
У меня в примере в серверной части расположено две конечные точки для веб-UI, сконфигурированные в плагине маршрутизации. Корневая конечная точка предоставляет index.html и статические ресурсы, в том числе, главный JS-файл (kt-multiplatform-sample.js). Вторая конечная точка предназначена для работы с вызовами к нашему веб-UI. Путь к конечной точке конфигурируется централизованно. Сервер и клиент используют одну и ту же константу. В данном случае хорошо и то, что не требуется беспокоиться о переименовании путей API или версионировании API.
Пока сложно принять окончательное решение о том, использовать ли такую незрелую технологию. Но готов ли Kotlin Multiplatform для построения полностековых (веб-)сервисов? Я считаю, что пока нет. Объясню, почему.
Я уже некоторое время экспериментирую как с вышеприведённым практическим примером, так и с очень похожим проектом. Природа молодых технологий такова, что после релиза новых зависимостей в них многое меняется; в особенности это касается технологий на стадии альфа. Мне пришлось немало поработать, пока у меня получился сервис на Kotlin Multiplatform, выглядевший и работавший именно так, как мне хотелось. После выпуска новых зависимостей мне приходилось многое перерабатывать, прежде чем мой сервис возвращался к приемлемому виду.
Каково мне заниматься такой разработкой, если моя специализация – бэкенд на Kotlin? Обращаться с этими новыми DSL для Kotlin было нормально. Писать на HTML и CSS на Kotlin немного странно, но мне нравилось заменять JavaScript на Kotlin. В обёртках, написанных на kotlin, много всякой магии, и мне приходилось повозиться с библиотеками, так как документация по ним – большая редкость.
Если у вас есть микросервисы на Kotlin, и вы планируете надстроить над ними пользовательский интерфейс – определённо имейте в виду Kotlin Multiplatform. Но я рекомендую немного подождать, пока эта технология дозреет.
Если же вы хотите больше узнать про Kotlin, то вашему вниманию мы предлагаем книгу «Kotlin. Программирование для профессионалов. 2-е изд.»:
» Оглавление
» Отрывок
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Для Хаброжителей скидка 25% по купону — Kotlin
Введение
Не буду здесь вдаваться в детали о том, с какой целью применяется микросервисный подход, а также не стану углубляться в теорию микросервисов. Начнём этот пост с допущения, что вы хотите улучшить микросервисный ландшафт, имеющийся у вас в настоящий момент, либо собираетесь мигрировать на микросервисную систему, чтобы улучшить удобство использования и/или администрирования – предоставив для этого веб-UI. Идеально, если при этом вы уже знакомы с Kotlin.
История
Наша команда занималась разработкой гигантских приложений Java EE для бэкенда – это был один из недавних проектов, растянувшийся на несколько лет. За годы работы мы концептуально разработали, как дробить эти приложения на несколько микросервисов, а также как переносить бизнес целиком в облако. В частности, мы занимались: технологическими и организационными преобразованиями, изучением и практикой новых технологий и технологических подходов, параллельной обработкой в старых и новых условиях, притом, что сам бизнес постоянно рос и менялся.
В этих приложениях был предусмотрен пользовательский веб-интерфейс, сделанный по технологии JSF и решавший несколько задач по администрированию. В частности, через него мы считывали состояния и управляли ими, инициировали действия, извлекали данные для анализа или отладки, т.д. У такого подхода были важные достоинства.
Прежде всего, он шёл на пользу не только разработчику, который мог «в лёгкую» обращаться с действующими приложениями, но и пользователю, которому было не менее удобно. К актуальному состоянию базы данных можно было обращаться, ничего не зная о базе данных. В том числе, можно было не допускать к управлению доступом к базе данных никого кроме разработчиков. События или синхронизационные действия можно было выводить на экран или инициировать без какой-либо технической подготовки. Поэтому наша цель была такой: в закрепляющемся микросервисном ландшафте эти возможности, предоставляемые через веб-UI, должны каким-то образом сохраниться.
Прежде, чем продолжить, разберём некоторые концепции.
Концепции
Микросервис без UI
Прежде всего, вопрос: нужен ли вообще UI нашему (микро)сервису? Как всегда, смотря по ситуации. Например, если вашему сервису не требуется что-либо «показывать», например, при управлении персистентностью или внутренним состоянием, либо если не окупятся усилия и время, потраченные на разработку UI (достаточно будет простой конечной точки API), то, вероятно, стоит обойтись без UI. Другой момент: есть ли у вас нужные ресурсы и знания для разработки UI?
Полный клиент
Рассмотрим пример: обращённый к пользователю монолитный веб-UI (Angular, React, т.п.), подкреплённый несколькими конечными точками на бекенде. Такие клиенты называются «полнофункциональными» или «толстыми».
Подход с плагинами
Можно вместо одного толстого UI, можно сделать несколько самостоятельных компонентов UI, сопряжённых вместе. Организовать и оркестровать разработку этих компонентов можно по-разному. Например, сделать одиночный репозиторий или несколько независимых, разрабатывать всё одной командой или несколькими. В данном случае принято говорить о «микрофронтендах».
Самодостаточная система (встраиваемый UI)
При подходе с созданием самодостаточной системы мы изолируем сам микросервис от возможностей его UI. В таком случае и UI, и сам сервис обычно выполняются в одном и том же процессе и развёртываются единым блоком. Именно этого подхода мы будем придерживаться в рамках данной статьи, так как он идеально сочетается с Kotlin Multiplatform.
Kotlin Multiplatform
Можно создать микросервис и UI к нему, воспользовавшись классическим подходом: держать наш код для JVM в одном пакете, а весь материал для UI – в другом, изолировав их друг от друга. Разделяться между этими пакетами ничего не будет, но сетевая конфигурация у них будет одинаковая. Такой подход хорош, например, в случае с сервисом на Spring-Kotlin, предоставляющим UI на Vue.js. Вывод клиентской части вставляется в classpath серверной части и предоставляется при помощи REST-контроллера. Работает хорошо, но как улучшить такую структуру?
Суть в том, что разработчик словно живёт в двух разных мирах. С одной стороны, есть код серверной части с собственной экосистемой, с другой – код клиентской части с совершенно иной экосистемой. Например, модели данных, конечные точки, константы, функции, тесты, библиотеки, инструменты и т.д. разделяются на две примерно равные группы, и обе группы требуется поддерживать, причём, на разных языках – даже если они находятся в одном и том же репозитории. В долгосрочной перспективе это может раздражать… меня в самом деле раздражает. Вот как можно улучшить ситуацию, воспользовавшись мультиплатформенными подходами.
Kotlin Multiplatform позволяет извлекать общий код – разделяемый между клиентской и серверной частью – и служит мостом между двумя мирами. Вы определяете общие внутренние модели, конечные точки, константы, функции, тесты, т.д. в центральном пакете или модуле, а ваши сервер и клиент всегда остаются синхронизированы, совместно работая с общей базой. Код становится разделяем между платформами. Ещё одно достоинство: работая с Kotlin Multiplatform, можно не отказываться от Kotlin!
Поддержка мультиплатформенного программирования – одно из ключевых достоинств Kotlin. Так экономится время на написани и поддержку одного и того же кода для разных платформ, и в то же время сохраняется гибкость и прочие преимущества, присущие нативному программированию.
Kotlin отлично подходит для разработки JVM-приложений, и уже очень хорошо известен в этом сообществе. Он применяется как для разработки сервисов с нуля, так и для переписывания унаследованного кода Java, интеграции нового кода на Kotlin в существующую базу кода на Java. Притом, что Kotlin очень хорош для разработки серверной части, концепция использовать Kotlin для разработки на клиенте сравнительно нова. Компания JetBrains поддерживает несколько Kotlin-обёрток, в частности, для React, Mocha или компонентов, работающих со стилями. В сообществе известны и иные решения. В Gradle предлагается несколько способов управления и объединения такого кода в пакеты при помощи webpack и для интеграции с npm при помощи yarn.
Kotlin/JS позволяет транспилировать код Kotlin, стандартную библиотеку Kotlin и любые совместимые зависимости в JavaScript.
К делу
Сервис, написанный для этого поста, я выложил на Github: https://github.com/jbilandzija/kotlin-multiplatform-sample
Здесь я использую очень простой бэкенд, основанный на Ktor, а также очень простой фронтенд, где применяется KotlinJS и React. Если вы ещё не слышали о Ktor, расскажу: это отличный инструмент из числа микрофреймворков для JVM, позволяющий создавать легковесные сервисы. Ktor отлично интегрируется с Kotlin и корутинами! Если вы предпочитаете работать с Spring или другим фрейморком, который вам нравится – да, можно и так.
Сервис состоит из трёх наборов исходников (не считая тестов): jvmMain, jsMain и commonMain. Эти наборы исходников (модули) определяются в главном файле Gradle. Существуют и иные варианты конфигурации. Мы подразделим код на разные модули и скомпилируем их, соответственно, под JVM и JS. У нас будут выделенные зависимости Ktor/Server и выделенные зависимости KotlinJS/Client.
Клиентская часть
«Традиционно» клиентская часть реализутся на том или ином JS-фреймворке, далее компилируется и складывается в общую папку со сборками Gradle, а далее каким-либо образом предоставляется для использования в серверной части. В качестве альтернативы можно было бы использовать какой-нибудь движок-шаблонизатор. В данном примере код получается сравнительно переплетённым, то есть, и клиент, и сервер могут совместно использовать код, лежащий в одной и той же папке. Это достигается при помощи обёрток Kotlin – в принципе, это библиотеки предметно-ориентированного языка на основе Kotlin. Мы пишем HTML, CSS и JavaScript на типобезопасном Kotlin.
После компиляции получаем два артефакта. Чтобы скомбинировать их оба в единый толстый JAR-архив, воспользуюсь плагином Shadow. С этим одиночным JAR можно обращаться так же, как и с любым другим (микро)сервисным JAR, который мог бы быть у нас и без встроенного UI. Сделаем из него контейнерный образ и загрузим на нашу любимую Platform-as-a-Service или подобный носитель.
Строим UI
В данном примере я пользуюсь React, в том числе, функциями React по управлению состоянием и жизненным циклом. Для этого используются компоненты функций, обёрнутые в DSL для Kotlin. Если эта тема для вас нова, можете познакомиться с ней здесь.
Мой пример структурирован в виде двух функциональных компонентов, InputComponent и OutputComponent. В структуре компонента InputComponent определяются внешний вид и поведение раздела Input. В компоненте OutputComponent отображается серверный вывод.
Именно от вас и вашей команды зависит, как структурировать компоненты. Я придерживался такого подхода: сверху определяем переменные и обработчики, далее идёт структура и вложенные настройки CSS. См. пример.
Тестирование
Тестирование Kotlin Multiplatform – тема, заслуживающая отдельного поста, она выходит за рамки проводимого здесь прототипирования. Для тестирования можно воспользоваться kotlin-test-js или kotest.io.
Модуль kotlin-test-js – это реализация обычных тестовых утверждений и аннотаций, прямо из коробки обеспечивающая поддержку тестировочных фреймворков Jasmine, Mocha и Jest. В качестве эксперимента она позволяет подключить их к вашему собственному фреймворку для модульного тестирования.
Серверная часть
У меня в примере в серверной части расположено две конечные точки для веб-UI, сконфигурированные в плагине маршрутизации. Корневая конечная точка предоставляет index.html и статические ресурсы, в том числе, главный JS-файл (kt-multiplatform-sample.js). Вторая конечная точка предназначена для работы с вызовами к нашему веб-UI. Путь к конечной точке конфигурируется централизованно. Сервер и клиент используют одну и ту же константу. В данном случае хорошо и то, что не требуется беспокоиться о переименовании путей API или версионировании API.
Заключение
Пока сложно принять окончательное решение о том, использовать ли такую незрелую технологию. Но готов ли Kotlin Multiplatform для построения полностековых (веб-)сервисов? Я считаю, что пока нет. Объясню, почему.
Я уже некоторое время экспериментирую как с вышеприведённым практическим примером, так и с очень похожим проектом. Природа молодых технологий такова, что после релиза новых зависимостей в них многое меняется; в особенности это касается технологий на стадии альфа. Мне пришлось немало поработать, пока у меня получился сервис на Kotlin Multiplatform, выглядевший и работавший именно так, как мне хотелось. После выпуска новых зависимостей мне приходилось многое перерабатывать, прежде чем мой сервис возвращался к приемлемому виду.
Каково мне заниматься такой разработкой, если моя специализация – бэкенд на Kotlin? Обращаться с этими новыми DSL для Kotlin было нормально. Писать на HTML и CSS на Kotlin немного странно, но мне нравилось заменять JavaScript на Kotlin. В обёртках, написанных на kotlin, много всякой магии, и мне приходилось повозиться с библиотеками, так как документация по ним – большая редкость.
Если у вас есть микросервисы на Kotlin, и вы планируете надстроить над ними пользовательский интерфейс – определённо имейте в виду Kotlin Multiplatform. Но я рекомендую немного подождать, пока эта технология дозреет.
Если же вы хотите больше узнать про Kotlin, то вашему вниманию мы предлагаем книгу «Kotlin. Программирование для профессионалов. 2-е изд.»:
» Оглавление
» Отрывок
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Для Хаброжителей скидка 25% по купону — Kotlin