Здравствуйте. В этой статье будет описан еще один способ создания установщика для приложений в InterSystems Caché. Под приложениями здесь имеются в виду разные библиотеки или утилиты, которые могут быть добавлены или удалены из Caché всего одним действием. Если вы всё ещё пишете инструкции для пользователей по установке ваших приложений в Caché, состоящие более чем из одной строки — самое время это автоматизировать.
Постановка задачи
Допустим, мы разработали некоторую веб-утилиту для Caché, которую хотим в дальнейшем поставлять. Конечно, хотелось бы не тревожить пользователей, которые собираются её устанавливать всякими подробностями по настройке и детальными инструкциями процесса установки. К тому же, все эти инструкции должны быть максимально подробными и ориентированными на пользователей, которые могут не знать Caché. В случае с веб-утилитой при установке потребуется попросить пользователя не только импортировать классы утилиты в Caché, но и, как минимум, настроить веб-приложение для доступа к нему, что является достаточно объемным набором действий:
Конечно, не составляет особого труда все эти действия выполнить программно — нужно будет только один раз разобраться с тем, как создаются веб-приложения с помощью Caché ObjectScript. Но даже в этом случае понадобится, например, попросить пользователя запустить установочный скрипт через терминал.
Установка в один импорт
В Caché есть возможность ограничиться всего одним действием при поставке — импортом пакета классов. И этого достаточно — пользователю просто нужно будет импортировать XML-файл с пакетом классов любым знакомым ему способом:
- Просто перетащить XML файл на область Студии с помощью drag-n-drop;
- Через портал управления: Обозреватель -> Классы -> Импорт;
- Через терминал: 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
{
<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, о котором так же писали на хабре, установка через класс-проекцию обладает следующими значительными отличиями:
- Используется только «чистый» Caché ObjectScript. Для %Installer’а же необходимо заполнить xData-блок специфичной разметкой, описанной немалым куском документации;
- Метод установки выполняется незамедлительно после импорта и компиляции классов нашего приложения, и его не нужно вызывать отдельно;
- Автоматически выполняется метод удаления при удалении класса (пакета), что априори нельзя реализовать через %Installer.
Данный подход к поставке приложений уже используется в моих проектах — Caché WEB Terminal и Caché Class Explorer. Там же можно подсмотреть и пример реализованного класса Installer.
Хотелось бы напоследок добавить о том, что сообщество инженеров InterSystems экспериментирует с внедрением Package Manager’a, который уже давно существует для таких платформ как NodeJS (npm), Ruby (RubyGems) и т.д. Этот инструмент позволит устанавливать и настраивать любые пакеты и приложения (такие как веб-терминал для Caché) используя всего одну команду. Ну а пока новые приложения на InterSystems Caché и Ensemble с исходными кодами можно найти в этом репозитории.