(define-route api-method ("/path/to/method/:(param1)"И так по всему модулю, в обработчике делается один несколько SQL-запросов, результаты которых представленны в виде списков (которые могу быть древовидными) свойств (plist), которые обычно тривиальным образом обрабатываются и полученный большой plist отдаётся клиенту в формате JSON. Ещё я заметил, что те же самые запросы нужно так же выполнять и в другом месте, где происходит формирование html и отдача его клиенту. Фактически, это было оформлено так, что были функции, возвращающие чистые данные в формате s-выражений, которые вызывались для генерации json в одном месте, а для генерации html в другом. При этом, маршруты, ответственные за генерацию JSON вырождались в "неприлично тупое":
:content-type "application/json")
(json:encode-json
(list :field1 (sql-query "select ...")
:field2 (sql-query "select ..."))))
(define-route api-method-route ("/path/to/method"Здесь можно было бы написать простой макрос, который бы определял функцию, возвращающую данные, и сразу же определял маршрут, в котором бы результат вызова данной функции конвертировался бы в формат JSON. Но я решил, что можно сделать лучше.
:content-type "application/json")
(json:encode-json (api-method ...)))
В RESTAS при определении маршрута с помощью define-route создаётся функция, имя которой совпадает с именем маршрута и которая может принимать keyword-параметры, соответствующие параметрам, указанным в шаблоне url. Эту функцию можно вызывать непосредственно из REPL или из других функций, но такой возможностью я пользовался не часто, ибо результатом её работы ранее обычно был HTML (ну или JSON в данном случае) и читать его в REPL как-то не очень приятно. Так вот, мне пришла в голову и я реализовал следующую идею:
(define-route api-method ("/path/to/method/:(param1)/:(param2)"По сравнению с первым листингом здесь видно некоторое разделение: в теле маршрута только логика в виде генерации s-выражения с данными, а в свойствах маршрута указано, что перед отдачей этих данных клиенту их необходимо конвертировать в формат JSON с помощью функции #'json:encode-json. Таким образом, фактически, определяется метод, который доступен как внутри приложения, так и для JavaScript-кода.
:content-type "application/json"
:render-method #'json:encode-json)
(list :field1 (sql-query "select ...")
:field2 (sql-query "select ..."))))
Но пример с JSON это только частный случай, вообще такая техника может иметь очень разные применения, например, легко себе представить модуль, все маршруты которого возвращают некие объекты из предметной области, я для генерации разметки во всех случаях используется одна и та же generic-функция. Что бы упростить программирования подобного случая (как и моего пример с JSON) и не писать во всех маршрутах одно и то же, я также ввёл переменную модуля {module-name}:*default-render-method*, которая содержит метод отображения по-умолчанию, и равная по-умолчанию же просто #'identity. Для настройки значения этой переменной при определении модуля можно использовать ключевой параметр :default-render-method, например:
(restas:define-module mymoduleКстати, в некоторых случаях это даёт возможность настраивать способ отображения при определении submodule с помощью restas:define-submodule и стандартного механизма настройки значений динамических переменных.
(:use #:cl)
(:defaul-render-method #'json:encode-json))
Комментариев нет:
Отправить комментарий