Построение RESTful web API в Caché

В InterSystems Caché появилась поддержка REST. О том, что такое REST на Хабре уже писали и не раз. Если кратко — это паттерн построения RESTful web API, и ему присущи следующие свойства:

  • разделение клиента и сервера,
  • независимость от состояния (stateless),
  • кэшируемая и многоуровневая архитектура,
  • единый интерфейс,
  • все запросы к RESTful web API состоят из корневого URL приложения плюс частные подзапросы,
  • CRUD через HTTP — HTTP методы GET, PUT, POST, DELETE (RESTful web API).

Типичное REST-приложение выглядит примерно так: есть корневой URL (http://example.com/resources/) и дочерние URL (http://example.com/resources/item17), к которым мы обращаемся по HTTP, с помощью с методов GET, PUT, POST, DELETE. Ниже таблица методов и действий с одним элементом и коллекциями:

Метод Запросы к коллекции элементов
http://example.com/resources
Запросы к одному элементу
http://example.com/resources/itemID
GET Получить список URI элементов коллекции, возможно доп. информацию Получить всю информацию об элементе
PUT Заменить существующую коллекцию на новую Заменить существующий элемент на новый
POST Создать новый элемент коллекции Как правило не используется
DELETE Удалить всю коллекцию Удалить элемент коллекции

А как в Caché?

В СУБД Caché поддержка REST появляется начиная с версии 2014.1 — эта версия пока доступна в виде филд-тест версии для партнеров и вузов InterSystems Campus. Чтобы создать REST приложение, нужно в настройках веб-приложения Caché определить класс-брокер, в котором указываются возможные расширения базового URL и соответствующие действия приложения при запросе этих расширений.
Класс-брокер создается как наследник класса %CSP.REST. Далее в нем прописывается карта путей URL приложения (например http://example.com/resources/ID — GET ), причем каждому URL в соответствие ставится метод класса Caché, который будет выполнять всю работу.
Карта путей — это перечисление всех возможных URL для обращения к приложению для получения данных или для изменения данных на сервере.

Примерная блок-схема работы REST веб приложения в Caché

rest-webapp-diagram

За дело

Для начала создадим веб-приложение /rest с Dispatch Class REST.Broker

  1. В Портале Управления Системой (Портал):
    • Портал → Администрирование системы → Безопасность → Приложения → Веб приложения
    • Нажмите кнопку «Создать новое веб приложение»
  2. На странице редактирования веб приложения нужно заполнить следующие поля (остальные поля остаются без изменений):
    • Имя: /rest (слеш обязателен)
    • Область: USER
    • Dispatch Class: REST.Broker (строка регистрозависима)
  3. Нажмите кнопку Сохранить

rest-mgmt-portal-add-app

Теперь надо создать класс REST.Broker — карту путей будущего web API. Открываем Studio

  1. Перейдите в область USER
  2. Создайте новый класс REST.Broker, нажав Ctrl+N или из Меню: Файл→Новый.
  3. Выберите вкладку Общие а там Класс Caché
  4. В мастере создания класса:
    • Введите имя пакета: REST
    • Укажите имя класса: Broker
    • Нажмите кнопку Далее
  5. В окне Тип класса:
    • Нажмите на кнопку Расширения
    • Имя класса-предка: %CSP.REST
    • Нажмите кнопку Завершить

Прописываем в классе-брокере карту путей и метод-обработчик

Class REST.Broker Extends %CSP.REST
{
XData UrlMap
{
<Routes>
 <Route Url="/test" Method="GET" Call="Test"/>
 </Routes>
}
ClassMethod Test() As %Status
{
    &html<Работает!>
    quit $$$OK
}
}

В карте путей XData UrlMap при доступе к URL /test происходит вызов метода Test класса REST.Broker. В случае вызова методов других классов, в Call нужно также указывать имя класса

По адресу http://<адрес сервера>/rest/test должна выводиться надпись «Работает!»
Простейшее RESTful web API готово.

Подготовка данных

Для более сложного примера нам понадобятся данные. Создадим класс Data.Company

  1. В мастере создания класса:
    • Имя пакета: Data
    • Имя класса: Company
    • Нажмите кнопку Далее.
  2. В окне Тип класса:
    • Нажмите на кнопку Persistent
    • Нажмите кнопку Далее
    • Выберите опцию XML Enabled
    • Выберите опцию Data Population (Генерация тестовых данных)
  3. Нажмите кнопку Завершить

Создадим свойство Name — у каждой компании должно быть название

Class Data.Company Extends (%Persistent%Populate%XML.Adaptor)
{
Property Name As %String(POPSPEC "Company()");
}

POPSPEC "Company()"

— сообщаем генератору тестовых данных, чего мы от него хотим, а то он бы нам имена людей выдавал тут.
Заполним класс тестовыми данными с помощью команды в терминале:

##class(Data.Company).Populate(10)

На стороне сервера

Для демонстрации CRUD операции Return (HTTP — GET) мы создадим новый класс REST.JSON для задач генерации JSON ответов на запросы к RESTful сервису.

Создаём класс REST.JSON

  1. В мастере создания класса:
    • Имя пакета: REST
    • Имя класса: JSON
    • Нажмите кнопку Далее.
  2. В окне Тип класса:
    • Нажмите на кнопку Расширения
    • Имя класса-предка: %Base
  3. Нажмите кнопку Завершить

Для начала напишем метод, отдающий JSON, содержащий список компаний со всеми их свойствами

ClassMethod GetAllCompanies() As %Status
{
    
   set st=$$$OK
   try {    
   do ##class(%ZEN.Auxiliary.jsonSQLProvider).%WriteJSONFromSQL(,"select * from Data.Company")
   catch ex {
       set st=ex.AsStatus()
   }
   quit st
}

Интересен здесь разве что метод класса jsonSQLProvider — он выводит на текущее устройство результат SQL запроса в формате JSON. Обратите внимание на запятую в списке параметров, она обязательна, т.к. первый параметр – опциональное имя javascript переменной на стороне клиента.

Отдавать данные в JSON мы научились, однако брокер об этом не знает.

Добавим в карту путей REST.Broker путь, чтобы знал

Route Url="/json/companies" Method="GET" Call="REST.JSON:GetAllCompanies"/>

Готово! Теперь по адресу http://<адрес сервера>/rest/json/companies вас ждёт список компаний в JSON.

Серверу — клиента!

А на стороне клиента мы будем превращать JSON, во что-нибудь, приятное глазу. Для этого используем MVC JS-фреймворк AngularJS.

Но для начала создадим новую CSP страницу

  1. В Caché Studio, создайте новую CSP страницу, нажав Ctrl+N или из Меню: Файл→Новый
  2. Выберите вкладку CSP файл
  3. Выберите Caché Server Page и нажмите кнопку OK
  4. Сохраните созданную страницу в папке csp/user под именем rest.csp

Делаем запрос к серверу, получаем ответ — список компаний в JSON — отображаем его

<!doctype html>
<html ng-app>
<head>
<title>REST Academy</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
<script language="javascript">
function ctrl($scope,$http) {
    // Запрос GET к RESTful web API
   $http.get("/rest/json/companies").success(function(data) {
        // Помещаем ответ сервера в переменную companies
       $scope.companies=data.children;
   }).error(function(data, status) {
        // Вывод информации об ошибке, если таковая возникнет
       alert("["+status+"]   Ошибка при загрузки компаний!["+data+"]");
   })
};
</script>
</head>
<body ng-controller="ctrl">
    <div ng-repeat="company in companies">
        {{company.Name}}
    </div>
</body>
</html>

Компилируем страницу, переходим по адресу http://<адрес сервера>/csp/user/rest.csp и смотрим на полный список компаний.

Реализация Create, Update, Delete на сервере

Реализуем серверную бизнес-логику для оставшихся 3 операций CRUD: добавление, изменение и удаление компании.

Для этого в класс REST.JSON добавляем методы

ClassMethod CreateCompany() As %Status
{
    st=$$$OK
    try {
    // Берём JSON из запроса и конвертируем в объект класса Data.Company
    $$$THROWONERROR(st,##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(%request.Content,"Data.Company",.obj,1))
    $$$THROWONERROR(st,obj.%Save())
    } 
    catch ex {
        st=ex.AsStatus()
    }
    quit st
}
ClassMethod DeleteCompany(compid As %StringAs %Status
{
    set st=$$$OK
    try {
        $$$THROWONERROR(st,##class(Data.Company).%DeleteId(compid))
    catch ex {
        st=ex.AsStatus()
    }
    quit st
}
ClassMethod UpdateCompany(compid As %StringAs %Status
{
 set st=$$$OK
 try {
   
   $$$THROWONERROR(st,##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(%request.Content,,.obj,1))
   
   // Открываем объект, который хотим отредакнировать
   set comp=##class(Data.Company).%OpenId(compid)
   throw:comp=$$$NULLOREF ##class(%Exception.StatusException).CreateFromStatus($$$ERROR(5001,"Company does not exist"))
    // Редактируем и сохраняем
   set comp.Name=obj.Name
   $$$THROWONERROR(st,comp.%Save())
 } 
 catch ex {
   set st=ex.AsStatus()
 }
 quit st
}

Добавляем соответствующие пути в брокер

<Route Url="/json/company" Method="POST" Call="REST.JSON:CreateCompany"/> 
<Route Url="/json/company/:compid" Method="DELETE" Call="REST.JSON:DeleteCompany"/>
<Route Url="/json/company/:compid" Method="PUT" Call="REST.JSON:UpdateCompany"/>

На этом завершается создание CRUD-полного RESTful web API в Caché.

Клиентская реализация Create, Update, Delete

Добавляем странице rest.csp функциональность по созданию, изменению, удалению компаний

<!doctype html>
<html ng-app>
<head>
<title>REST Academy</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
<script language="javascript">
function ctrl($scope,$http) {
    // Запрос GET к RESTful web API
        $scope.getCompanies=function() {
       $http.get("/rest/json/companies").success(function(data) {
             // Помещаем ответ сервера в переменную companies
           $scope.companies=data.children;
       }).error(function(data, status) {
                 // Вывод информации об ошибке, если таковая возникнет
           alert("["+status+"] Ошибка при загрузке компаний! ["+data+"]");
       });
   };
  
      // Создать новую компанию
    $scope.create = function (company){
       $http.post("/rest/json/company",company)
       .success(function(data){$scope.getCompanies();$scope.alertzone="Добавили компанию "+company.Name;}).error(function(data,status){
        $scope.alertzone="["+status+"] Ошибка добавления компании :( ["+data+"]"; });
    }

    // Обновить существующую компанию
  $scope.update = function (company){
       $http.put("/rest/json/company/"+company.ID,company)
        .success(function(data){$scope.alertzone="Обновили компанию "+company.Name;}).error(function(data,status){ // поменял alert(....); на alertzone
        $scope.alertzone="["+status+"] Ошибка обновления имени компании :( ["+data+"]"; });
    }
            
    // Удалить компанию
    $scope.delete = function (company){
        $http.delete("/rest/json/company/"+company.ID)
        .success(function(data){$scope.getCompanies();$scope.alertzone="Удалили компанию "+company.Name;}).error(function(data,status){
            $scope.alertzone="["+status+"] Ошибка удаления компании :( ["+data+"]"; });
    }
};
</script>
</head>
<body ng-controller="ctrl" ng-init="getCompanies();">

<h4 ng-model="alertzone"><font color=red>{{alertzone}}</font></h4>

<form name="compCreateForm" ng-model="company" ng-submit="create(company); company='';">
    Добавить компанию <input type="text" ng-model="company.Name"/>
    <input type="submit" value="Добавить"/>
</form>
<br>
<div ng-repeat="company in companies">
    <form name="compForm" ng-submit="update(company); compForm.$setPristine();">
        <input type="text" ng-model="company.Name"/>
        <input type="submit" value="Сохранить" ng-show="compForm.$dirty"/>
        <input type="button" value="X" ng-click="delete(company);"/>
    </form>
</div>
</body>
</html>

В результате готов фронтэнд к web API по адресу http://<адрес сервера>/csp/user/rest.csp.

Итого

В рамках данной статьи мы научились строить и настраивать RESTful web API на сервере Caché. Построение клиентской части также возможно на основе сервера Caché.

Что дальше?

Если кому-то интересно могу рассказать про обеспечение безопасности, разделение прав, и другие полезности для разработки RESTful web API на базе Caché.

Полезные ссылки

Скачать RESTful web API, построенное в этом туториале
Глоссарий по технологиям InterSystems — так же RESTful web API
JSON экспорт — класс класс2(SQL)
%request класс
%responseкласс
XML экспорткласс

Построение RESTful web API в Caché: 2 комментария

    • Действительно, на том хосте сейчас работает другое приложение, http://37.139.6.156/glossary/terms
      Я убрал ссылки на пример из статьи.
      Вы можете установить и запустить работающий пример на своем хосте.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *