02 марта 2015

Дебажимся в продакшене или как я взял и упоролся Groovy

Настоящие лиды фиксят баги на продакшене изменением параметров.
(c) один из настоящих лидов.


Все мы прекрасно знаем, что очень многое в любом приложении (да чего уж там - куске кода) определяется параметрами. Крутанул в одну сторону - работает, крутанул в другую - не работает.

Знаем и пользуемся этим. За что иногда хочется оторвать руки.
В большинстве случаев параметры получаются один раз на старте приложенияи и могут быть переопределены только после его рестарта. Однако есть случаи когда параметры необходимо переопределять в runtime и для этого тоже есть инструменты.

Лирическое отступление: Если последняя фраза (про переопределение параметров в runtime) вас смутила, повергла в шок или просто удивила - идите и смотрите как это делается у Netflix на примере Archaius. Дальше вам читать может быть без пользы.

Но не все управляется параметрами.
Бывает так, что подкрутить нужно не только параметры, но и логику.
С логикой все несколько хуже, я бы даже сказал стремновато.
Перекрутить логику можно на заранее заготовленную (другая реализация интерфейса и переключение параметра), но трудно переткнуть на новую - то есть ту которой в рантайме приложения нет.

Я говорю «трудно» потому, что ничего невозможного нет, и для этой задачи в Java например есть OSGI.  Вот с этой строчки вы бы наверное уже могли бы на меня накинутся, но потерпите - я и сам не фанат OSGI и прекрасно отдаю себе отчет о том, чего стоит использование таких вот решений и как придется переколбасить архитектуру приложения.

Не хочется OSGI но хочется крутить логику ? Ну ок, тогда читаем дальше.
А читать-то собственно и немного. Идите на гитхаб, я уже все слепил.

Что там ?  Там стандартная Jetty c одним сервлетом который принимает один параметр (строковый) и возвращает вам его в некоем обработанном виде. Обработки ошибок и всего что там по идее должно быть там нет.

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


Дальше собственно самое интересное.

Берем листинг номер 1, сохраняем в файлик, скармливаем приложению на старте - и, вуаля, у нас с вами есть какая-то логика, которая просто будет нам возвращать строку которую передали в приложение.



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



Как это работает ?

Работает это на дефолтных возможностях Groovy по парсингу кода.
Ключевой является вот эта строчка кода
   

Тут Groovy грузит исходный код из файла и компилирует его.
Внимание! ClassLoader от Groovy кэширует сорсы, так что не стоит удивляться если что-то не прогрузилось, а стоит использовать параметр метода parse которые говорит кэшировать или нет.
Приведение объекта содержащего логику к интерфейсу изначально было сделано сугубо из-за простоты такого подхода, но после,  в процессе размышлений я пришел к выводу что так и надо : хочешь менять логику - потрудись хотя бы выделить под нее интерфейс и реализовать его!

Ограничения решения.

Первое и самое главное ограничение - это то что ваша «сменяемая» логика не может тащить за собой зависимости на код которого нет в рантайме. То есть если вы в альтернативной версии логики хотите попользоваться другой библиотекой,эмммм скажем для нарезки изображений, то вы должны протащить ее (при деплое приложения) заблаговременно. Если вас не устраивает подобного рода ограничение - то вам нужно идти на … OSGI.

Второе ограничение решения  - слабая устойчивость к конфигурационным ошибкам.
Если кто-то скормит в такое место некорректную логику (некомпилируемый код), то логика вашего приложения в этой точке не будет работать. Совсем. Что из этого следует для вас - придумайте сами. Можно предпринять какие-то методы защиты - кэшировать предыдущее значение, откатываться к нему в случае чего, автоматически откатываться на реализацию логики по умолчанию и прочее, но фундаментально это проблемы не решает - это решение плохо защищено. Отсюда вывод и рекомендация - использовать подобного рода решения только для проведения экспериментов и/или дебага.

Третье - Groovy сильно медленнее чистой Java. Если вы хотите вставить это в нагруженный/частоиспользуемый кусок кода, то искренне надеюсь, что вы это делаете по нужде (дебаг) или из профессионального любопытства (контроллируемый эксперимент в полевых условиях). Вывод - окончание эксперимента/отладки должно привести к убиранию такой разводки в коде.

Преимущества решения.


Преимущество первое - реальный боевой код кругом, реальное окружение, реальные данные (если не боитесь их повредить/потерять).
Второе - писать такую логику можно прямо в окружении проекта, потом просто копировать  код и подсовывать в приложение.
Преимущество третьв (хипстерское)  - синтаксический сахар грувей. Но лично я к нему прохладен.


К исследованию возможности такого решения меня побудили вот эти статьи на хабре (раз и два).

Матчасть.
P.S. и не упарывайтесь, пожалуйста.
P.P.S да, я знаю что есть Java 8 , а в ней Nashorn и все такое. Просто я не люблю JS.