Сегодня я собираюсь исследовать библиотеку под названием Zapatos, которая позиционирует себя как Постгрес с нулевой абстракцией для машинописного текста. Большинство людей использовали ORM и находили их немного неуклюжими или изо всех сил пытались работать с соглашениями об использовании. Zapatos предоставляет инструмент командной строки для автоматического создания схемы вашего машинописного текста, который обеспечивает поддержку нескольких действительно отличных функций:
- Произвольный SQL: он предлагает простые строительные блоки, которые позволяют вам писать произвольные SQL-запросы с использованием шаблонов с тегами. Вы можете применить соответствующие типы к входным и выходным данным и получить статическую типизацию при написании основных операторов SQL.
- Ежедневный CRUD: Zapatos предоставляет функции быстрого доступа, которые легко генерируют запросы CRUD (создание, чтение, обновление, удаление), обеспечивая полностью типизированные операции без каких-либо осложнений или неожиданностей. Язык основных операций CRUD прост и понятен, а благодаря некоторой цепочке позволяет вам работать с автозаполнением при работе с вашей схемой.
- СОЕДИНЕНИЯ как вложенные JSON: используя вложенные вызовы ярлыков, Zapatos генерирует запросы LATERAL JOIN, которые позволяют создавать сложные вложенные структуры JSON. Эти структуры по-прежнему полностью типизированы, что обеспечивает согласованность и целостность данных.
- Транзакции: Zapatos включает вспомогательные функции транзакций, которые помогают управлять транзакциями и повторять их, упрощая обработку многоэтапных операций с базой данных.
Эти функции в совокупности позволяют разработчикам эффективно работать со своими базами данных Postgres, предоставляя удобный и типизированный интерфейс для создания схемы, запросов SQL, операций CRUD, извлечения вложенных данных и управления транзакциями. Я никогда не боюсь добавлять оружие в свой арсенал, так что давайте приступим к делу.
Для этого нам нужна база данных и схема Postgres, поэтому мы начнем с создания нового проекта и быстрой настройки необходимых зависимостей с помощью пары простых команд: git init
и touch docker-compose.yml
. Чтобы немного облегчить себе жизнь, мы также возьмем самые последние (на момент написания) настройки компоновки из Документации Postgres.
Чтобы настроить начальную схему, мы внесем некоторые незначительные изменения в этот начальный сценарий, как показано здесь:
version: "3.1" services: db: image: postgres restart: always environment: POSTGRES_USER: admin POSTGRES_PASSWORD: admin1pass volumes: - ./infra/data:/var/lib/postgresql/data - ./infra/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d adminer: image: adminer restart: always ports: - 8080:8080
Затем создайте каталог для хранения наших сценариев запуска с mkdir -p infra/docker-entrypoint-initdb.d
, заполните сценарий инициализации с touch infra/docker-entrypoint-initdb.d/init-db.sql
и заполните его следующим кодом:
Запуск docker-compose up
должен быстро подтвердить, что наши первоначальные сценарии работают нормально, и создать новую базу данных, но давайте еще раз проверим, все ли в порядке. Поскольку наш первоначальный docker-compose включает установку администратора, мы можем проверить его, щелкнув URL-адрес, введя свои учетные данные администратора и переключившись на схему электронной коммерции, чтобы увидеть таблицы:
Пока довольно хорошо и просто, теперь мы можем начать самое интересное!
Теперь, когда у нас есть схема для интерпретации, давайте настроим Zapatos!
Следующие несколько шагов будут очень простыми, все, что нам нужно сделать, это изменить файл docker-compose.yml, чтобы открыть наши локальные порты, установить и настроить Zapatos, а затем запустить инструмент командной строки для создания схемы. Во-первых, давайте исправим docker-compose
, чтобы открыть службу Postgres, работающую в докере. Это так же просто, как добавить две строки в объявление службы БД:
services: db: (...) + ports: + - 5432:5432 adminer: (...)
Если вы повторно запустите команды docker, это должно открыть службу Postgres на порту 5432 на вашем локальном компьютере.
Затем давайте настроим дополнительный проект для API с mkdir api
, а затем cd
в нем. После этого мы можем запустить npx zapatos
, чтобы получить запрос на установку Zapatos на локальный компьютер. Как только эта установка будет завершена, мы запустим touch zapatosconfig.json
и настроим файл следующим образом:
После этого мы сможем повторно запустить npx zapatos
, чтобы сгенерировать нашу схему. Это создаст папку с именем zapatos
в нашем проекте API с таблицами в schema.d.ts
! Загляните внутрь, если хотите лучше понять API, который становится доступным после управления вашим проектом с помощью Zapatos.
Стоит отметить, что вы также можете программно регенерировать схему с помощью этих команд, но для наших целей мы собираемся сделать ее простой и запустить ее как есть.
Теперь мы можем начать инициализацию нового проекта с помощью npm init -y
, чтобы пропустить некоторые настройки. Нам также понадобится API для доступа к информации в таблицах, поэтому мы можем начать с получения нескольких базовых маршрутов и некоторых конечных точек для тестирования.
Чтобы настроить проект с единым стилем доступа к API, давайте начнем с моделирования отношений, которые будут иметь разные конечные точки в файлах и папках: mkdir -p api api/pool api/user api/user/address api/product api/order api/order/item
, затем просто добавьте touch api/index.ts api/pool api/user/index.ts api/user/address/index.ts api/order/index.ts api/order/item/index.ts api/product/index.ts
.
После настройки всех этих файлов мы перейдем к основному файлу api/index.ts и настроим наш первоначальный API:
Затем настройте пользовательский маршрутизатор в файле ./api/user/index.ts
:
Как только это будет сделано, мы можем установить некоторые зависимости с помощью npm i dotenv express pg zapatos
, npm i --save-dev @types/express @types/node tsx typescript
, настроить typescript с помощью npx tsc --init
, а затем изменить сценарии package.json, чтобы включить способ запуска проекта:
"scripts": { "start": "tsx watch index.ts", (...)
Сделав это, мы быстро создадим dockerfile, способный запускать API на node:alpine
(я буду использовать версию, установленную на моем локальном компьютере):
И теперь мы можем просто запустить приложение как есть с docker-compose up
. Это должно запустить сервер, и в терминале вы увидите console.logs
для подтверждения. Мы можем дополнительно свернуть API, чтобы увидеть некоторый базовый успех.
Теперь, когда у нас есть API, способный обрабатывать запросы, нам нужно смоделировать создание пользователей. Для этого нам понадобится пул соединений, который можно легко настроить с помощью pg
. Для этого я сделаю эту настройку:
И добавьте в .env строку подключения, аналогичную той, что настроена для основного конфигурационного файла Zapatos, за исключением того, что мы будем использовать имя контейнера для хоста:
PORT=3000 DATABASE_URL=postgresql://admin:admin1pass@db:5432/ecommerce
Теперь мы можем реализовать некоторые пользовательские конечные точки, открыв ./api/user/index.ts
и добавив следующий код:
Давайте посмотрим на внесенные здесь изменения и разберем, что Zapatos позволил нам сделать. На L8 мы описываем GET верхнего уровня, который возвращает всех пользователей и некоторые метаданные о них с помощью команды db.sql
, которая позволяет нам использовать строковые литералы шаблона для получения того, что по сути является автозаполнением sql:
Честно говоря, именно здесь я действительно начал понимать. Вы не будете использовать db.sql
тонну, если вам действительно не нужно контролировать сгенерированный SQL для повышения производительности, но это абсолютно потрясающе. Вы почти не можете ошибиться при вводе имен своих свойств, и все, что выводится, по умолчанию полностью типизировано:
Довольно круто, но, как обсуждалось выше, вам не захочется писать SQL вручную для многих задач. Давайте посмотрим на следующие пару строк, чтобы лучше понять вспомогательные функции, связанные с операциями CRUD. Я собираюсь немного попрыгать, чтобы разные конечные точки работали правильно, когда мы выполняем какой-то пример кода:
Создать/обновить
Начиная с L15, мы видим, что команда PUT настроена с телом, используя необязательный идентификатор и адрес электронной почты, которые позволят нам создать или обновить учетную запись пользователя, используя соответствующие команды db.update
или db.insert
.
Чтобы проверить это, я запущу две команды curl — одну для создания учетной записи пользователя и одну для ее обновления, и обратите внимание, что свойство update_by изменяется при каждом выполнении. Вот результаты (обратите внимание, что я использую make
только потому, что печатать это раздражает):
Читать
Начиная с L52, у нас есть маршрут, параметризованный с помощью :id
, который позволяет нам получить идентификатор пользователя из URL-адреса. Запуск curl https://localhost:3000/1 вернет первого зарегистрированного пользователя, а окончательное значение будет инкрементным для целевого пользователя.
Вам не обязательно делать это с реальным пользователем в реальной системе, если только это не какая-то социальная платформа, но это хороший пример того, как использовать метод Zapatos db.select
для возврата конкретных записей.
Это все хорошо, но мы хотим начать заполнять некоторые заказы для этих клиентов, но для этого нам сначала понадобятся некоторые продукты. Как только это будет сделано, мы сможем изучить создание Order, а затем добавить в него OrderItems. Давайте начнем с настройки маршрутизатора продукта, используя те же знания, которые мы получили от пользователей.
Сначала мы отредактируем конечные точки API продуктов, чтобы мы могли создавать и читать все продукты:
Для этого примера я создал API, позволяющий одновременно создавать или обновлять несколько продуктов, настроив ожидаемый тип в API на массив и написав цикл for для использования значений. Zapatos по-прежнему отлично предоставляет нам статическую типизацию на протяжении всего процесса разработки, и строго нет необходимости запускать код, если вы не хотите перепроверить.
Это довольно мощно!
Теперь, когда у нас есть несколько продуктов для работы, следующим шагом будет настройка потока заказов.
Заказ в этом простом приложении будет довольно простым; чтобы добавить элементы в заказ, он сначала должен существовать. Обычно это делается путем извлечения информации из токена пользователя из аутентифицированного потока, но это немного выходит за рамки наших целей использования Zapatos. Наша реализация позволит пользователям инициализировать заказ, отправляя свой идентификатор пользователя в конечную точку API.
В результате получается довольно простой код:
Конечная точка порядка позволяет нам изучить новую концепцию Zapatos: на L17 есть соединение, обозначенное опцией «боковой» при выборе. Что действительно интересно в этом, так это то, что когда мы описываем результирующий объект с «предметами» в качестве свойства, мы получаем автозаполнение вызова db.parent
:
Качественный товар! На L25 мы видим конечные точки, способные добавлять ордера, поэтому давайте добавим новый ордер:
Что ж, наш счастливый путь пройден! Каждый хороший программист должен думать о случаях сбоя, поэтому давайте посмотрим, что произойдет, если мы используем несуществующего пользователя:
Возврат этой попытки заключается в том, что запрос не может создать заказ, а это именно то, что мы хотим с проблемой ограничения. Это упрощает задачу, так как Zapatos не сможет выполнить обещание, а результат легко интерпретируется для обработки ошибок.
Теперь, когда мы отсортировали сами заказы, давайте реализуем элементы заказа. Что-то, что придет довольно быстро, заключается в том, что добавление элементов заказа немного сложнее, чем просто вставка/обновление записей; наша схема включает свойство «количество», что означает, что нам нужно будет скорректировать это значение, если кто-то добавит два товара в свой заказ. У нас также есть понятие «цена за единицу», которое включается в заказ на случай, если мы хотим получить скидку на несколько одинаковых товаров.
Наличие чрезмерно сложной логики на уровне API, как правило, является анти-шаблоном, которого мы хотим избежать, и хорошей целью является стандартизация способа взаимодействия с базовым поставщиком данных. Для этого напишем уровень репозитория:
Реализация репозитория объединяет код для создания, обновления и удаления в зависимости от того, находится ли продукт уже в заказе и его количество больше 0. Это охватывает все необходимые бизнес-правила. Кроме того, мы перенесли ранее реализованную бизнес-логику, связанную с созданием и извлечением заказов, в репозиторий, что позволяет нам извлечь ее из orderRouter. Централизовав код на уровне репозитория, мы можем вернуться к orderRouter и значительно упростить код:
Кроме того, мы использовали клиент транзакций Zapatos, чтобы убедиться, что при создании или обновлении элементов заказа все операции должны выполняться вместе, иначе все выполнение завершается с ошибкой и откатывается. Теперь мы можем уверенно зайти в itemRouter и реализовать элементы заказа:
А теперь к тестам:
И для проверки удаления:
Я надеюсь, что вы нашли это погружение в использование Zapatos таким же интересным, как и я. Преимущества этого многочисленны; инженеры могут использовать простоту использования стандартных блоков для написания произвольных SQL-запросов со статической типизацией, обеспечивающей целостность данных. CRUD-запросы очень просты, они обеспечивают полностью типизированные операции с беглым языком и поддержкой автозаполнения. Использование вложенных вызовов ярлыков позволяет создавать сложные вложенные структуры JSON с помощью запросов LATERAL JOIN. Вспомогательные функции транзакций значительно упрощают обработку многоэтапных операций с базой данных.
Дизайн библиотеки элегантен и прост, и от начала до конца весь этот проект и статья заняли у меня всего 5 часов, что очень легко, когда вы изучаете совершенно другой подход к поиску данных. Я большой сторонник Typescript и статической проверки типов и был приятно удивлен в процессе изучения этого. Zapatos — привлекательная альтернатива неуклюжим и глючным ORM, и я бы порекомендовал попробовать, если вы работаете с Typescript, Node и Postgres.