SQLSet — отделяем GO код от SQL-запросов
- среда, 17 декабря 2025 г. в 00:00:11
Однажды мне пришлось участвовать в переводе большого старого проекта на новую СУБД. Это заняло несколько месяцев тогда. И этот урок я запомнил на всю жизнь. В проекте повсеместно код приложения был перемешан с кодом SQL-запросов. При этом они во многих местах еще �� генерировались динамически из фрагментов текста. С тех пор я являюсь ярым сторонником отделения SQL-кода от непосредственно кода программы и патологически не перевариваю динамическую генерацию запросов.
Очевидным и хорошим способом отделения SQL от кода является тотальное использование хранимых процедур. Так я первое время и делал - весь SQL-код хранился непосредственно в БД. Тем не менее, такой подход имеет ряд недостатков:
Необходимость в дополнительных миграциях и контроле за ними
Меньшая портируемость
Более сложный синтаксис
Перенесение низкоуровневой логики проекта в слой данных
Кроме того, есть СУБД, которые не поддерживают использование хранимых процедур, такие как SQLite, DuckDB, до недавнего времени CockroachDB.
В связи с этим напрашивается решение - добавить в проект функционал, аналогичный хранимым процедурам - именованный набор текстов SQL-запросов.
Хочу представить вам простую библиотеку sqlset для удобного хранения и использования SQL-запросов в проектах Golang. При старте приложения мы натравливаем её на каталог с файлами наборов SQL-запросов (можно использовать embedded filesystem). Она их загружает в мапу, чтобы потом можно было получать из неё текст запроса по его имени. Каждый sql-файл может содержать произвольное количество запросов и загружается в мапу как отдельный набор. Таким образом, наборы запросов разбиты на категории (например - "user", "payment", "order" и т. д). Собственно, это и всё практически.
// Create a new SQLSet from the embedded filesystem.
sqlSet, err := sqlset.New(queriesFS)
if err != nil {
log.Fatalf("Failed to create SQL set: %v", err)
}
...
// Get a specific query
query, err := sqlSet.Get("users", "GetUserByID")
if err != nil {
fmt.Errorf("failed to get query: %w", err)
}
Буду безмерно благодарен за советы, здоровую аргументированную критику!