Несколько месяцев назад толстый написал о том, чтобы пойти к черту и вернуться, переписав Бамперы на React. Чтобы завершить нашу мысль, я собираюсь поговорить о бэкэнде Bumpers. Мы не сделали ничего столь драматичного, как переписывание, но мы учились делать вещи The Go Way, и это повлияло на наше мышление до такой степени, что кажется, что это стоит записать. Итак, начнем.

Как и Джейкоб, я также ненавижу фреймворки, я ненавижу не использовать фреймворк и ненавижу катить свой собственный. С другой стороны, я люблю писать SQL (что, по-видимому, не является нормальным явлением для любви и превью остальной части этого сообщения).

Предисловие

Бэкэнд бамперов - это HTTP-сервер Go. Он работает на Heroku и в основном является сервером API, но у него есть несколько маршрутов, обслуживающих наш приятный интерфейс. Bumpers API предоставляет клиентам Bumpers некоторые данные из нашей базы данных Postgres. Действительно шокирует API, но это мир, в котором мы живем сейчас.

Мы испробовали несколько разных подходов к взаимодействию с этой базой данных - мы решили использовать пару интерфейсов репозитория и скрыть за ними весь доступ к нашей БД.

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

Что действительно необычно в этих репозиториях в 2017 году, так это то, что у нас нет ORM или какой-либо другой библиотеки баз данных, генерирующей для нас SQL. Мы пишем весь наш SQL вручную.

Зачем ты вообще это делаешь

Когда я говорю ORM, я действительно имею в виду библиотеку, которая действительно пытается управлять вашим взаимодействием с базой данных. Наиболее полные ORM - это такие вещи, как ActiveRecord и Hibernate, но при таком отсутствии определения вы можете расширить метку до библиотек, таких как Sequel и Korma, даже если они не пытаются владеть всей вашей жизнью или даже иметь дело с « объекты". Я активно не говорю о базовых вещах, таких как драйверы или пулы соединений или поддерживающие переменные запроса - пожалуйста, не пишите свой собственный SQL-экранирование в 2017 году.

Люди склонны брать одну из этих библиотек по нескольким из следующих причин:

  • Это часть используемой структуры. Удачи.
  • Приложение должно быть независимым от базы данных. Это не настоящая проблема.
  • Вам нравится $COOL_PROGRAMMING_LANGUAGE больше, чем SQL. В этой ORM легче писать, смотреть и понимать код, чем смотреть и понимать SQL-запрос.
  • Библиотека - это полезная абстракция. Он позволяет составлять и повторно использовать запросы таким образом, как если бы вы сами не писали SQL. Это упрощает работу с данными на вашем языке, а не со строками результатов SQL. Это, вероятно, поможет вам развиваться немного быстрее.

Мы не используем фреймворк, который дает представление о базе данных, поэтому у нас остается два реальных критерия для оценки наших библиотек.

Я лично очень увлекаюсь SQL, а Джейкоб и Ян его не ненавидят (пока?), Поэтому скрытие SQL за Go не является само по себе причиной. Есть также свидетельства того, что приложения, которые пытаются отрицать существование скрытой базы данных, в конечном итоге имеют довольно грубые крайние случаи. Например, если вы действительно используете ActiveRecord, вам нужно знать ActiveRecord настолько хорошо, что вы знаете, какой SQL он генерирует, а затем решаете, можно ли отправить этот SQL. Я думаю, что это полностью опровергает тезис о том, что это легче понять.

Хорошо, это хорошая причина

Итак, мы подошли к решению, предоставляет ли библиотека базы данных «полезную абстракцию». Ценность хорошей абстракции ОГРОМНА, даже если вам все еще нужно знать, что происходит с вашей базой данных. Я думаю, что совершенно разумно понимать весь SQL, который генерирует запрос ActiveRecord, а также думать, что области видимости являются хорошим и полезным инструментом.

Что касается Bumpers, мы рассмотрели, что в настоящее время существует для Go. Современные библиотеки, претендующие на некое наследие в стиле ORM, похоже, предоставляют отображение структур и некоторую помощь с простыми запросами SELECT, INSERT и UPDATE и не более того. Некоторые из них предоставляют DSL, вдохновленные ActiveRecord, для создания строк запроса (их было много) поверх этого. Когда мы разговаривали с людьми, использующими ту или иную из этих библиотек в производственной среде, мы, как правило, получали ответ на вопрос «пожимает плечами» ascii и не более того. Облом.

Мы нашли и НЕМЕДЛЕННО начали использовать sqlx. sqlx выполняет структурное отображение, которое делают другие библиотеки, и предоставляет функцииGet и Select, которые обрабатывают шаблон выполнения db.QueryRow или db.Query сканирования результатов в структуру или фрагмент структур. С sqlx все наши методы запроса выглядят примерно так:

Вместе это примерно 90% того, что мы чувствовали, как будто мы получали от любой из других библиотек, и не требует (почти) накладных расходов на выяснение того, что происходит под покровом.

Итак, мы решили просто продолжать работу с операторами необработанного SQL.

Другой 10%

Итак, теперь у нас осталась проблема, заключающаяся в том, что необработанный SQL довольно многословен и его трудно использовать повторно. У нас нет точного ответа, но мы начали организовывать наши файлы Repo таким образом, чтобы это помогало.

Каждый из наших репозиториев находится в отдельном файле. У этих файлов есть интерфейс репо наверху, за которым следует набор констант, определяющих запросы SQL, которые нам понадобятся, а затем структура, реализующая интерфейс.

Файл, в котором находится CategoryRepo сверху, выглядит примерно так:

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

Они обычно выглядят примерно так:

Что, честно говоря, довольно здорово. Это дает нам одно место для поиска N + 1 запросов, пропущенных полей и всего того интересного, что появляется, когда мы ищем ошибки.

Хорошо, где мы?

Плохие части

Хотя эти функции выглядят великолепно, SQL вверху - немного более печальная история. Чем больше мы добавляем, тем шумнее становится файл. Мы составляли константы (и только константы, здесь SQL времени выполнения не меняется), чтобы уменьшить шум.

Эпизоды довольно прохладные, но у нас есть довольно много разных способов получения Users, и вещи в UserRepo могут стать довольно сумасшедшими, когда вы начнете добавлять массовые выборки.

Там много шума. Подготовка всех наших операторов в функциях новой структуры означает, что поиск запроса для оператора обычно включает в себя определение перехода к оператору, поиск его в функции нового репозитория, а затем выполнение определения перехода к константа, чтобы вернуться к нашим составным константам. Соглашения об именах для констант и операторов очень помогают, но это все еще немного коряво. Мы рассматривали возможность переноса запросов в отдельный файл whatever_queries.go, но еще не пробовали.

Неудивительно, что отношения между структурами / таблицами сложны. В настоящее время мы требуем, чтобы EpisodeRepo было единственным репо, которому разрешено возвращать Episode, а UserRepo - единственным репо, которому разрешено возвращать User. В итоге мы получаем реализации Repo, у которых есть другие поля Repos в качестве полей, и где репо, на которое ссылаются, знает о таблице соединения.

Уф, это корявая проза. Вот пример: Episode имеет несколько Authors, а UserRepo должен знать о нашей episode_authors таблице соединения:

Он работает достаточно хорошо, чтобы избежать каких-либо реальных забавных ситуаций с запросами N + 1. Это действительно означает, что каждый раз, когда мы хотим получить Episode, мы запрашиваем для этого каждого автора. На данный момент это нормально, и мы можем обойтись без этого при нашем текущем объеме данных, но это может быть не всегда.

Наконец, поскольку мы пишем собственные SQL-запросы для всех этих Get методов, мы пишем тесты для всех этих функций, которые будут бесплатно поставляться с ORM. Это определенно похоже на то, что это должно учитываться в наших строках шаблонов на репо.

Хорошие детали

Хотя в этом коде все еще есть шаблон, конечный результат все еще читаем. Для нас не было сумасшествием заглянуть и существенно изменить наши репозитории. Это верно, когда в наших репозиториях есть два десятка методов, что безумие. Самой болезненной частью было предварительное создание новых репо. Обмен нескольких минут предварительной скуки на удобочитаемость и удобство обслуживания кажется компромиссом для Go, и мы, как правило, рады оптимизировать, чтобы операторы были довольны, а не разработчики. Так что нас это устраивает.

Отсутствие необходимости угадывать, что делает ORM, - еще одна вещь для оператора - поскольку мы пишем запросы вручную, мы должны заранее продумать, какие индексы будет использовать запрос, будет ли соединение быстрее, чем два запроса. , или если SELECT FOR UPDATE блокирует строки, в которых нет необходимости. Выполнение EXPLAIN ANALYZE запроса, а также проверка кода SQL - это потрясающе, и все эти явные размышления о SQL действительно заставляют нас думать о таких вещах, как «транзакции» и «целостность данных». В конечном итоге мы пишем SQL лучше, чем могли бы, потому что мы должны думать об этом.

И последнее, но не менее важное: это означает, что мы получили полный доступ к функциям допинга в новых версиях Postgres. Иногда это может быть слишком много для Shiny New Toy, но jsonb типов столбцов и INSERT… ON CONFLICT DO NOTHING уже были потрясающими. Учитывая, что мы проводим обзор кода для SQL, использование этих вещей не безумие, и нет никакого странного несоответствия импеданса между абстракцией ORM и этими функциями.

КОНЕЦ

Вот где мы закончили. Мы достаточно довольны нашей нынешней ситуацией, но по-прежнему не забываем о том, что нас ждет следующая крутая вещь. Иногда я выхожу из поезда и думаю о том, как будет выглядеть что-то вроде Go на HugSQL, так что, если вы работаете над этим ОБЯЗАТЕЛЬНО, дайте мне знать.

Если у вас есть вопросы или вы думаете, что мы абсолютно безумны или у вас был подобный опыт, оставьте комментарий или заметку, и я постараюсь ответить.

✌️