Комментарии 9
В VK используете sqlx ? A jackc/pgx рассматривали?
VK большой и проектов много, я думаю в зависимости от задач используют разные библиотеки :)
В качестве драйвера используется pgx и еще заполнение таблиц большими объемами данных делается напрямую через pgx CopyFromSource.
Была идея везде использовать напрямую pgx но что-то не дошли руки, так как довольно много кода пришлось бы переписать.
Так толком и непонятно, почему автор так сильно против Query
Имхо, ORM - лютое дерьмо для тех, кто не осилил чистый SQL.
Есть люди вполне владеющие чистым SQL и пишущие код с ORM, потому что кода может быть сильно меньше, а импакт по производительности может быть не настолько значимым, особенно если проект не хайлоад.
Я против Query, потому что такой код очень тяжело читать и ревьювить и в нем легко ошибиться. Вместо Query я предлагаю использовать Select или Get - так код будет гораздо чище а SQL мы будем так же как и для Query писать руками.
Что то вредное.
Очень вредный совет по "Обёрткам по запуску транзакций"
Пример некорректен, тк я сильно сомневаюсь что у вас это просто функции, скорее всего в реальном коде - это методы объявленные на структуре с названием типа repository.
Но в любом случае, из-за этих функций обёрток начинают лезть проблемы - а если требуется и юзера обновить и в user_history записать изменения, а если еще в аутбокс событие положить? Еще одна/две/три, но на 90% копипастные обёртки с доп параметрами в виде model.Outbox, model.UserHistory?
Транзакция - это частный случай unitOfWork, реализуйте его нормально на уровне сервисных классов/бизнес логики, а не на уровне репозитория - это не верный слой абстракции для этого паттерна.
Нет, это именно самые общие функции которые не зависят от того, в какой части проекта они вызываются. В репозитории же тоже надо как-то данные селектить и чтобы лишние несколько строчек кода или оборачивание ошибки не писать при каждом вызове я считаю должны быть такие обертки.
По поводу транзакций - да, можно конечно только на уровне бизнес логики запускать транзакции если есть вообще деление на репозиторий и бизнес логику. Просто в последнее время же в моде микросервисный подход. Если весь смысл микросервиса только в том, чтобы строчку в базе сохранить и потом выдать список всех объектов, то и нет необходимости сильно на слои делить.
Пример не самый удачный.
Давайте попробую привести свой. Возможно, тоже не самый удачный, но смысл в сочетании действий.
func updateOrderStatusTx(ctx context.Context, tx *sqlx.DB, orderID int64, status Status) (Order, error) {
var order Order
err := dbutils.Get(ctx, tx, &order, `UPDATE orders SET status = ? WHERE id = ?`, status, orderID)
return order, err
}
func savePaymentTx(ctx context.Context, tx *sqlx.DB, ...) (..., error) {
// ...
}
func saveDeliveryTx(ctx context.Context, tx *sqlx.DB, ...) (..., error) {
// ...
}
func saveOutboxMsgTx(ctx context.Context, tx *sqlx.DB, ...) (..., error) {
// ...
}
// и тут начинается
func pay(ctx context.Context, dbh *sqlx.DB, orderID int64, payment ...) (..., err error) {
err = dbutils.RunTx(ctx, dbh, func(tx *sqlx.Tx) error {
_, err = savePaymentTx(ctx, tx, ...)
_, err = updateOrderStatusTx(ctx, tx, ...)
_, err = saveOutboxMsgTx(ctx, tx, ...)
return err
})
return u, err
}
func deliver(ctx context.Context, dbh *sqlx.DB, orderID int64, payment ...) (..., err error) {
err = dbutils.RunTx(ctx, dbh, func(tx *sqlx.Tx) error {
_, err = saveDeliveryTx(ctx, tx, ...)
_, err = updateOrderStatusTx(ctx, tx, ...)
_, err = saveOutboxMsgTx(ctx, tx, ...)
return err
})
return u, err
}
func payAndDeliver(ctx context.Context, dbh *sqlx.DB, ...) (..., error) {
err = dbutils.RunTx(ctx, dbh, func(tx *sqlx.Tx) error {
_, err = savePaymentTx(ctx, tx, ...)
_, err = saveDeliveryTx(ctx, tx, ...)
_, err = updateOrderStatusTx(ctx, tx, ...)
_, err = saveOutboxMsgTx(ctx, tx, ...)
return err
})
return u, err
}
В итоге получится что под каждый вариант использования (pay, deliver, payAndDeliver, etc) будет создано по функции с около нулевой переиспользуемостью.
А если выносить транзакции в виде unitOfWork на уровень бизнес-логики/useCase, а сами запросы на уровень infrastructure/repositry, то всё очень удачно композируется.
Просто в последнее время же в моде микросервисный подход. Если весь смысл микросервиса только в том, чтобы строчку в базе сохранить и потом выдать список всех объектов, то и нет необходимости сильно на слои делить.
Поправьте если не прав, но звучит так как будто вам такой подход симпатизирует, но вы сами его не использовали.
Просто это уже нано-сервисный подход, а ля распределённый монолит.
Хочется адептам такого подхода пожелать удачи обеспечивать транзакционную целостность и тратить своё время на что-то полезное, а не на написание двух фазных коммитов и саг.
Как запускать SQL в Go с максимальным комфортом