Давайте поговорим о SQL и серверных программных приложениях.

  1. Получить HTTP-запрос; проанализировать JSON
  2. Сделайте один или два SQL-запроса
  3. Выполните некоторую бизнес-логику (‹- возможно, интересная часть здесь)
  4. Ответить на HTTP-запрос с помощью JSON

Также, вероятно, работает клиент Kafka или SQS, но он также взаимодействует с базой данных SQL.

Это та счастливая маленькая скучная петля, в которой мы находим хорошую внутреннюю службу без сохранения состояния. Между прочим, хорошо, что она скучна. Если кто-то хочет рассказать вам об архитектуре серверной части своего веб-приложения, и он достает маркер Expo, это красный флаг в моей книге.

Но в любом случае, давайте сосредоточимся на средней части, на этом SQL-запросе.

Требования

Наступил 2020 год, мы же не собираемся писать наши SQL-запросы вручную, не так ли? Мы собираемся использовать большую жирную мощную библиотеку с макросами и магией, чтобы сгенерировать их для нас! Мы будем просить об этом довольно много:

  1. Генерация запросов: необходимо преобразовать наш код Scala в запросы SQL.
  2. Ремонтопригодность: мы хотим сохранить схему в одном месте кода. Тест сниффинга: если имя столбца изменяется, это делается однострочно в коде.
  3. Возможности: мы хотели бы писать сложные запросы и в полной мере использовать возможности базовой базы данных.
  4. Сериализация / сопоставление: мы хотели бы, чтобы решение легко переводило классы case и кортежи в вызовы базы данных и обратно.

Почему не Слик?

Слик работает. Я много лет использовал Slick в серьезных производственных системах. Я хочу сразу отдать дань уважения Slick, так как это был один из самых важных проектов в экосистеме Scala на протяжении почти десяти лет. Проблемы, которые он стремится решить, - задача не из легких, и, в конце концов, она работает.

Но у Слика есть ряд проблем. Первый - это проблема трения API. Подготовка моего первого Table для компиляции в новом проекте часто занимает у меня почти час, и даже в лучшем случае требует большого количества шаблонов. Когда ошибки компиляции загадочны, погружение в исходный код не улучшает ситуацию. Фактически, вероятно, есть только один человек на Земле, который понимает компилятор Slick, о чем свидетельствует небольшая активность в репозитории Slick, что приводит нас к другой проблеме.

Стильный, поскольку проект по сути мертв. В качестве артефакта он довольно полезен, но, как говорится, вы получаете то, что видите. Взгляните на репозиторий, и вы увидите, что никто не работает над Slick. Немного страшно, когда такой важный компонент приложения является загадочным, полон проблем и плохо обслуживается. Для подробного сравнения Slick и Quill см. Этот SlideShare: https://www.slideshare.net/deusaquilus/quill-vs-slick-smackdown

Перо

Введите Quill, более современное решение. Quill рекламирует:

  • Отображение без шаблонов: схема базы данных отображается с использованием простых классов case.
  • Цитированный DSL: запросы определяются внутри блока кавычек. Quill анализирует каждый процитированный блок кода (цитату) во время компиляции и переводит их во внутреннее абстрактное синтаксическое дерево (AST)
  • Генерация запроса во время компиляции: вызов ctx.run считывает AST цитаты и переводит его на целевой язык во время компиляции, выдавая строку запроса как сообщение компиляции. Поскольку строка запроса известна во время компиляции, накладные расходы времени выполнения очень низкие и аналогичны использованию напрямую драйвера базы данных.
  • Проверка запроса во время компиляции: если настроено, запрос проверяется по базе данных во время компиляции, и компиляция завершается ошибкой, если он недействителен. Проверка запроса не изменяет состояние базы данных.

Нарушение…

Boilerplate бесплатно

Это действительно здорово. В большинстве случаев классы отображаются автоматически. Если они содержат какие-либо пользовательские типы, требуется пара кодировщик / декодер.

DSL запроса "в стиле коллекции"

Если вы использовали Slick, написание запросов Quill очень похоже. Таблицы работают так же, как коллекции, с функциями map и filter, а объединения могут выполняться с использованием идиоматических выражений for-complation. Но это не учебник.

Генерация запросов во время компиляции

Это замечательно по двум причинам. Первое - это прозрачность; ведение журнала запросов во время компиляции по умолчанию включено, что, я уверен, не случайно. Можно легко увидеть генерируемый SQL-запрос, и, безусловно, следует выделить время, чтобы прочитать его. То, что он компилируется, не означает, что это именно тот запрос, который вы хотели. Второй - производительность. Все, что Quill должен сделать во время выполнения, - это интерполировать некоторые значения в заранее созданную строку и отправить их по сети.

Caveat emptor: по мере роста количества запросов время компиляции будет значительно увеличиваться. Я считаю, что это справедливая цена. Чтобы ускорить процесс, можно:

  • Поменяйте местами DynamicTableQuery экземпляра, выполняя большую часть разработки, и замените их обратно для реальной сборки и тестирования. Это подталкивает генерацию запроса к среде выполнения.
  • Переместите запросы в отдельный блок компиляции, чтобы они реже перекомпилировались. Это помогает, если вы работаете над другими частями системы.

Где он ломается

Quill может легко обрабатывать оперативные запросы, которые используются в нашем приложении; Я имею в виду вставки, простые извлечения, обновления, удаления и одиночные соединения по принципу «выберите откуда». Вы должны иметь возможность реализовать 90% запросов вашего приложения с помощью Quill.

Однако возникают проблемы со сложными запросами. Обычно они занимаются аналитикой, статистикой или создают своего рода «поток» агрегированной информации. В качестве общего теста на запах я бы с осторожностью отнесся к любому запросу, который у вас возникнет с трудом при переводе на Quill. Сделайте шаг назад и спросите себя: может быть, вы слишком много делаете с самим запросом. Возможно, его следует разбить на несколько запросов с некоторым преобразованием и агрегированием на стороне сервера. Может быть, хорошим решением было бы преждевременно преобразовать данные в специальную таблицу.

Если вы задали эти вопросы, но все равно получили ответ, что вам нужен более сложный SQL-запрос, хорошо. Пришло время признать фундаментальную истину: лучший интерфейс для взаимодействия с базой данных SQL - это сам SQL. Период. Я потратил часы и часы, пытаясь сопоставить некоторые из моих «важных вопросов» с Quill, но безрезультатно, и я не хочу думать о том, сколько времени я потратил на борьбу со Сликом за эти годы. Это может быть очень неприятно! У меня был точный запрос, который мне был нужен в моем SQL-клиенте в другом окне, протестирован и готов к работе, но я не мог перевести его на Quill (или Slick) land. Ага!

Конечно, можно было бы зарегистрировать ошибку или задать вопрос о Gitter, но я знал, что попал в сферу крайних случаев, и на самом деле не винил Куилла в том, что он не смог сгенерировать мой запрос. К настоящему времени я знал, что мне нужен побег в простой SQL, а не исправление ошибки в Quill. Это был вопрос принципа и наличия повторяемого «выхода», если это когда-нибудь повторится, особенно с учетом того, что я планировал интенсивно использовать специфические для PostGres функции.

(Я буду ссылаться на PostGres в примерах, поскольку это моя предпочтительная реляционная база данных, но эта конфигурация работает с любой базой данных, совместимой с JDBC, и позволяет вам максимально использовать вашу конкретную базу данных)

Входит Дуби.

Дуби просто выполняет ваши SQL-запросы. Вот что он делает. Ниже мы видим простой запрос, сопоставленный с необязательным классом case, без шаблонов.

Это ОБЫЧНЫЙ SQL, который я вижу в этом примере? Какая пародия! Но в то же время какая свобода! Я могу выполнить любой запрос, который могу написать!

Но как насчет всей небезопасности? Список вещей, которые могут пойти не так во время выполнения с этим запросом, не короток: синтаксическая ошибка SQL, столбец с ошибкой, переименованный столбец, несоответствующий тип результата, несоответствующий тип ввода и список продолжается ...

Дуби Чек

Автор Doobie слышит наши крики. Запросы со строковой типизацией ужасают, поэтому Doobie предоставляет невероятно мощную программу проверки запросов, которая обращается к реальной базе данных для проверки запроса, начиная с общего синтаксиса и компиляции и заканчивая мелкими вещами, такими как расширение чисел и приведение типов. Да, для этого требуется тестирование с реальной базой данных. Но выполнение всех ваших запросов Doobie через средство проверки перед отправкой кода гарантирует, что нас не беспокоит ни одна из вышеперечисленных проблем. Опять же, справедливый обмен на всю имеющуюся у нас мощность.

Квилл или Дуби? Почему не оба

А теперь мы подошли к главному. У нас есть рабочие запросы, 95% процентов запросов в нашем приложении, insertUsers и findByEmails, написанные на Quill, с нулевым шаблоном и трением, а затем у нас есть простые SQL-запросы, когда нам действительно нужна полная мощность базы данных. мы платим за.

Когда ваш коллега спрашивает, почему вы не использовали функции PostGres JSON для обработки этого столбца JSON, теперь ваш ответ не должен быть «Мне не удалось заставить его работать с моей библиотекой Scala». Это было бы печально. Вы можете использовать все функции, которые предоставляет ваша база данных! При условии, что вы DoobieCheck ваши статические запросы во время CI.

Как нам собрать их под одной крышей? Ответ здесь, в Книге Дуби

Quill использует «контексты», чтобы превратить запрос AST (абстрактное синтаксическое дерево) в результат. Использование разных контекстов дает разные типы вывода. Например, есть MirrorContext, который выводит запрос в виде строки, которая используется для быстрой разработки и тестирования, база данных не требуется. Другие контексты фактически запускают запросы и возвращают результаты из базы данных.

Интеграция doobie-quill обеспечивает DoobieContext для Quill. Когда мы запускаем наши запросы Quill, мы получаем Doobie ConnectionIO, который является исполняемым запросом. Именно с помощью этого DoobieContext мы можем транслировать AST запроса Quill в страну Doobie, где уже находятся наши статические запросы. Статические запросы и запросы quill можно запускать даже в одной транзакции!

Давай сделаем это!

Надеюсь, я убедил вас в полезности и мощности такого гибридного решения. ORM и генераторы запросов - отличные инструменты, но когда они терпят неудачу или мы просто предпочитаем писать простой SQL, обязательно иметь аварийный люк на простом SQL, который был бы максимально безопасным и удобным в обслуживании. Я считаю, что комбинация Quill + Doobie проверяет все условия, и настоятельно рекомендую этот подход для систем Scala.

Да, для тех, кто считает, в нашей Exodia пока всего 2 штуки, но давайте назовем их 4, если посчитать Scala и нашу любимую СУБД. Последний кусок - ZIO, который поможет нам склеить все вместе в простом и функциональном стиле.

Далее мы посмотрим, как именно этого добиться с помощью дополнительных фрагментов кода и примера проекта.