В этой статье я хотел бы обсудить проблему дублирования кода в Adaptivist ScriptRunner.
Когда начинаешь писать скрипты в ScriptRunner, то обычно весь код пишешь в одном скрипте и затем добавляешь этот скрипт в пост функцию, валидатор, условие и тому подобное.
Все идет хорошо до тех пор, пока ты не обнаружил ошибку в коде, или не произошли изменения в бизнес-требованиях. В этом момент ты понимаешь, что поправить нужно не один скрипт, а несколько скриптов с изменившейся логикой, а еще хуже то, что ты не помнишь, какие точно скрипты нужно править.
Можно запустить поиск по директории, в которой лежат скрипты, но как показывает практика, такое решение проблемы практически всегда оставляет баги в коде.
Это классическая проблема, связанная с дублированием кода.
Попробуем избавиться от дублирования кода.
Самый простой способ решения проблемы это создание вспомогательного класса, в котором будут находиться общие части кода, а скрипт на groovy будет обращаться к этому вспомогательному классу.
Попробуем это реализовать.
Сначала создаем вспомогательный класс с именем UtilHelper.groovy в директории scripts/groovy/util:
Затем создаем пост функцию с именем postfunction.groovy в директории scripts/groovy/. В этой пост функции будем вызывать функцию getMessage из нашего вспомогательного класса.
Для того, чтобы не усложнять пример, мы не будем добавлять пост функцию в бизнес-процесс, а вызовем ее из консоли скриптов(Script Console):
Мы получили сообщение из UitlHelper.groovy файла. Значит наш способ 1 сработал.
Теперь я хотел бы рассказать о подводных камнях этого способа:
Я протестировал данный код на версии ScriptRunner 5.2.2
На более ранних версиях ScriptRunner возможны следующие проблемы:
Чтобы избежать проблем этого способа реализации, а так же получить ряд дополнительных преимуществ, я бы предложил иной способ решения вопроса с дублированием кода.
Этот способ основывается на том, что у нас есть плагин, который содержит общий код. Таким образом мы бы могли использовать общий код как в плагинах нашей разработки, так и в ScriptRunner.
В моей предыдущей статье я рассказал о том, как сделать плагин, к которому можно обращаться из других плагинов. Плагин назывался jira-library. Плагин предоставлял сервис LibraryService с функцией getLibraryMessage. Попробуем вызвать эту функцию.
Плагин jira-library можно взять здесь. Плагин нужно собрать командой
Далее мы должны написать скрипт на ScriptRunner, который будет обращаться к плагину jira-library. Для этого используются аннотации @WithPlugin и @PluginModule. @WithPlugin позволяет импортировать классы плагина jira-library, а @PluginModule позволяет внедрить(inject) LibraryService в скрипт. Подробно почитать про принцип работы этих аннотаций можно здесь.
Скрипт будет выглядеть вот так:
Попробуем запустить его в консоли скриптов:
На скриншоте видно, что сообщение из плагина jira-library отобразилось. Значит наше решение работает.
При данном способе реализации возникают следующие проблемы:
Преимущества этого подхода следующие:
Когда начинаешь писать скрипты в ScriptRunner, то обычно весь код пишешь в одном скрипте и затем добавляешь этот скрипт в пост функцию, валидатор, условие и тому подобное.
Все идет хорошо до тех пор, пока ты не обнаружил ошибку в коде, или не произошли изменения в бизнес-требованиях. В этом момент ты понимаешь, что поправить нужно не один скрипт, а несколько скриптов с изменившейся логикой, а еще хуже то, что ты не помнишь, какие точно скрипты нужно править.
Можно запустить поиск по директории, в которой лежат скрипты, но как показывает практика, такое решение проблемы практически всегда оставляет баги в коде.
Это классическая проблема, связанная с дублированием кода.
Попробуем избавиться от дублирования кода.
Способ 1
Самый простой способ решения проблемы это создание вспомогательного класса, в котором будут находиться общие части кода, а скрипт на groovy будет обращаться к этому вспомогательному классу.
Попробуем это реализовать.
Сначала создаем вспомогательный класс с именем UtilHelper.groovy в директории scripts/groovy/util:
package groovy.util;
public class UtilHelper {
public static String getMessage() {
return "util helper message";
}
}
Затем создаем пост функцию с именем postfunction.groovy в директории scripts/groovy/. В этой пост функции будем вызывать функцию getMessage из нашего вспомогательного класса.
package groovy
import groovy.util.UtilHelper;
log.error(UtilHelper.getMessage())
Для того, чтобы не усложнять пример, мы не будем добавлять пост функцию в бизнес-процесс, а вызовем ее из консоли скриптов(Script Console):
Мы получили сообщение из UitlHelper.groovy файла. Значит наш способ 1 сработал.
Теперь я хотел бы рассказать о подводных камнях этого способа:
Я протестировал данный код на версии ScriptRunner 5.2.2
- Если я добавляю файл postfunction.groovy как инлайн скрипт в консоль скриптов,
то UitlHelper класс не всегда находится для импорта. - Изначально я создал postfunction.groovy в директории scripts/groovy, а затем переместил файл в директорию scripts/groovy/util. Но несмотря на это я все-равно мог импортировать класс из директории scripts/groovy/.
На более ранних версиях ScriptRunner возможны следующие проблемы:
- Возникает статическая ошибка компиляции, связанная с тем, что компилятор не может найти импортируемый UtilHelper класс. При запуске скрипта скрипт отрабатывает без ошибок.
- Если внести изменения в вспомогательный класс, то скрипт не видит изменения в вспомогательном классе до тех пор, пока не внести изменения в скрипт.
Чтобы избежать проблем этого способа реализации, а так же получить ряд дополнительных преимуществ, я бы предложил иной способ решения вопроса с дублированием кода.
Способ 2
Этот способ основывается на том, что у нас есть плагин, который содержит общий код. Таким образом мы бы могли использовать общий код как в плагинах нашей разработки, так и в ScriptRunner.
В моей предыдущей статье я рассказал о том, как сделать плагин, к которому можно обращаться из других плагинов. Плагин назывался jira-library. Плагин предоставлял сервис LibraryService с функцией getLibraryMessage. Попробуем вызвать эту функцию.
Плагин jira-library можно взять здесь. Плагин нужно собрать командой
atlas-mvn package и
установить в Jira.Далее мы должны написать скрипт на ScriptRunner, который будет обращаться к плагину jira-library. Для этого используются аннотации @WithPlugin и @PluginModule. @WithPlugin позволяет импортировать классы плагина jira-library, а @PluginModule позволяет внедрить(inject) LibraryService в скрипт. Подробно почитать про принцип работы этих аннотаций можно здесь.
Скрипт будет выглядеть вот так:
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import ru.matveev.alexey.tutorial.library.api.LibraryService
@WithPlugin("ru.matveev.alexey.tutorial.library.jira-library")
@PluginModule
LibraryService libraryService
log.error(libraryService.getLibraryMessage()
Попробуем запустить его в консоли скриптов:
На скриншоте видно, что сообщение из плагина jira-library отобразилось. Значит наше решение работает.
При данном способе реализации возникают следующие проблемы:
- В версии выше 5.1. скрипт работает только как инлайн. Для того, чтобы скрипт заработал из файла необходимо написать скрипт вот так:
import com.onresolve.scriptrunner.runner.customisers.PluginModule import com.onresolve.scriptrunner.runner.customisers.WithPlugin import ru.matveev.alexey.tutorial.library.api.LibraryService import com.onresolve.scriptrunner.runner.ScriptRunnerImpl @WithPlugin("ru.matveev.alexey.tutorial.library.jira-library") LibraryService pl = ScriptRunnerImpl.getPluginComponent(LibraryService) log.error(pl.getLibraryMessage())
Баг открыт в Adaptivist.
Преимущества этого подхода следующие:
- Можно сделать общий код как для разрабатываемых плагинов, так и для скриптов в ScriptRunner
- Можно реализовывать в собственном плагине то, что не поддерживает ScriptRunner, и затем использовать функциональность плагина в ScriptRunner. Например, таким образом можно реализовать ротацию функций(feature toggle). Можно создать модуль типа webwork, который позволит включать и выключать какую-то функциональность, и разместить его в разделе плагины. Далее можно экспортировать сервис, который будет информировать скрипт в ScriptRunner, включена ли требуемая функциональность. Если включена, то скрипт будет выполнять необходимые действия.
andreybotanic
А я вот совсем недавно столкнулся с такой проблемой: при использовании аннотации @WithPlugin ScriptRunner не смог заимпортировать классы из моего плагина. К счастью, это проявилось лишь в том, что я не смог получить доступ к перечислениям, и эту проблему было легко обойти.