Комментарии 18
Можете пояснить зачем вы каждый раз вычисляете баланс? Вообще говоря это очень плохая идея, так-как чем дальше тем дольше он у вас будет считаться.
Мы его и не вычисляем каждый раз. Он денормализован и вычисляется только при записи транзакции.
Поэтому было решено денормализовать БД, добавив поле “balance” в модель пользователя. Данное поле обновляется в методе “save” в модели “UserBalanceChange”, а для уверенности в актуальности данных в нем, мы каждую ночь его пересчитываем.
Правильнее, конечно же, хранить информацию о текущем балансе пользователя в кэше (например, в Redis) и инвалидировать при каждом изменении модели.
Я вот про это. Последнее не надо. Как и пересчитывать баланс. Если у вас есть опасность неверности баланса, то сделайте таблицу истории балансов
class BalanceChange(models.Model):
user = models.ForeignKey('User', related_name='balance_changes')
user_balance_change = models.ForeignKey('UserBalanceChange', related_name='balance_changes')
balance = models.DecimalField(_('Amount'), default=0, max_digits=18, decimal_places=6)
tsfrom = models.DateTimeField(_('date'), default=timezone.now)
tsto = models.DateTimeField(_('date'), default=timezone.now)
Как-то так. Да и в этом случае можно отказаться вообще от хранения баланса. А в качестве текущего баланса берется запись с tsto со значением null.
Хранить данных примерно столько же, с UserBalanceChange общий баланс можно точно так же в транзакции обновлять (без агрегаций, через UPDATE… SET balance = balance — 100.00). С UserBalanceChange проще вставить событие «в середину», прошедшим числом, ну и поломать что-то сложнее.
Кроме того, вроде бы BalanceChange сложно заставить работать правильно, если несколько изменений баланса происходят одновременно — мы не можем правильно прочитать текущее значение баланса (его еще может не быть видно) => не можем правильно заполнить поле «balance» (а значит, если отказаться от хранения баланса, то он может стать неверным), и нам надо как-то разрешать конфликты по tsfrom / tsto.
UserBalanceChange — это, насколько понимаю, примерно реализация martinfowler.com/eaaDev/AccountingNarrative.html.
Я бы в нем еще поле reason сделал GFK вместо int, чтоб записи в UserBalanceChange связывать с событиями, которые к измемению баланса привели (например, к подписке или к реферралу).
Кроме того, вроде бы BalanceChange сложно заставить работать правильно, если несколько изменений баланса происходят одновременно — мы не можем правильно прочитать текущее значение баланса (его еще может не быть видно) => не можем правильно заполнить поле «balance» (а значит, если отказаться от хранения баланса, то он может стать неверным), и нам надо как-то разрешать конфликты по tsfrom / tsto.
UserBalanceChange — это, насколько понимаю, примерно реализация martinfowler.com/eaaDev/AccountingNarrative.html.
Я бы в нем еще поле reason сделал GFK вместо int, чтоб записи в UserBalanceChange связывать с событиями, которые к измемению баланса привели (например, к подписке или к реферралу).
except Exception, e: pass
Я надеюсь, что все-таки в оригинальном коде вместо «pass» присутствует корректный обработчик ошибок.
Я надеюсь, что все-таки в оригинальном коде вместо «pass» присутствует корректный обработчик ошибок.
Арифметика с плавающей точкой в отношении денег в какой-то момент принесёт неожиданные сюрпризы.
habrahabr.ru/post/112953/
При очередной оплате домашнего интернета у меня на счету показывается что-то типа 449,9999890 вместо 450; и из-за этого однажды не списалась оплата с последующей приостановкой предоставления услуг.
habrahabr.ru/post/112953/
При очередной оплате домашнего интернета у меня на счету показывается что-то типа 449,9999890 вместо 450; и из-за этого однажды не списалась оплата с последующей приостановкой предоставления услуг.
Для реализации этого механизма мы используем celery – написан task, который выполняется каждый час.
А почему celery, а не cron + commands?
Если это простенький сайтик с 2-3 задачами по расписанию, то тогда особого смыла конечно нет
Если проект достаточно сложный, то celery удобнее в плане управления и масштабирования.
При росте выносим все задачи на отдельный сервер(а) и удобно рулим ими.
Если проект достаточно сложный, то celery удобнее в плане управления и масштабирования.
При росте выносим все задачи на отдельный сервер(а) и удобно рулим ими.
В дополнение к вышесказанному:
Celery позволяет не только делать периодические задачи с заданными интервалами выполнения, но и отстреливать задачу с чётко заданным временем выполнения, и это время может быть вычислено динамически на основе пользовательских данных или чего либо другого.
Также, кроме просто выноса всех задач на отдельный сервер, можно задачи распределять на выполнение в разные очереди и назначать на каждую очередь своих обработчиков. Это позволяет не стопорить частые и быстрые задачи теми задачами, которые требуют больших ресурсов и работают долго.
Celery позволяет не только делать периодические задачи с заданными интервалами выполнения, но и отстреливать задачу с чётко заданным временем выполнения, и это время может быть вычислено динамически на основе пользовательских данных или чего либо другого.
Также, кроме просто выноса всех задач на отдельный сервер, можно задачи распределять на выполнение в разные очереди и назначать на каждую очередь своих обработчиков. Это позволяет не стопорить частые и быстрые задачи теми задачами, которые требуют больших ресурсов и работают долго.
А зачем делать так, чтобы получить user?
Можное же просто
Зачем создавать и сразу же повторно сохранять?
order = OrderForPayment.objects.get(id=kwargs['InvId'])
user = User.objects.get(id=order.user.id)
Можное же просто
user = order.user
Зачем создавать и сразу же повторно сохранять?
balance_change_reason = UserBalanceChange.objects.create(
user=user,
reason=UserBalanceChange.TARIFF_HOUR_CHARGE,
amount=-hour_rate,
)
balance_change_reason.save()
Вообще странно открыть статью про биллинг и не увидеть ничего про транзакции на уровне БД.
Особенно изворот с балансом в этом плане сомнителен — должен обновляться в той же транзакции, которая добавляет UserBalanceChange.
Минус тут только один — чем больше записей в системе, тем больше время изменения баланса(пополнение\списание) и тут можно по всякому извращаться.
p.s. не в тот «уровень» ответил :(
Особенно изворот с балансом в этом плане сомнителен — должен обновляться в той же транзакции, которая добавляет UserBalanceChange.
Минус тут только один — чем больше записей в системе, тем больше время изменения баланса(пополнение\списание) и тут можно по всякому извращаться.
p.s. не в тот «уровень» ответил :(
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Разработка своей системы биллинга на Django