Pull to refresh

Code evaluation как средство отладки

Reading time3 min
Views2.8K

Господа разработчики java приложений. Сегодня вашему вниманию представляется простой способ использования code evaluation, реализация которого позволит исполнять произвольный код в работающем приложении, что в свою очередь позволит сэкономить массу времени на CI/CD.

Зачем это нужно?

Если вам приходится разрабатывать в условиях микросервисной архитектуры, особенно в большой компании, то скорее всего вам знакома ситуация, когда посмотреть как "по-боевому" работает приложение можно только на стендах на которых есть интеграции с другими микросервисами. И поскольку далеко не все вещи можно проверить локально на заглушках, для того чтобы проверить ту или иную гипотезу, приходится пушить новый код (не факт что работающий корректно) в репозиторий... прогонять его через CI/CD... и только потом по логам понять что где-то что-то пошло не так. И хорошо если логи сразу покажут в чём вы ошиблись, потому как иначе этот процесс пуша и прогона по пайплайнам может стать вашим круговоротом сансары.

Исполнение динамически введённого кода поможет решить эту проблему.

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

Как мы с вами знаем, groovy это полностью совместимый с Java язык программирования с динамической компиляцией. Эти две особенности groovy и помогут нам в том, что бы реализовать code evaluation для java приложений. О том как добавить поддержку groovy в java проект вы сможете легко найти сами. А я приведу пример, как реализовать code evaluation (в некотором смысле аналогичный тому, который вы можете видеть во время дебага в вашей IDE).

1) Создадим groovy класс, а в нём шаблонную строку в которую поместим класс и плейсхолдер для динамически введённого кода. Пример такой строки:

def EXPRESSION_CLASS_TEMPLATE = """
package dev.toliyansky.eval.service
class ExpressionClass implements java.util.function.Supplier<Object> {
	def get() {
		%s
	}
}
"""

Примечание: package должен быть такой же как и у класса в котором вы этот код будете писать.

Почему мы имплементируем Supplier будет описано ниже.

2) Динамически скомпилируем и загрузим этот класс.

Кусок кода ниже может быть размещён например в REST контроллере, который получает code как тело запроса.

def finalClassCode = String.format(EXPRESSION_CLASS_TEMPLATE, code)
def supplier = groovyClassLoader.parseClass(finalClassCode)
                                .getDeclaredConstructor()
                                .newInstance() as Supplier<Object>
def result = supplier.get()

В первой строчке заменяем %s на код который хотим динамически ввести и исполнить в рантайме.

Во второй строке компилируем и загружаем класс из строки, которую получили на предыдущем шаге. Тут важно заметить что созданный инстанс класса приводим к Supplier для того, чтобы далее в третьей строчке можно было сделать вызов метода в котором должен исполниться динамически введённый код. Supplier<Object> в данном случае идеально нам подходит.

Вот собственно и всё.

Пример использования code evaluation

Допустим у вас web приложение в kubernetes. Вы написали какую-то новую фичу, закоммитились, прошли код ревью, прогнали код через CI/CD и вот POD с вашим приложением наконец поднимается, вы заходите в логи и видите что фича работает не так как ожидалось. Допустим, для примера, что вы забыли в конструкторе что-то проинициализировать и поэтому остальная бизнес логика не отрабатывает с банальным NullPointerException.

Имея в своём арсенале HTTP роут с исполнением динамического кода, можно спокойно обратиться к applicationContext, вытащить нужный бин и ручками проинициализировать забытую переменную. После чего потыкать в инициирование отработки бизнес кода и проверить результат, минуя весь CI/CD. Таким банальным примером всё не ограничивается. При желании можно даже в райнтайме переопределить метод класса с бизнес логикой и играться до тех пор пока не отладите код и потом уже зная как оно себя ведёт коммититься и прогонять пайплайны.

Готовое решение для web приложений на spring boot

Если вы не хотите возиться с добавлением groovy в ваш java проект и писать все эти контроллеры, обёртки, разбираться с динамикой, а просто хотите добавить в одну строчку зависимость в ваш проект и чтобы работало из коробки, то тогда специально для вас презентую маленький проект который всё это умеет - evaluator-spring-boot-starter

Это, как можно догадаться из названия, spring boot starter. Подключение данного стартера добавляет в ваше web приложение роут http://host:port/eval отдающий WEB-UI, в котором можно ввести код который хотите динамически исполнить, а результат выведется в окне рядом. Всё это приправлено подсветкой синтаксиса, и прочими удобствами. Если же, например, ваше приложение не имеет выходного роута из кластера и вы можете только лишь использовать curl или wget из терминала POD, то тогда можно использовать роут http://host:port/eval/groovy и отправлять код как параметр GET запроса или как тело POST запроса.

Впрочем, всё это более-менее подробно описано в readme проекта.

Скриншот WEB-UI evaluator-spring-boot-starter
Скриншот WEB-UI evaluator-spring-boot-starter

В итоге

  • Продемонстрировано как code evaluation может сэкономить время на отладке приложения

  • Продемонстрировано как реализовать code evaluation в java проекте

  • Продемонстрировано готовое решение в виде spring boot starter.

Tags:
Hubs:
Total votes 7: ↑6 and ↓1+7
Comments4

Articles