Обновить
8
-1
Игорь Стовпец@stoi

Разработчик Go (а еще — Delphi, Android, C++)

Отправить сообщение

Ну а про ORM-ы лучше вообще промолчу ))))

Признаюсь, что мне подсознательно кажется, что за динамику топят те, кто не любит конструировать сложные SQL запросы, процедуры. Ну или не умеет и не хочет заморачиваться. Кажется так, потому что несколько лет назад был таким же. Но теперь я считаю, что лучше не строить над SQL сладкие надстройки. В любом случае, когда ты пишешь найтивный SQL-код - у тебя больше гибкости и контроля. Юзать билдер - это всё равно, что на Python писать go-код с помощью фреймворка. Всё равно что разговаривать с иностранцем через Google Translate. Ну или гланду удалять через одно место. Изврат )
В самом Postgres, кстати можно создавать динамические запросы в процедурах. Да и вообще, в Postgres сейчас столько всего напихали - трудно найти то, что он не умеет делать. Невозможно. И делает это он по-любому оптимальнее обычно. Как-то так...

Со вторым кейсом на прошлой работе сталкивался мой коллега. И это реально будет проблемой, если нарвешься. Но мне в первую очередь не нравится смешивание GO и SQL. Я люблю порядок, когда всё на своих полках )

Недостатки динамических запросов

  • Смешивается в одну кучу приложения GO и код SQL. Портировать будет труднее.

  • Уязвимость к SQL-инъекциям — главная и самая опасная. Если в запрос напрямую вставляются пользовательские данные без правильной обработки, злоумышленник может внедрить вредоносный код. Например, классика вроде ' OR '1'='1 может обойти аутентификацию или вытащить все данные из таблицы. Это приводит к утечкам данных, удалению БД или даже выполнению команд на сервере.

  • Проблемы с производительностью — Каждый динамический запрос часто компилируется заново, что не позволяет эффективно кэшировать планы выполнения. В результате — повышенная нагрузка на CPU, медленные запросы и возможные проблемы с параметр-сниффингом (неоптимальные планы для разных значений параметров).

  • Сложность отладки и поддержки — Код становится менее читаемым, труднее тестировать и логировать. Ошибки в формировании строки могут привести к синтаксическим ошибкам или неожиданному поведению.

Есть еще huma - тоже билдер. Мне подобный сахар не нравится по нескольким причинам. Напишу в отдельном комментарии. Запросы должны быть статичными если только это возможно. А это возможно и целесообразно в 99,999% случаев.
Вот: https://habr.com/ru/articles/977046/#comment_29259296

Вопрос: А зачем в нативных либах (в родном пакете) - ОРМ? ИМХО - перебор. В сторонних либах - есть всё что душе угодно, выбирай - не хочу )

Оверхед на запуск?! Лишние сколько-то миллисекунд при запуске - редфлаг?... Ну даже не знаю, что сказать )

Всё что угодно, но лучше избегать динамики ИМХО. Помимо всего, СУБД не любят уникальные запросы и это всё может ощутимо сказаться на производительности в целом.

Да, вы угадали. И долго не мог выбрать - как лучше. Но хотелось иметь максимально простой вариант - добавил в sql-файл запрос и всё. Я периодически забываю запускать кодогенерацию ). Решающим аргументо было: Тесты на все запросы в любом случае должны быть. И ошибки в именах запросов они в любом случае должны выявлять. Если так - кодогенерация становится еще одной дополнительной и не обязательной подстраховкой. Но в целом, я считаю что плюсы и минусы кодогенерации равны 50/50. И это уже личный выбор, вкусовщина.

Не надо разный SQL. Нужно один запрос с масимальным количеством полей. Если поле на задано (пустая строка) - по нему не фильтруем.
Пример:

CREATE OR REPLACE FUNCTION get_account_list(
	IN a_login TEXT,
	IN a_password TEXT,
	IN a_limit INT,
	IN a_offset INT
)
RETURNS TABLE (
  list client_accounts,
  full_count BIGINT
)
LANGUAGE plpgsql
AS $$
BEGIN
    RETURN QUERY
		SELECT a, count(*) OVER() AS full_count FROM client_accounts AS a
		WHERE ((a.login = a_login) OR (a_login = '')) and
			((a.password = a_password) OR (a_password = '')) 
		ORDER BY a.id  ASC
		LIMIT a_limit
		OFFSET a_offset;			
END;
$$;

Ээээ, передергиваете )) Компиляция бинарника - обязательная и неизбежная процедура для всех компилируемых языков и она всегда автоматизируется в CI. Альтернативы в рамках GO тут нет. А вот про кодогенерацию забыть легко - я сто таз это делал )) . И альтернатива есть.
Я вовсе не топлю за не-кодогенерацию. Я просто хотел подсветить ситуацию 50/50, поскольку сам долго морщил лоб на эту тему и решил что идеального варианта нет.
Для себя выбрал - без кодогенерации. Но в этом случае наличие тестов на все запросы - обязательно. ИМХО, тесты по-хорошему должны покрывать и без этого все запросы к БД. В идеале-то... )

Отличная тулза. Но на мой взгляд, использование кодогенрации имеет и минусы (уже писали). Как минимум - это лишнее телодвижение, о котором запросто можно забыть.

Ради такого чистого и красивого main() можно и закрыть глаза на неявности ))

Я писал, что нейминг у меня спорный - не обращайте внимания. Контроллеры тут - про другое совсем. По поводу зависимости:
Когда ваша программа должна издать звук, вы дергаете у интерфейса аудиокарты например метод Beep(). Но аудиокарта не может обратиться к программе. Так и тут. Программа - слой service (use case), аудиокарта - слой contoller. По аналогии с компом, в слое контроллер - контроллер HDD, контроллер аудиокарты, контроллер видеокарты и т. п. Это слой "исполнителей", которые выполняют простые команды - запиши строку в БД, запиши сообщение в очередь NATS. Ну как-то так...

Я добавил диаграмму в статью.

Потому я вас и не понимаю, видимо )) Под словом "слой" вы подразумеваете другое.

Нет, я с вами не согласен. Слой use_cases это абстракция. И она одна в любом приложении. В случае с Go - папка/пакет. А уж внутри этого пакета может быть сколь-угодно сложная иерархия папок/пакетов, но они уже не называются слоями - это содержимое общего слоя use_cases (в моём случае я назвал его "service", но это не играет роли)

Но ключевой тезис, который я тут озвучил - "Сервер не может быть одновременно клиентом и наоборот" он как раз и тут спасает. Один объект может дергать методы другого, но при этом тот другой ни в коем случае не должен дергать методы первого. А лучше - вообще не знать о его существовании. Типа вот я сервис - пользуйтесь мной кто угодно.
Многоуровневая иерархия, где каждый уровень является сервером для уровня выше и клиентом - для уровня ниже.
Если всегда стараться следовать этому принципу (и в слоях и в иерархии объектов внутри слоя) - проблем не будет.

Логично выделить такие "сложные" функции в отдельный слой - мы с вами путаемся в терминологии. Это уже не "слой" в моём понимании, а некая групповая сущность в слое use_cases - "сервис", наверное. И да выстроить иерархию таких сервисов внутри слоя - не всегда просто.
Решение в лоб - не делить use_cases на сервисы вообще. Ну или делить условно - просто в разные файлы раскидать оставив одно пространство имен. Для небольших микросервисов - это самое оно.
Но когда проект большой, надо как-то уже разбивать это на отдельные сервисы иначе имена методов становятся длиннющими (нужно использовать префиксы) и код всё больше становится похож на лапшу ))

Информация

В рейтинге
Не участвует
Откуда
Сергиев Посад, Москва и Московская обл., Россия
Зарегистрирован
Активность