Установка приложений в Caché с использованием проекций

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

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


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

5e860f9077ee46c985f7af1c75a7c0f8.png


d83e515656c140e98c429d8d103a8665.png


b89b68d17d204d51a94c3a85492f8ba7.png


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

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


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

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


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

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


Расширить поведение компилятора Caché, а именно выполнить произвольный код при компиляции и «декомпиляции» классов позволяет создание класса-проекции в пакете, который мы собираемся установить пользователю. Это такой класс, который наследует %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
{



}

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. Используется только «чистый» Caché ObjectScript. Для %Installer«а же необходимо заполнить xData-блок специфичной разметкой, описанной немалым куском документации;
  2. Метод установки выполняется незамедлительно после импорта и компиляции классов нашего приложения, и его не нужно вызывать отдельно;
  3. Автоматически выполняется метод удаления при удалении класса (пакета), что априори нельзя реализовать через %Installer.


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

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

© Habrahabr.ru