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

  • Tutorial

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

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


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




Конечно, не составляет особого труда все эти действия выполнить программно — нужно будет только один раз разобраться с тем, как создаются веб-приложения с помощью 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
{
<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. Используется только «чистый» 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 с исходными кодами можно найти в этом репозитории.

InterSystems

95,42

Вендор: СУБД Caché, OLAP DeepSee, шина Ensemble

Поделиться публикацией

Похожие публикации

Комментарии 25
    +2
    Установка — ОК! А как быть с обновлением решений с помощью такой поставки?
      +2
      Кажется, это вопрос на ещё одну статью!

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

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

      1. В Caché WEB Terminal, при выполнении команды /update, на сервере вызывается метод Update. Он загружает из сети XML указанной версии, а затем импортирует и компилирует загруженные классы;
      2. В проекте DeepSeeWeb так же есть метод, загружающий XML последнего релиза с GitHub;
      3. Есть утилита для continuous-обновления приложений напрямую из репозитория, если там хранятся XML — GitHubUpdater.
        0
        Ну то есть вот это Update уже работает не через Projection механизм, а как-то еще, правильно?
          +1
          Само обновление (процесс загрузки классов из сети) — да. Нужно дополнительно написать код, который будет заниматься обновлением: скачает и импортирует XML (или *.cls с 2016.1) файлы. Проекции — это всего лишь возможность выполнить что-то во время компиляции.
      +1
      А вот как кастомизацию установки делать? Куда поставилось-туда поставилось? ) Если вдруг по условиям установки требуется что-то спросить у пользователя как это здесь?
        0
        Исследовал как-то, что read в случае с проекциями, к сожалению, не работает. Так что чуть ли не единственным вариантом остаётся просить пользователя задавать какой-то глобал перед установкой. А при отсутствии глобалов устанавливать приложение "по-умолчанию", что, в принципе, для большинства пользователей подходит.
          0
          Ну я бы тут не read пользовался, а вызывал бы UI, может средствами стандартного CSP…
          Или так: сначала ставишь нечто, что позволяет организовать простой диалог с пользователем, а потом уже используешь эту "диалоговую" тулзу для кастомизации параметров.
            0
            Это нереально полезно открывать во время установки окно браузера с настройками приложения. Если знает кто код такой, и это правда возможно, было бы любопытно узнать как.
              +2
              ClassMethod RunPage(Url As %String = "http://intersystems.ru")
              {
                  #Include %occOptions
                  Set Browser = $s($$$isWINDOWS:"start", $$$isUNIX:"x-www-browser", 1:"x-www-browser")
                  Set Command = Browser _ " " _ Url
                  Do $ZF(-1, Command)
              }

              Откроет веб-страницу в браузере по умолчанию.

                0
                Откроет на сервере, и то не факт, что касается Windows.
                  0
                  Откроет на сервере
                  Да. Но как это ограничение обойти не представляю.
                  и то не факт, что касается Windows.
                  Работает на Windows XP.
                    +1
                    а никак не обойти $zf работает на сервере и все тут.
                    на XP да, на чем то новее не будет, точнее на win7 с бубном можно еще сделать, нужно разрешить интерактивный режим
                    так на win8,10 уже все
                    так что лучше ничего интерактивного не запускать на сервере.
                      0
                      У меня на 10ке работает. При вызове из терминала пользователь будет интерактивный, а не тот, кто службу запустил.
                      В студии этот же пользователь, но страницу не открывает.
                0
                Мне кажется, тут надо разделять место импорта: если импорт пакета установки делается через drag-n-drop в студию, речь идет о клиенте и винде.
                А если через импорт классов в панели управления — это уже точно сервер.
                Если это упростить и свести к тому, что и в случае Студии и случае Панели управления будет открываться некое приложение в браузере (т.е. на сервере), то можно предустановить сначала некую простенькое приложение, которое сможет динамически формировать диалоговый UI для устанавливаемых пакетов.
                Что думаете?
                  0
                  Если идет импорт через студию это можно сделать, можно отловить этот процесс, и возможно даже запустить в студии диалог
                  но сделать это можно только через класс системы контроля версий
                  там есть действие на импорт файла, и при импорте можно отобразить web-страницу в студии, и тут уже можно как-нибудь придумать и вытянуть все необходимое из загружаемого файла и после сделать установку по настройкам введенным с такой страницы
                  проблема в том что сервер уже должен быть настроен на подобнове поведение, и в том что пока нельзя установить несколько разных классов контроля версий без хитрого переделывания
                    0
                    Ребята, это все ужасно. Overdesign, over complicated.
                    1. Если надо параметризовать через аргументы или с интерактивом установку чего-то, то используем %Installer.Manifest и генерируемую функцию вызываем из командной строки с аргументами или спрашивая о них.
                    2. Если есть возможность инсталлироваться никого не спрашивая, то используем проекции. Если надо что-то спросить, см. пункт 1

                    P.S.
                    %Installer.Manifest хорошо интегрируется в пользовательские установщики (которые могут быть с GUI и похожими пирогами).
                      –1
                      Ребята, это все ужасно.

                      Что ж тут ужасного? мне нравится этот способ установки. "Ужасного" в нем только то, что это похоже на бэкдор: импортировал пакет с классами, а заодно тебе поставилось все что угодно.
                      Как можно улучшить: добавить в панель управления Caché ссылку: Установка приложений, которая ожидает импорт файла с пакетом и предлагает еще и API для интерактивного диалога а еще и в одно всем известное место кладет логи и ошибки установки.
                        +2
                        и тут пора вспомнить про CPM, развивая его и внедряя в поставку Caché, можно будет такого добиться
                          +2
                          С этим невозможно не согласиться. Пора нам, Дима, тряхнуть стариной, и таки выпустить CPM в жизнь!
                            +3
                            /me ушёл писать модуль leftpad для Caché ObjectScript
                              0
                              Напиши! И сразу позовем alizar сделать пост)
          0
          Еще одна штука: неплохо бы после инсталляции видеть логи, что куда поставилось и были ли ошибки. Куда-то в стандартный лог пишется? Если да, то в какой? в cconsole.log или еще куда?
            0
            По-умолчанию пишется на текущее устройство. А вот %Installer предоставляет интерфейс логгера, с помощью которого можно организовать запись как угодно.

              0
              По-умолчанию пишется на текущее устройство.

              Ну это если делать write. А можно поиспользовать этот интерфейс логгера для указанного в статье способа установки? (т.е. без %Installer)?
              Или было же что-то, чтобы писать в cconsole.log
                +1
                А можно поиспользовать этот интерфейс логгера для указанного в статье способа установки?

                Можно написать сам логгер и вызывать его.

                Или было же что-то, чтобы писать в cconsole.log

                Это из коробки есть (документация):
                Set Status = ##class(%SYS.System).WriteToConsoleLog(Message, Flag, Severity) 

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое