Как мы мигрировали на Scalar

Некоторое время назад, решили мы поменять тошнотворный Swagger на что-то современное. Остановились на Scalar-е. Современный, компактный и возможностей побольше. Здесь постараюсь рассказать с чем столкнулись при этом.

Действующие лица

  1. Текущий проект. На котором API документация показывается в Swagger-е
  2. OpenAPI. Спецификация по которой генеруется структура и описание REST API. Собственно то во что развился Swagger.
  3. Swagger. В нашем случае UI, который отображает данные сгенерированные по стандарту OpenAPI.
  4. Scalar. Просто UI, который отображает данные сгенерированные по стандарту OpenAPI и на который мы хотим смигрировать Swagger.
  5. Springdoc. Прослойка между SpringBoot и нашей документацией

Инициируем типа наш текущий проект со Swagger-ом

  1. Идем на сайт Spring, забираем SpringBoot3 приложение и добавляем в IDE
  2. Исправляем зависимость:

    на
  3. Создаём котроллер, который что-то возвращает:
  4.  Запускаем приложение и идем по адресу localhost:8080/demo. Видим результат:

    У нас есть приложение.

Получаем OpenAPI спецификацию

Здесь как раз нам помогает Springdoc:

  1. Добавляем зависимость:
  2. Добавляем в application.yaml
  3. Идем по адресу localhost:8080/demo. Видим результат:

    Как можно заметить, мы получили в ответ JSON файл, который собственно и содержит всю документацию о моем эндпоинте. Теперь его можно скармливать в любой UI или клиент. Они сами будут его парсить и отображать.

Swagger

  1. Добавляем зависимость:
  2. Идем по адресу localhost:8080/swagger-ui. Видим результат:Ностальгируем по 2015 году.

Вот в таком состоянии была наша документация перед миграцией на Scalar.

Первый подход. Дёшево, надежно, практично

Переход на Scalar у нас осуществлялся одновременно с разделением наших эндпоинтов на две разные группы, которые изначально предполагалось прятать за ролевой моделью. Соответственно нужно было иметь две разные непересекающиеся OpenAPI спецификации. Помогает в этом опять же Springdoc:

  1. Добавляем конфигурацию:

    Как видно эндпоинты мы группируем по пакетам
  2. Идем поадресу localhost:8080/swagger-ui. Получаем:

Как можно заметить, получился комбобоксик в правом верхнем углу, где можно переключаться между разными группами эндпоинтов. По началу это меня не устраивало, поскольку невозможно было разделить по ролям для отображения. Для этого нужно было иметь два разных эндпоинта, которые можно было бы подключать к соответствующим OpenAPI ссылкам.

Решение с мигрцией на Scalar, которое нашлось в сети, получилось весьма простым, топорным и очень гибким. Нужно просто на своей стороне генерировать HTML код:

  1. Убираем зависимость:
  2. Создаём свой собственный Scalar контроллер:

    Как это работает. Браузер получает HTML, загружает JavaScrip в строке 28, который в свою очередь получает конфигруцию из строки 27 и отрисовывает UI. А в строке 27 хранится пока путь к соответствующей OpenAPI документации.

    Также есть возможность модифицировать HTML. Например, задать нужное название вкладки.

  3. Идём по соотвествующим ссылкам. Видим необходимые документации:

Вуаля! Всё работает.

Но. В процессе код-ревью выясняется, что мы не хотим генерировать HTML и не хотим прятать документацию по ролям (т.е. все должны видеть всё). Начитается поиск иного пути.

Современный программист больше конфигуратор

Правильно. Меньше кода – больше конфигурации. Приходится открывать документацию по Scalar-у и читать ((

Итак, делаем как написано в доке:

  1. Добавляем зависимость:
  2. Добавляем свойства:
  3. Идём по адресу localhost:8080/docs И пара-пара-пам! Приплыли! Сливаем воду! Иду на https://mvnrepository.com/artifact/com.scalar.maven/scalar. Вижу последнюю версию 0.4.4 (версии 0.1.0 нет вообще!). Пробую 0.4.4 – без изменений. Оригинальная документация бесполезна!

Конфигуратор не сдаётся никогда!

Ещё варианты? Кто-то где-то слышал, что Springdoc поддерживает Scalar. Удивительно но факт. Начинаю подключать Scalar через Springdoc:

  1. Удаляем зависимость, что добавляли прежде:
  2. Добавляем зависимость:
  3. Также давайте не забывать, что у нас уже был подключён Swagger. Поэтому нужно вернуть часть от Swagger-а назад дабы вернуть состояние приложения с которым я работал:

  4. Запускаем, идём на localhost:8080/docs. И, о, чудо! Заработало!Только есть проблема. Работает как Swagger, так и Scalar. Вроде решение очевидное. Давайте изменим свойство springdoc.swagger-ui.enabled=false. Делаем. И не работает! Ни Swagger, ни Scalar! С ума сойти! Нафига мне две UI-ки? Лезу в код:

И что мы видим? А видим мы прямую зависимость Scalar-а от Swagger-а (стр. 24-25). Зачем?!

Как позже выяснилось отсюда было два выхода. Убрать зависимость Swagger-а или обновить версию Springdoc-а. Я пошёл по второму пути. Версия 2.8.15 решила эту проблему:

Супер! Анализируем, что получилось!

Основной навык конфигуратора отгадайка

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

Комбобоксик слева вверху. Франкенштейн в конфигурации. Там Springdoc использует конфигурацию Scalar-а. Я промочу о логотипчике и других конфигурационных свойствах, которые собирались отовсюду. Вроде всё устраивает, кроме названий селекторов в комбобоксике. Логично предположить, что это title в настройках скалара. Ок. Делаю:

И что? И ничего. Всё без изменений! Мелочь, казалось бы. Но это нужно исправлять! А как понять, что делать? Опять таки, где читать? Нигде. Остаётся код. Лезем в код опять:

И что мы видим? А видим мы, мать вашу, тот же самый контроллер, который я написал вначале. Ничего сложного! Но вот, что касается нужного кода, то его собственно нет. Уходит он в метод org.springdoc.scalar.AbstractScalarController::getDocs. Только вот проблема. Нет такого класса в этом джарнике. Нет даже такого пакета! Хотя пакет от Springdoc!

Запускаю поиск класса AbstractScalarController в Jar Explorer-e по всему локальному репозиторию. И нахожу:

Сука! В common-е! В common-е лежит класс отвечающий за отображение Scalar-а! Зачем?!

Ладно, идём в код:

А в коде мы видим, что source инициируется особым образом в строке 39. title берется не из свойств Scalar-a, а из свойств групп, которые мы определяли ранее в Java коде! Причём, два дурих свойств Scalar-a тупо обнуляются! То есть их невозможно установить от слова никогда! Также рекомендую обратить внимание на строчки 42 и 43. Сейчас нам они не понадобятся, но в дальнешем, еще с ними столкнёмся в другой проблеме. Там тупо меняется композиционная переменная контроллера т.е. синглтона.

Ок. Довавляем названия в группы:

Смотрим, что получилось:

Супер! Что и требовалось!

  • мы мигрировали на Scalar
  • вся миграция в виде конфигурации
  • эндпоинты разбиты на группы
  • также был добавлен логотипчик, убрана консоль разработчика, добавлен title на табку браузера и другие мелочи

Вроде всё готово! Можно в прод!

Деплою в темп для теста и… нихрена не работает!

Страничка грузится, JavaScript загружается, всё подтягивается кроме OpenAPI JSON файла. Ссылка на него: “url”:”http://temp:443/v3/api-docs/demo-group”. Кик видим протокол HTTP, а порт HTTPS. Браузер такое наебалово не любит, а поэтому отказывается загружать.

Пробую как-то это исправить на своей стороне. Документация бесполезна, поэтому сразу лезу в код:

Что бросается в глаза? Строчки с 3 по 6. Самый главный вопрос зачем? Зачем тянуть в код тестовый URL Scalar-a? Это когда вы строите демку Scalar-a, чтобы посмотреть как работает. Но зачем её протягивать сквозь Java код – непонятно севершенно!

Тем не менее, вроде как, можно в Scalar-е указать один верхний URL типа: /v3/api-docs и всё должно подтянуть (условие не сработает). Делаю. И всё работает! Ура! Но только первый раз при заходе на страничку. Дальше – нифига! А почему? А помните те две строчки выше, на которые я обращал внимание? Вот они то и влияют на это поведение. Первый раз все хорошо, а потом null вместо URL потому как оно перетёрлось в свойствах.

Следующим действием попробовал я последнюю версию org.springdoc:springdoc-openapi-starter-webmvc-scalar. На тот момент 3.0.1. А эта зараза требует SpringBoot4. Я вроде подглядел в код последнего Springdoc Scalar-a и мне показалось что там что-то изменено. Посим я из последних сих заодно обновил наш SpringBoot3 со всеми зависимостями, но всё было по прежнему!

Выть хотелось! Бред какой-то. Никак здесь в моём случае невозможно избежать абсолютной ссылки.

Как выяснилось позже наша аппка была спрятана за проксиком, а эти проксики на каждом энвайрменте были по разному сконфигурированы, на одном, например, был протокол HTTP, а на другом HTTPS. Поэтому пришлось захардкодить в настройках деплоймента соответствующие хедеры:

После этого всё заработало.

P.S. Польза от всякоразных ИИ закончилась на кастомном контроллере. В дальнейшем все, что мне удалось попробовать, были абсолютно бесполезны. Один из них до последнего пытался мне вставить свойство логотипчика в конфигурацию Scalar-а. Хотя его там никогда не было и нет до сих пор. Такое…

Виды энергетических предобразований

В продолжении размышлений про электричество хотелось бы расширить тему про преобразование електрической энергии в другие виды энергий. Как было замечено ранее то что мы называем електричеством очевидно представляет из себя особый вид энергии, которая может быть преобразована в другие виды энергий с помощью различных материалов и их форм. Замечу, что здесь к физике процесса присоединяются взаимодействие веществ и их характеристики, а также форма тела как физические процессы. Например, из особого раствора в аккумуляторе или батарейке мы получаем, то что называем электричеством. И если химия искусственно отделена от физики, что является по сути предрассудком, то вот форма тела, очевидно, в данном случае, выступает как отдельная физическая характеристика. К примеру, из одной и той же проволоки можно получить физическую сущность как электомагнитное поле, свернув её в катушку, так и нагреть окружающую среду. А можно и то и другое в зависимости от того какую форму придать этому куску проволоки и какой ток через неё пропустить.

Другими словами если подитожить очевидные вещи, то можно сказать, что преобразование электричества в другие виды энергии тесно связано с материаловедением и формой этого же материала, а, значит, мы можем рассматривать это как физические сущности.

Рассмотрим какие же преобразования нам известны:

Прямое преобразование Обратное преобразование
Световое Лампа накаливание, диодные лампы Фотоелемент
Тепловое Отопительный радиатор, резистор Термоэлектрогенератор
Механическое Электродвигатель Генерация переменного тока
Химическое Электролиз Аккумулятор
Электромагнитное Катушка, антенна Генерация переменного тока
Электронное ЭЛП

Как можно видеть, нам известны практически все виды преобразований электрической энергии в другие виды энергий, а также справедливо обратное. Мы можем добывать электриктрическую энергию из иный видов энергий.

Любопытная ситуация с электронным преобразованием. Мы достаточно уверенно освоили преобразование электричества в поток электронов. Но вот обратное преобразование практически неосвоено. А оно, на мой взглад, может весьма ощутимо освоить добычу электричества в АЭС. Ведь очевидно, что возможность получать электричество непосредственно из ядра реактора значительно увеличит КПД этих станций.

Вместо вывода можно сказать, что комбинируя материалы, придавая им оиределённую форму, мы можем преобразовывать электрическую энергию в другие виды энергии и наоборот.

Электричество

Некоторое время назад пришла мне в голову неожиданная мысль.
А именно о коротком замыкании. Что то я мерял тестром и засмотрелся на циферки, которые мелькают на табло, если в холостую замкнуть контакты тестера. Другими словами, сделать КЗ на тестере. Тестер естественно не сгорит – сам погасит это КЗ (если это вообще КЗ для тестера), но циферками отреагирует, что ему это не травится.
Тогда что такое КЗ? Давайте посмотрим на его формулу:
U=IR (R -> 0)
Другими словами, КЗ – это когда в цепи отсутсвует какое либо сопротивление (или потребитель). В это случае получается, что то что мы называем одним полюсом в ЭДС(U) “перетекает” в другой полюс. Дальше можно строить гипотезы.
Но вернёмся к сопротивлению или потребилям. Когда это всё есть никакого КЗ не происходит. Давайте сопротивление рассматривать тоже как потребитель. Ведь ничего нам это не запрещает делать. Теперь предлагаю рассмотреть что из себя представляют эти потребители. Лампочка, резистор, хим. расствор, радиатор вентилятор и проч. А чем в свою очередь являются все эти потребители? Правильно. Там просходит преобразование энергии. Так называемая “электрическая” энергия преобразуется в магнитную, химическую, тепловую, квантовую и проч. Причём известно как прямое преобразование, так и обратное. Например тот же резистор. Это по сути нагреватель. Только “нагревать” его можно по разному. От светящейся спирали (лампочка накаливания) до рзистора в цепи (греется в определённом диапазоне). Есть и обратный процесс получения электричества с термопары. По другим преобразованиям тоже можно привести примеры (химическое, фото, магнитное).
Основываясь на этом замечании, можно рассматривать электричество как некий универсальный вид энергии, который может быть преобразован в другие виды.
Также могут быть пересмотрены другие составляющие электричества. Сила тока – это сила которая ограничивает преобразование. Напряжение – плотность преобразования и проч.

Осторожно с mockito

Казалось бы, популярный, уже оттестированный фреймворк. И тут те на!
Допустим нам нужно замокать нечто вроде:

Дальше в тесте:

Согласно моим ожиданиям, этот код должен свалиться с NPE, потому как происходит анбоксинг после вызова метода getInt. На самом деле mockito возвращает 0.
Проверялось на mockito-all:1.10.8 и 1.10.19.
Со стороны mockito всё нормально. “Прикрылись” фиговым листком джавадоком:
By default, for all methods that return value, mock returns null, an empty collection or appropriate primitive/primitive wrapper value (e.g: 0, false, … for int/Integer, boolean/Boolean, …).

ArrayList не совсем Array

Недавно столкнулся с необычным поведением у java.util.ArrayList. A именно метод java.util.List.add(int, Object). Предлагаю рассмотреть на примере:

данный код свалится с эксепшеном

Но этот же код отлично отработает с нативным массивом:

Казалось бы в чем разница? Может ArrayList инициализируется по своему? Проверил, все в порядке создается настоящий внутренний массив размером который указывается в конструкторе:

Остаётся метод add:

Как мы видим сразу же идёт проверка диапазона:

Другими словами когда мы используем конструктор java.util.ArrayList.ArrayList(int) мы как бы получаем доступ до внутрянки, одновременно с этим ничего больше не получаем взамен. На мой взгляд несколько намешано в архитектуре. Выставлен конструктор только лишь для того, чтобы оказать влияние на внутреннее предствление. И всё.

Так что в очередной раз можно утвердится в мысли, что ArrayList в Java это больше список нежели массив…

Load your CLASSPATH at runtime

One of the great advantages of programming in JAVA is the ability to add to your code as you go. You can create classes and turn them into packages which can then be imported and used in any other JAVA application. Each instance of your program relies on the CLASSPATH environment variable for external packages. For example you can import an external JDBC driver for your application to connect your database. You can also import dynamic modules to add and change program functionality. Setting the classpath variable can be done as a part in the environment variables or while invoking the JVM. However, when your program relies on external web updates, it’s not practical to change the command arguments every time there is a major update. In this article, we are going to explore programmically changing your classpath to modify program features. We are going to assume you have an updater module that basically drops all of your JAR files into the module’s directory. We are then going to read all the files in the directory and add them to your classpath at run time. The code to reading all the files in the directory looks like this:

And the last step is to append the files to the classfile.

It is very important to program your classpath as the first step when you invoke your program. Any objects that are being initialized before the classpath is set can result in a no class definition error. You’ve seen how useful it can be to any software that relies on external packages. The possibilities are endless. Happy programming!

Source

Как я писал Android приложение

Не думал, что захочется поделиться опытом по написанию моего первого функционального Android приложения. Когда начинал его писать, думал, что всё будет просто, ведь, блин, их уже то понаписано тьма! Однако мне пришлось спотыкаться на ровном месте, причём не раз!

Итак, сменил я зимой сего года место работы. На новом месте свой интернет, свои “злые” айтишники и подумалось, что негоже мне светить в траффике урл сайта где я новости читаю. Скажем так, не популярен он в нынешнем обществе. Соответственно, приложение в Android самый логичный выход. Там может быть мобильный интернет и никто ничего с работы не увидит.

Стартап за 2 дня

Опыта работы по написанию приложений для Android у меня не было. Вернее, ещё в 2011, помнится, пытался баловаться плагинчиком для Eclipse. Но много лет прошло. Плагинчик уже не модный, а модна Idea. Вот и пришлось тыкать в новые кнопки и в новых окошках (не люблю я Idea). Тут же можно добавить всякоразные там проблемы с Android API, инициализацией проекта в IDE и другой сопроводительной активностью.

Тем не менее, за два дня функционал был готов. Я мог загружать список всех новостей и просматривать отдельную из них. С меня потребовалось всего лишь дамп HTML с сайта и его парсинг. Google дал информацию как это всё организовать в приложении. Ну а новостной сайт позволял это своей архитектурой.

Немножко напильником

Само собой, нужно было слегка подпилять, что было сделано за два дня. Я особо не заморачивался с красивостью кода, тестами и прочими тормозами стартапа. Пришлось всунуть jsoup  для парсинга HTML, когда сабстрингать стало уже больно. Повозился с картинками, чтобы их ширина выравнивалась под размер экрана. Вырезка ненужного HTML и рекламных ссылок. Плюс один юнит тест. Неделя ушла на всё это.

Всё работает, но нет

В итоге у меня есть приложение, которое работает в эмуляторе. И даже впихнул его себе в телефон. Но вылезла беда. Если в сети WiFi оно ещё работает более-менее, то в мобильной сети оно совсем отказывалось вменяемо работать. Вернее, могло грузить список новостей несколько минут и то, не было гарантии, что все прогрузится. Причем речь шла о нескольких киллобайтах текста. Начались наблюдения. Оказалось, что у одного оператора тупит, у другого оператора почти не тупит. Примерно та же ситуация наблюдалась и при попытке зайти на сайт через браузер. 3G, 4G никакой роли не играли. На прямой вопрос у оператора: вы, уроды, сниффите? Ответ: нет конечно же! как вымогли подумать такое! Но факт есть факт. На все остальные сайты заходит вменяемо, но вот на мой, с новостями – тупит. Законная сторона меня лично не парит. Во первых, симка предоплатная, во-вторых закон я не нарушаю. Так что, стукачу оператору и рыцарям плаща и кинжала – физкульт привет! Парит меня, что работает никак моя аппликашка! Несколько месяцев я мучался, переключал симки в телефоне, но потом немножко разгрузился с текучкой и вернулся к разработке. Первым делом нужно было устранить сниффинг. Google плюс мой сайт с поддержкой PHP и проксик готов. Теперь контент грузится на обеих операторах одинаково, а на моём сайте заработал счётчик с посещениями. Ещё раз всем им там привет!

Грузи, грузи

За время, которое прошло, у меня на машине появился Docker. И это чудо не захотело дружить ни с VirtualBox, ни с Android эмулятором. Времени, а главное желания разбираться, в этом гауне не было. Пришлось разработку перенести на другую машину, попутно укрепив ненависть к Docker.

Чем парсишь, Вань

Всё. Новости вроде грузит нормально теперь, но вот если открываешь отдельно взятую – боль! Иногда грузит, но долго, иногда чёрный экран и ничего. Предположение первое – опять сеть. Дебаг мне в помощь и… Не сеть! Парсинг! Пришлось рефакторить парсер и мерять. Получив цифры для jsoup, я начал допиливать его конкурент на сабстрингах. И уже при частичной реализации парсинга я получил цифры 420 против 24. Я  охренел – более чем на порядок отличие! Попытался поискать аналог jsoup, нашёл какой-то noname, но и его цифры (около 120) меня не соблазнили. Кроме того, у него был более тупой API чем у jsoup. Отсюда вынужденное решение – переписывать всё на сабстринги с последующей миграцией в StringBuilder. Уже чувствую вибрацию приверженцев секты “красивый код”, но мне, чёрт возьми, нужно было работающее приложение, а не красивый код внутри хреново рабочего! Много пришлось повозиться со всякими там циклами, скобочками и прочей мелочёвкой. Университет вспоминался часто с курсом основ алгоритмизации! Никаких новых алгоритмов! Упаси бог! Просто – необычные и запутанные циклы с индексами. Много циклов и индексов. Месяц ушёл на всё это.

Ща как запиздячу

Внутренне удовлетворение достигнуто. Но чхать моё приложение хотело на моё удовлетворение. Один хрен криво работало. Иногда новость подгружается весело и задорно, иногда тьма на весь экран, но с большой вероятностью подгрузки. Такое ощущение что ресурс у операционки занят и мой запрос на сервер что-то ждёт где-то в дебрях Android. Воркэраунд для этого вроде очевидный – запилить пул тредов и в фоне загружать новости в кеш, пока я читаю первую новость или просматриваю список новостей. Здесь было две попытки. Первая привела к стохастическому свалу всего приложения без объяснения причин. После того, как обернул код загрузки новости в try-catch-Throwable, проблема исчезла.

Итог

Через пол года у меня есть в телефоне приложение, которое:

  1. Работает.
  2. Меня не бесит.
  3. Я его написал сам.
  4. Есть куда по-чучуть что-то улучшать в будущем.

Мораль

Я не вспомнил о куче мелких “камушков” которые занимали время. Иконка, цвет кнопки, картика “под краешек” экрана и прочая мелочёвка. Хочу сказать, всё оно через Ж делается в Android. Такое вот втутреннее ощущение, что рудимент и рудиментом погоняет. Работа с Android помимо самой Java, требует ещё определённой сноровки и в самом фреймворке. Нельзя снисходительно джавистам отноститься к андройдщикам!

Много я не ожидал. Изначально думал, что всё будет гораздо проще. Например, я так и не докопал можно ли логировать работу приложения, трудности были с выплывающим окошком и т.д. Не ожидал, что будет столько проблем, не связанных с разработкой. По сути много затыков лежали в области внедрения и сопровождения приложения.

До сих пор остаётся открытым вопрос: написал бы я приложение, если бы уделял внимание всякоразным бестпрактисам, юнит тестам и TDD (шепотом и восхищённо)?

Также я бы подчеркнул, что разработка приложения это не есть непосредственно девелопмент с тестированием. Очень большую роль играет деливери и сопровождение. В моём случае, как видно, это были ключевые звенья в разработке. Никакими бестпрактисами я бы не отловил проблемы с мобильной сетью! Так что, как это может ни прискорбно для многих прозвучать, но будущее за “хуяк, хуяк и в продакшн”(c).