В 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. Ниже таблица методов и действий с одним элементом и коллекциями:
Метод | Запросы к коллекции элементов example.com/resources |
Запросы к одному элементу example.com/resources/itemID |
GET | Получить список URI элементов коллекции, возможно доп. информацию | Получить всю информацию об элементе |
PUT | Заменить существующую коллекцию на новую | Заменить существующий элемент на новый |
POST | Создать новый элемент коллекции | Как правило не используется |
DELETE | Удалить всю коллекцию | Удалить элемент коллекции |
А как в Caché?
В СУБД Caché поддержка REST появляется начиная с версии 2014.1 — эта версия пока доступна в виде филд-тест версии для партнеров и вузов InterSystems Campus. Чтобы создать REST приложение, нужно в настройках веб-приложения Caché определить класс-брокер, в котором указываются возможные расширения базового URL и соответствующие действия приложения при запросе этих расширений.
Класс-брокер создается как наследник класса %CSP.REST. Далее в нем прописывается карта путей URL приложения (например example.com/resources/ID — GET ), причем каждому URL в соответствие ставится метод класса Caché, который будет выполнять всю работу.
Карта путей — это перечисление всех возможных URL для обращения к приложению для получения данных или для изменения данных на сервере.
За дело
- В Портале Управления Системой (Портал):
- Портал → Администрирование системы → Безопасность → Приложения → Веб приложения
- Нажмите кнопку «Создать новое веб приложение»
- На странице редактирования веб приложения нужно заполнить следующие поля (остальные поля остаются без изменений):
- Имя: /rest (слеш обязателен)
- Область: USER
- Dispatch Class: REST.Broker (строка регистрозависима)
- Нажмите кнопку Сохранить
- Перейдите в область USER
- Создайте новый класс REST.Broker, нажав Ctrl+N или из Меню: Файл→Новый.
- Выберите вкладку Общие а там Класс Caché
- В мастере создания класса:
- Введите имя пакета: REST
- Укажите имя класса: Broker
- Нажмите кнопку Далее
- В окне Тип класса:
- Нажмите на кнопку Расширения
- Имя класса-предка: %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
- Нажмите кнопку Далее.
- В окне Тип класса:
- Нажмите на кнопку Persistent
- Нажмите кнопку Далее
- Выберите опцию XML Enabled
- Выберите опцию Data Population (Генерация тестовых данных)
- Нажмите кнопку Завершить
{
Property Name As %String(POPSPEC = "Company()");
}
POPSPEC = "Company()" — сообщаем генератору тестовых данных, чего мы от него хотим, а то он бы нам имена людей выдавал тут.
Заполним класс тестовыми данными с помощью команды в терминале: w ##class(Data.Company).Populate(10)
На стороне сервера
Для демонстрации CRUD операции Return (HTTP — GET) мы создадим новый класс REST.JSON для задач генерации JSON ответов на запросы к RESTful сервису.
- В мастере создания класса:
- Имя пакета: REST
- Имя класса: JSON
- Нажмите кнопку Далее.
- В окне Тип класса:
- Нажмите на кнопку Расширения
- Имя класса-предка: %Base
- Нажмите кнопку Завершить
{
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 мы научились, однако брокер об этом не знает.
Готово! Теперь по адресу http://<адрес сервера>/rest/json/companies вас ждёт список компаний в JSON, пример.
Серверу — клиента!
А на стороне клиента мы будем превращать JSON, во что-нибудь, приятное глазу. Для этого используем MVC JS-фреймворк AngularJS.
- В Caché Studio, создайте новую CSP страницу, нажав Ctrl+N или из Меню: Файл→Новый
- Выберите вкладку CSP файл
- Выберите Caché Server Page и нажмите кнопку OK
- Сохраните созданную страницу в папке csp/user под именем rest.csp
<html ng-app>
<head>
<title>REST Academy</title>
<script src="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: добавление, изменение и удаление компании.
{
s 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 {
s st=ex.AsStatus()
}
quit st
}
ClassMethod DeleteCompany(compid As %String) As %Status
{
set st=$$$OK
try {
$$$THROWONERROR(st,##class(Data.Company).%DeleteId(compid))
} catch ex {
s st=ex.AsStatus()
}
quit st
}
ClassMethod UpdateCompany(compid As %String) As %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/: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
<!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, построенное в этом туториале
Пример RESTful web API
Глоссарий по технологиям InterSystems — так же RESTful web API
JSON экспорт — класс — класс2(SQL)
%request — класс
%response — класс
XML экспорт — класс
Автор: eduard93