Как стать автором
Обновить

Комментарии 33

Есть ли возможность обернуть в транзакцию набор последовательных операций на уровне ORM?
Не называйте словом ORM модели в Django. ORM – это паттерн програмирования, который _не_ реализован в Django models. Если вы посмотрите официальную документацию, вы нигде не найдете там этого слова. Пожалуйста, не вводите людей в заблуждение.
Действительно. Спасибо за замечание :)
Да есть оно там. Вот пример.

Я не спора для, проясните вопрос: почему это не ORM? Дайте, пожалуйста, определение ORM, такое, чтобы Django под определение не попало. Если использовать наивное понимание Object-Relational Mapping, то код в Django именно что выполняет отображение объектов в реляционную БД и обратно. Сами они утверждают, что код в Django — ActiveRecord. Если ActiveRecord не является разновидностью ORM, объясните разницу, пожалуйста.
Честно говоря, я не понял что должна была показать мне ваша ссылка.

Поясняю.

ORM призванный решить проблемы «persistence» вашего приложения. Например, выступая прослойкой между БД и приложением, ORM гарантирует, что если приложение в рамках одной сессии несколько раз выбирает один и тот же «объект» из БД, то физически это будет один и тот же объект.

В Django это выглядело бы так:

>>> entry1 = Entry.objects.get(pk=1)
>>> entry2 = Entry.objects.get(pk=1)
>>> entry1 is entry2
True


Заметьте, я ипользовал оператор `is`, а не `==`. Этот принцип выполняется для разных запросов любой сложности, это не просто результат кеширования. Django ничего подобного вам не гарантирует, так что никакой это не object-relational mapping.

То что реализовано в Django – именно что реализация паттерна Active Record, который хоть и можеть быть частью реализацию ORM, самом по себе ORM совершенно не является. Но все привыкли называть это ORM в основном с подачи авторов документаций по PHP фреймворкам, у которых к сожалению не редко возникают проблемы с терминологией.

Из того что мне известно, ORM реализуют:
— SqlAlchemy (а точнее та ее часть, которая называется SqlAlchemy ORM)
— .NET Entity Framework
— Hibernate

Не имеют никакого отношения к ORM и реализуют паттерн Active Record:
— Django Models
— Yii и большинство других PHP фрейморков
Пока что вы меня только убедили в обратном: код в Django — ORM, т.к. сопоставляет объекты и записи в реляционной БД. Другими словами, сопоставление объектов и записей есть? Значит, ORM.

Дайте всё-таки определение ORM, пожалуйста, так, чтобы было понятно, что сравнение объектов должно выполняться не при помощи операторов типа ==, а исключительно с помощью операторов типа is. (И не относящееся к конкретному языку.)
А Django models не маппит записи бд в объекты. Если хотите, django models маппит записи в бд в состояние объектов на момент их выборки из бд. Состояния одного и того же объекта могут быть несколькими объектами, в то время как сам объект только один. В этом одно из принципиальных различий, и на самом деле оно вытекает даже из самого названия (и да, я согласен что это совершенно не очевидно на первый взгляд). ORM – это не просто штука, позволяющая работать с данными в таблицах «как с объектами», это именно что биекция данных бд в объектную модель, где каждой записи сопоставлен один и только один объект. В этом смылсе ORM, так же как и БД занимается вопросами консистентности данных и борется с коллизиями на свое уровне абстракции, и она обязана этим заниматся, будучи отображением базы данных в приложеии, в то время как Active Record не занимается этими вопросами вообще, создавая тем самым abstraction leak.

В любом случае, это вопрос определений, и честно говоря, я не хочу продолжать этот спор. В Computer Science eсть много понятий, которые за последнее время сильно исказились. Наверное для вас будет открытием если я скажу, что большинство веб-фреймворков не являются MVC, даже не смотря на то, что их разработчики бьют себя в грудь и утверждают об обратном. Дело в том, что MVC предполагает прямое взаимодействие между моделью и отображением посредством событий, а в большинстве веб-фрейворков ничего подобного нет.
Кстати, это именно та причина, почему Django разработчики честно называют свой фреймворк MTV, хотя никаких принципиальных различий в архитектуре между ним и большинством «MVC» фрейворков нет.
Если что-то не названо, то это не значит, что этого нет.
ORM в Django есть, реализована через паттерн проектирования ActiveRecord (разновидность ORM).
С чего вы взяли, что Active Record – разновидность ORM? Это не правда. Active Record может быть реализован как часть ORM, точно так же, как и существовать по отдельности от ORM. Выше я ответил вашему товарищу более подробно.
> С чего вы взяли, что Active Record – разновидность ORM
По определению Active Record и определению ORM.

ActiveRecord — всего лишь частный случай ORM. Есть и другие варианты: Table Data Gateway, Row Data Gateway, Data Mapper.
Более того, каждый Active Record — это не только частный случай ORM, но и частный случай Row Data Gateway.
Вы перечислили data-access патерны, а ORM на уровень выше, там где domain model.
а почему
        Profile.objects.\
            filter(pk=self.pk)\
           .update(balance=F('balance') + balance)

а не
self.balance = F('balance') + balance
Потому что потом вы скорее всего напишите
self.save()
а save перезаписывает все данные в базе на текущие значения в модели
нет, я напишу self.save(update_fields=['balance'])
self.balance = F('balance') + balance
#… тут кто-то в другом процессе тоже обновил этот же баланс
self.save(update_fields=['balance']) тут данные этого кого-то перезапишутся вашими
это не отличается от:
        #… тут кто-то в другом процессе тоже обновил этот же баланс
        Profile.objects.\
            filter(pk=self.pk)\
           .update(balance=F('balance') + balance)

в обоих случаях на уровне SQL будет update set balance = balance + «balance_из_параметра»
Да, я просмотрел, что у Вас тоже F используется. В этом случае действительно гонок не будет
если что, я не докапываюсь, я просто пытаюсь выяснить, может я что-то упустил.
НЛО прилетело и опубликовало эту надпись здесь
И еще:

В некоторых участках когда, возможно, стоит писать так:
def update_balance(self, balance):
        Profile.objects.select_for_update().\
            filter(pk=self.pk, balance=self.balance)\
           .update(balance=F('balance') + balance)


Чтобы не допустить, что balance может стать отрицательным
не понятна мысль, выборка идет по primary key, это однозначное определение одной записи, и как ваше дополнительное условие связно с отрицательным балансом?
Фильтр не найдет запись с таким балансом — т.е. в паралелльном запросе у пользователя он изменился по каким-либо причинам, и, возможно, требуется отдельная обработка такой ситуации
очень спорно, он может изменится по вполне легальной причине
На самом деле это очень правильно решение. Не меняйте данные не глядя. Перед тем, как прибавить или вычесть баланс вы же должны были сделать какие-то проверки. Как минимум, что баланс будет не меньше нуля (как было сказано выше). Но могут быть и другие, что пользователь потратил не больше, чем установил лимит, что положил не больше, чем возможно по законодательству. Мало ли. И поэтому, важно не только проверить по текущему значению баланса, что все ок, но и то, что этот баланс не поменялся за время проверок.

Но надо подумать, как это будет сочетаться с транзакциями, возможно они будут тут только мешать.
Поясните, почему в вашем примере связка F + update не решает проблему?
def update_balance(self, balance):
    Profile.objects.filter(pk=self.pk).update(balance=F('balance') + balance)

Решает. Но как правило вместе с обновлением баланса требуется выполнить еще какое-либо действие (сменить статус у заказа, например). Здесь и понадобятся транзакции
Ожидал большего от такого заголовка и вступления, чем пара пунктов из документации.
Зачем вообще было писать про самый обычный и всеми применимый способ?
Тема дедлоков не раскрыта. Хорошо конечно, когда select… for update на request только один.

Вообще, можно не бороться с конкурентростью, а делегировать все обновления баланса единственнуму инстансу celery worker'a.

Если вы не платежная система с сотней тысяч пользователей, производительности одного worker'a хватит за глаза.
Ну кто ж так делает-то?!: balance = balance + x
За свой опыт работы с системами, где нужно отслеживать движение финансов я уяснил для себя такую вещь — везде, где финансы есть, нужно использовать отдельную таблицу, где будет отражено любое движение средств, и баланс — это должна быть не циферка в базе, а сумма всех транзакций, которую можно поместить в отдельную view с кешированием, если нужно быстро. Иначе куча проблем потом может быть.
Moneylog можно использовать, где логгируется все перемещение денег
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.