Как создать простой REST API с Clojure

Вступление

Сегодня веб-сервисы могут варьироваться по объему от базового API с несколькими таблицами в БД до крупномасштабных систем, поддерживающих сотни миллионов пользователей, с массивными кодовыми базами и временем отклика, настроенным с точностью до миллисекунды. В зависимости от поставленной задачи современный разработчик имеет широкий выбор инструментов с десятками популярных языков и фреймворков, каждый со своим особым взглядом на то, как что-то делать для решения заданного набора проблем.

Среди этих языков - Clojure, все более популярный диалект Lisp, ориентированный на JVM, Microsoft CLR, движки JavaScript и другие платформы. Lisp существует уже невероятные 60 лет и был разработан в Массачусетском технологическом институте для реализации Lambda Calculus. Clojure основывается на Lisp, используя уже существующие среды выполнения (такие как JVM) и добавляя отличные функции, такие как потокобезопасные типы данных для параллелизма и взаимодействие Java VM.

В этой статье мы рассмотрим, как реализовать JSON REST API с Clojure и PostgreSQL для хранения коллекции друзей с именем, псевдонимом и занятие и получить список друзей обратно из БД.

Копия исходного кода этого проекта доступна здесь, на GitHub.

Обзор

Clojure - это язык функционального программирования (FP) с интересным и мощным синтаксисом, который может показаться незнакомым, если исходить из процедурного или объектно-ориентированного мышления. FP является декларативным, что означает, что код определяется с точки зрения того, что нужно выполнить, а не того, как это сделать.

Это отражение того, как FP эволюционировал непосредственно из области математики, что привело к очень чистому и мощному языку, который уменьшает побочные эффекты, препятствуя изменению состояния (изменчивость).

В этой статье основное внимание будет уделено практическому аспекту создания и запуска API, поэтому мы начнем с настройки среды и проверки нашего .env.development файла по умолчанию с переменными конфигурации проекта.

Конфигурация

Для запуска этого проекта требуется следующая настройка:

  • Clojure (с пивом: brew install clojure)
  • Leiningen (с пивом: brew install leinengen)
  • PostgreSQL

Чтобы инициализировать базу данных, выполните команду $ ./db_create.sh, которая создаст базу данных clojure_pg_example и friends таблицу. Информация о подключении к базе данных, а также порт, на котором будет работать API, содержатся в файле .env.development:

Этот файл будет использоваться пакетом dotenv для инициализации переменных среды для локальной разработки, что позволит развернуть этот проект в соответствии с Фактором III (Конфигурация) из Принципов 12-факторного приложения.

Определение проекта

Конфигурация проекта содержится в project.clj:

В дополнение к стандартному имени проекта, описанию и другой информации в этом файле определены зависимости проекта. Добавлены пакеты для настройки HTTP-сервера, обработки JSON, загрузки переменных среды, подключения к базе данных и других рутинных задач.

В :profiles был добавлен дополнительный профиль:dev {:main clojure-example.core/-dev-main}, который заставит наше приложение запускать функцию -dev-main вместо -main при разработке. Это может быть полезно при настройке таких функций, как автоматическая горячая перезагрузка, которую мы вскоре рассмотрим.

Точка входа в приложение

Выполнение программы начинается в src / clojure_example / core.clj:

В верхней части файла определяется пространство имен с ns и импортируются библиотеки, которые будут использоваться путем передачи их в :require, включая те, которые требуются для настройки HTTP-сервера и функций обработчика для конечных точек маршрута.

Макрос aptly-nameddefroutes определяет маршруты API:

  • GET / (повторить запрос)
  • GET /friends (получить список друзей из БД)
  • POST /friends (сохранить запись друга в БД)

Две функции -main и -dev-main являются доступными функциями точки входа для приложения, в зависимости от того, запущено ли приложение в рабочем режиме или в режиме разработки. Внутри -dev-main функция wrap-reload используется для обертывания функций API в обработчике часов, который выполняет горячую перезагрузку любых изменений, внесенных в исходный файл на диске, который используется API. Это чрезвычайно полезно и экономит много времени при создании и тестировании приложения. В остальном функции точки входа такие же, app-routes обертывание промежуточным программным обеспечением для значений по умолчанию с использованием значений по умолчанию для создания API, а затем, в свою очередь, обертывание этих функций в оболочках JSON для синтаксического анализа тела запроса и форматирования вывода ответа. Далее мы проверим обработчики маршрутов.

Обработчики маршрутов

Функции обработчика маршрута находятся в src / clojure_example / lib / routes.clj:

В этом файле три функции, которые соответствуют трем app-routes, определенным в предыдущем файле:

  • echo-route (дословно повторить объект запроса)
  • get-friends-route (просто вызывает api/get-friends)
  • add-friend-route (вызывает api/add-friend с параметрами запроса JSON)

Отсюда легко увидеть, как чистый синтаксис Clojure может сохранить ваш код аккуратным и легким для чтения (как только вы (осмотритесь (все круглые скобки))).

Внутренний API

Сама логика API содержится в src / clojure_example / lib / api.clj:

Первая функция get-friends не принимает параметров и просто вызывает db/select с именем таблицы :friends и массивом ключевых слов для идентификатора, имени, псевдонима и занятия в качестве полей, возвращаемых из запроса.

Вторая функция add-friend деструктурирует его аргумент на компоненты, которые затем повторно собираются в переменную record, которая передается вместе с именем таблицы :fields в db/insert функцию. Результирующий объект с другом id и другими данными возвращается для отправки в качестве ответа обработчиком маршрута.

Уровень базы данных

Последний файл в нашем примере проекта - src / clojure_example / lib / db.clj:

Этот файл импортирует dotenv для доступа к переменным среды в дополнение к JDBC, который мы будем использовать для связи с экземпляром PostgreSQL. Соединение определяется с помощью определения -db с использованием переменных среды для информации о соединении.

Служебная функция concat-fields использует clojure.string/join для объединения элементов в массиве вместе с , разделителем для упаковки параметров в строку, разделенную запятыми, которая будет использоваться в запросе SQL.

Функция insert вызывает jdbc/insert, передавая целевой :table и record для вставки. Объект результата будет заключен в массив, поэтому функция first используется для извлечения результата из массива с одним элементом, который затем возвращается обработчику маршрута для отправки обратно в виде ответа JSON.

Функция select вызывает jdbc/query с именем :table и SQL-запрос, состоящий из оператора выбора, запрашивающего fields (которые отформатированы для SQL с использованием вышеупомянутой функции concat-fields) из указанного :table и возврата полученного списка записей обратно в маршрут. обработчик для отправки клиенту в виде ответа JSON.

Тест-драйв

Чтобы протестировать API, запустите сервер с lien run server, который получит необходимые зависимости и напечатает сообщение о том, что сервер работает на порту, указанном в файле среды. Служебный сценарий ./post.sh для удобства предоставлен в виде простой оболочки curl:

$ ./post.sh '{"name":"Sam","nickname":"X","occupation":"ninja"}'

Это вернет результирующий объект с предоставленными данными, а также id и метку времени создания, подтверждающую успешное завершение нашей операции POST. Чтобы получить список ранее добавленных друзей:

$ curl localhost:3000/friends

Это вернет массив объектов в формате JSON с именем, псевдонимом и описанием, тремя полями, запрошенными в запросе к базе данных, описанном ранее.

Заключение

У Clojure довольно крутая кривая обучения из-за его неумолимых корней в математике, но при правильном использовании получаемые программы надежны, просты в обслуживании, невероятно быстры и отзывчивы. Возможность загружать и запускать новый код на лету без перезапуска JVM - это лишь одна из мощных функций этого языка, которая также требует более высокой заработной платы, чем любой другой язык с 2019 года, согласно в этот отчет о переполнении стека.

В этой статье показано, как простой готовый к работе с облаком REST API в комплекте с базой данных PostgreSQL можно быстро собрать в Clojure с помощью всего нескольких строк чистого, надежного кода.

Спасибо за чтение и удачи в следующем проекте!

Кеннет Рейли (8_bit_hacker) технический директор LevelUP