В этой статье я хотел бы обсудить проблему дублирования кода в Adaptivist ScriptRunner.
Когда начинаешь писать скрипты в 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):

image

Мы получили сообщение из UitlHelper.groovy файла. Значит наш способ 1 сработал.
Теперь я хотел бы рассказать о подводных камнях этого способа:

Я протестировал данный код на версии ScriptRunner 5.2.2

  1. Если я добавляю файл postfunction.groovy как инлайн скрипт в консоль скриптов,
    то UitlHelper класс не всегда находится для импорта.
  2. Изначально я создал postfunction.groovy в директории scripts/groovy, а затем переместил файл в директорию scripts/groovy/util. Но несмотря на это я все-равно мог импортировать класс из директории scripts/groovy/.

На более ранних версиях ScriptRunner возможны следующие проблемы:

  1. Возникает статическая ошибка компиляции, связанная с тем, что компилятор не может найти импортируемый UtilHelper класс. При запуске скрипта скрипт отрабатывает без ошибок.
  2. Если внести изменения в вспомогательный класс, то скрипт не видит изменения в вспомогательном классе до тех пор, пока не внести изменения в скрипт.

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

Способ 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()

Попробуем запустить его в консоли скриптов:

image

На скриншоте видно, что сообщение из плагина 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, включена ли требуемая функциональность. Если включена, то скрипт будет выполнять необходимые действия.

Комментарии (1)


  1. andreybotanic
    09.02.2018 17:33

    А я вот совсем недавно столкнулся с такой проблемой: при использовании аннотации @WithPlugin ScriptRunner не смог заимпортировать классы из моего плагина. К счастью, это проявилось лишь в том, что я не смог получить доступ к перечислениям, и эту проблему было легко обойти.