Здравствуйте. В этой статье будет описан еще один способ создания установщика для приложений в InterSystems Cache. Под приложениями здесь имеются в виду разные библиотеки или утилиты, которые могут быть добавлены или удалены из Cache всего одним действием. Если вы всё ещё пишете инструкции для пользователей по установке ваших приложений в Cache, состоящие более чем из одной строки — самое время это автоматизировать.

Постановка задачи


Допустим, мы разработали некоторую веб-утилиту для Cache, которую хотим в дальнейшем поставлять. Конечно, хотелось бы не тревожить пользователей, которые собираются её устанавливать всякими подробностями по настройке и детальными инструкциями процесса установки. К тому же, все эти инструкции должны быть максимально подробными и ориентированными на пользователей, которые могут не знать Cache. В случае с веб-утилитой при установке потребуется попросить пользователя не только импортировать классы утилиты в Cache, но и, как минимум, настроить веб-приложение для доступа к нему, что является достаточно объемным набором действий:




Конечно, не составляет особого труда все эти действия выполнить программно — нужно будет только один раз разобраться с тем, как создаются веб-приложения с помощью Cache ObjectScript. Но даже в этом случае понадобится, например, попросить пользователя запустить установочный скрипт через терминал.

Установка в один импорт


В Cache есть возможность ограничиться всего одним действием при поставке — импортом пакета классов. И этого достаточно — пользователю просто нужно будет импортировать XML-файл с пакетом классов любым знакомым ему способом:

  1. Просто перетащить XML файл на область Студии с помощью drag-n-drop;
  2. Через портал управления: Обозреватель -> Классы -> Импорт;
  3. Через терминал: do $system.OBJ.Load("C:\FileToImport.xml","ck").

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

Создание проекции


Расширить поведение компилятора Cache, а именно выполнить произвольный код при компиляции и «декомпиляции» классов позволяет создание класса-проекции в пакете, который мы собираемся установить пользователю. Это такой класс, который наследует %Projection.AbstractProjection и переопределяет два класс-метода: CreateProjection, который выполняется при компиляции класса, и RemoveProjection, который выполняется перед повторной компиляцией класса и при его удалении.

Обычно такой класс называется Installer. Давайте рассмотрим пример такого класса для нашего приложения MyPackage:

Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = (Class1, Class2) ] 
{

Projection Reference As Installer;

/// This method is invoked when a class is compiled.
ClassMethod CreateProjection(cls As %String, ByRef params) As %Status
{
	write !, "Installing..."
}

/// This method is invoked when a class is 'uncompiled'.
ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status
{
	write !, "Uninstalling..."
}

}

Поведение здесь можно описать так:

  • При первом импорте пакета выполняется только метод CreateProjection;

  • В случае повторной компиляции класса MyApp.Installer или в случае импорта этого класса “поверх” уже существующего будет вызван метод RemoveProjection с параметром recompile = 1 у старого класса (который был скомпилирован ранее), а только затем выполнится метод CreateProjection нового класса (который загружается);

  • В случае удаления пакета (а вместе с ним и класса MyApp.Installer) будет вызван только метод RemoveProjection с параметром recompile = 0.

Так же важно отметить следующее:

  • Ключевое слово класса CompileAfter содержит список имён классов нашего приложения, компиляции (или удаления) которых необходимо дождаться перед тем, как выполнять методы класса-проекции. Настоятельно рекомендуется всегда заносить в этот список имена всех классов вашего приложения, так как если в процессе компиляции других классов возникает ошибка, код в классе-проекции не должен быть выполнен;

  • Оба метода принимают первый параметр cls — это название класса, в нашем случае, оно будет всегда равняться “MyApp.Installer”. Дело в том, что такой «установщик» можно создать для любого класса нашего приложения по отдельности, унаследовав их от класса, наследующего %Projection.AbstractProjection. Только в таком случае появится смысл использовать этот параметр, но для нашей цели такого делать не нужно;

  • Оба метода принимают второй параметр params — это ассоциативный массив, который содержит много дополнительной информации о текущих настройках компиляции и набор значений параметров текущего класса в виде «имя параметра» — «значение». Можно посмотреть более детально на то, что содержится в params, выполнив zwrite params;

  • Метод RemoveProjection принимает параметр recompile, который равняется 0 только в том случае, если класс удаляется, а не компилируется повторно.

Класс %Projection.AbstractProjection также содержит и другие методы, которые мы можем переопределять, но для поставленной задачи они совсем не нужны.

Пример


Теперь посмотрим немного глубже на задачу создания веб-приложения для нашей утилиты. Смоделируем простой случай — предположим, у нас есть REST-приложение, которое всего-то отвечает “I am installed!” при попытке его открыть через веб-браузер. Чтобы создать такое приложение, нам необходимо создать класс, описывающий его:

Class MyPackage.REST Extends %CSP.REST
{

XData UrlMap
{
<Routes>
<Route Url="/" Method="GET" Call="Index"/>
</Routes>
}

ClassMethod Index() As %Status
{
	write "I am installed!"
	return $$$OK
}

}

Класс создан, теперь осталось зарегистрировать это веб-приложение через портал управления. Как это делается как раз и было показано на картинках в начале этой статьи. После выполнения указанных действий, на этом этапе было бы неплохо убедится, что веб-приложение работает, зайдя по адресу http://localhost:57772/myWebApp/ (1. Косая черта в конце необходима; 2. Порт 57772 может быть другим в вашей системе. Он будет идентичен порту, на котором вы просматривали портал управления).

Всю эту рутину создания веб-приложения конечно же можно автоматизировать, переопределив метод CreateProjection для создания веб-приложения, и метод RemoveProjection для его удаления. Наш класс-проекция в простейшем случае будет выглядеть так:

Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = MyPackage.REST ]
{

Projection Reference As Installer;

Parameter WebAppName As %String = "/myWebApp";

Parameter DispatchClass As %String = "MyPackage.REST";

ClassMethod CreateProjection(cls As %String, ByRef params) As %Status
{
    set currentNamespace = $Namespace
    write !, "Changing namespace to %SYS..."
    znspace "%SYS" // необходимо изменить пространство имён на %SYS, так как класс Security.Applications существует только там
    write !, "Configuring WEB application..."
    set cspProperties("AutheEnabled") = $$$AutheUnauthenticated // общедоступное приложение
    set cspProperties("NameSpace") = currentNamespace // приложение для области, куда производится импорт
    set cspProperties("Description") = "A test WEB application." // описание приложения
    set cspProperties("IsNameSpaceDefault") = $$$NO // это приложение не является основным для области
    set cspProperties("DispatchClass") = ..#DispatchClass // ранее написанный класс-обработчик
    return ##class(Security.Applications).Create(..#WebAppName, .cspProperties)
}

ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status
{
    write !, "Changing namespace to %SYS..."
    znspace "%SYS"
    write !, "Deleting WEB application..."
    return ##class(Security.Applications).Delete(..#WebAppName)
}

}

В этом примере при каждой компиляции класса MyPackage.Installer будет создаваться веб-приложение, а при «декомпиляции» — удаляться. Конечно, было бы хорошим тоном добавить немного проверок на то, существуют ли веб-приложение перед тем, как его удалять или создавать (##class(Security.Applications).Exists(“Имя”)), но ради простоты примера оставим это читателю как домашнее задание.

На данном этапе, после создания классов MyPackage.REST и MyPackage.Installer, мы можем экспортировать классы как один XML файл и делиться этим файлом со всеми желающими. У них, в свою очередь, при импорте и компиляции XML файла автоматически будет создано веб-приложение, и всё, что останется сделать, так это зайти по указанному вами в инструкции адресу.

Рассмотренный в этой статье пример приложения, которое само себя устанавливает, можно скачать отсюда и сразу попробовать «установить».

Итог


В отличие от метода установки приложений с использованием системного класса %Installer, о котором так же писали на хабре, установка через класс-проекцию обладает следующими значительными отличиями:

  1. Используется только «чистый» Cache ObjectScript. Для %Installer’а же необходимо заполнить xData-блок специфичной разметкой, описанной немалым куском документации;

  2. Метод установки выполняется незамедлительно после импорта и компиляции классов нашего приложения, и его не нужно вызывать отдельно;

  3. Автоматически выполняется метод удаления при удалении класса (пакета), что априори нельзя реализовать через %Installer.

Данный подход к поставке приложений уже используется в моих проектах — Cache WEB Terminal и Cache Class Explorer. Там же можно подсмотреть и пример реализованного класса Installer.

Хотелось бы напоследок добавить о том, что сообщество инженеров InterSystems экспериментирует с внедрением Package Manager’a, который уже давно существует для таких платформ как NodeJS (npm), Ruby (RubyGems) и т.д. Этот инструмент позволит устанавливать и настраивать любые пакеты и приложения (такие как веб-терминал для Cache) используя всего одну команду. Ну а пока новые приложения на InterSystems Cache и Ensemble с исходными кодами можно найти в этом репозитории.

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


  1. morisson
    18.03.2016 18:14
    +2

    Установка — ОК! А как быть с обновлением решений с помощью такой поставки?


    1. ZitRo
      18.03.2016 20:39
      +2

      Кажется, это вопрос на ещё одну статью!

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

      Вот небольшая сводка того, что уже есть по этой теме на сегодня:

      1. В Cache WEB Terminal, при выполнении команды /update, на сервере вызывается метод Update. Он загружает из сети XML указанной версии, а затем импортирует и компилирует загруженные классы;
      2. В проекте DeepSeeWeb так же есть метод, загружающий XML последнего релиза с GitHub;
      3. Есть утилита для continuous-обновления приложений напрямую из репозитория, если там хранятся XML — GitHubUpdater.


      1. morisson
        19.03.2016 00:49

        Ну то есть вот это Update уже работает не через Projection механизм, а как-то еще, правильно?


        1. ZitRo
          19.03.2016 01:06
          +1

          Само обновление (процесс загрузки классов из сети) — да. Нужно дополнительно написать код, который будет заниматься обновлением: скачает и импортирует XML (или *.cls с 2016.1) файлы. Проекции — это всего лишь возможность выполнить что-то во время компиляции.