Сейчас озвучу проблему, наверняка я не первый кто с ней сталкивается и решения уже давно известны, но видимо они мне не попадались или не запомнились.
Речь идёт о задаче обеспечения неизменности состояния бизнес-объектов до конца бизнес-операции в многопользовательской среде.
Представим что есть какая-то операция, по бизнес правилам она может быть успешно выполнена только при определённом состоянии каких-то объектов. Как мы это реализуем? В коде сначала читаем эти объекты, проверяем их состояние, и если оно соответствует бизнес-правилам, то выполняем операцию. В многопользовательской среде здесь могут быть проблемы: состояние проверенных объектов может измениться между чтением объектов и выполнением операции, что по сути это приведёт к выполнению операции с нарушению бизнес-правил. Серьёзность такого нарушения может варьироваться — иногда это допустимо, а иногда нет (на одно место продано два билета).
Для избежания такой ситуации логично выполнять блокировку прочитанных объектов до конца операции так, чтобы никто другой не мог изменить их состояние.
Здесь не идёт речь о проверке состоянии самого изменяемого объекта — решение через "оптимистическую блокировку" известно. Я говорю о стабильности состояния других логически связанных сущностей, которые сами не изменяются операцией.
Первое решение, которое в принципе уже работает, это использование serializable-транзакций на базе. Здесь по сути все блокировки выполняет база. Масштабы и объём блокировок в базе в целом слабопредсказуемы, чтобы как-то на них повлиять надо плотно садиться за структуру базы и запросы.
У этого решения имеются ещё недостатки: появляются дедлоки по мере роста нагрузки и количества вовлечённых таблиц, не охватывает данные, которые не читаются из базы (находятся в кеше — справочники).
Какие другие решения можете посоветовать?
Писать свой менеджер блокировок? Есть примеры?
Технологии и архитектура: .NET, трёхзвенка Desktop Client — WCF services on IIS, EF — MS Sql Server, нет горизонтального масштабирования.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Какие другие решения можете посоветовать?
LMAX disruptor архтитектура. Если вкратце, то всю информация хранится в памяти, обработчик однопоточный. Операции (забронировать билет) читаются из блокирующей очереди. Затем меняются данные в памяти, без блокировок, т.к. обработчик однопоточный. Дальше CQRS: информация о том, что поменялось, пишется в persistent queue. При рестарте приложения данные в памяти восстанавливаются путем "перепроигрывания" всех записанный событий над некоторым начальным состоянием.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Всем доброго
P_K>Сейчас озвучу проблему, наверняка я не первый кто с ней сталкивается и решения уже давно известны, но видимо они мне не попадались или не запомнились.
Кэп: решается "правильным" проектированием. Т.е. таким, чтобы место, в котором осуществляется блокировка / синхронизация была как можно меньшим, в идеале — один атомарный инкремент/обновление поля.
Не, вру. В идеале — чтобы необходимости в транзакциях не было вообще. Отличить правильное проектирование от неправильного очень просто: если команда регулярно водит хоровод вокруг кода с обсуждением "ну и что теперь со всем этим делать?" — точно неправильное
Решений может быть тонна.
В высоконагруженных системах может сработать регистрация пулов ресурсов за каждой отдельной машиной (скажем, пул регистрируется на 5 минут, каждые 30 секунд продлевается).
Это может быть установка флага бронирования на отдельном ресурсе (опять-таки с отметкой времени, когда произошло бронирование).
Это может быть try-update loop — optimistic concurrency + retry, если не получилось.
В особо отчаянных раскладах можно даже прикрутить distributed lock или реализовать обработку силами только одного потока на одной из машин (ну и держать наготове второй инстанс на подхвате).
Всё зависит от конкретных сценариев и от опыта/предпочтений в команде.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Всем доброго
P_K>Сейчас озвучу проблему, наверняка я не первый кто с ней сталкивается и решения уже давно известны, но видимо они мне не попадались или не запомнились.
P_K>Речь идёт о задаче обеспечения неизменности состояния бизнес-объектов до конца бизнес-операции в многопользовательской среде. P_K>Представим что есть какая-то операция, по бизнес правилам она может быть успешно выполнена только при определённом состоянии каких-то объектов. Как мы это реализуем? В коде сначала читаем эти объекты, проверяем их состояние, и если оно соответствует бизнес-правилам, то выполняем операцию. В многопользовательской среде здесь могут быть проблемы: состояние проверенных объектов может измениться между чтением объектов и выполнением операции, что по сути это приведёт к выполнению операции с нарушению бизнес-правил. Серьёзность такого нарушения может варьироваться — иногда это допустимо, а иногда нет (на одно место продано два билета). P_K>Для избежания такой ситуации логично выполнять блокировку прочитанных объектов до конца операции так, чтобы никто другой не мог изменить их состояние.
P_K>Здесь не идёт речь о проверке состоянии самого изменяемого объекта — решение через "оптимистическую блокировку" известно. Я говорю о стабильности состояния других логически связанных сущностей, которые сами не изменяются операцией.
P_K>Первое решение, которое в принципе уже работает, это использование serializable-транзакций на базе. Здесь по сути все блокировки выполняет база. Масштабы и объём блокировок в базе в целом слабопредсказуемы, чтобы как-то на них повлиять надо плотно садиться за структуру базы и запросы. P_K>У этого решения имеются ещё недостатки: появляются дедлоки по мере роста нагрузки и количества вовлечённых таблиц, не охватывает данные, которые не читаются из базы (находятся в кеше — справочники).
P_K>Какие другие решения можете посоветовать? P_K>Писать свой менеджер блокировок? Есть примеры?
P_K>Технологии и архитектура: .NET, трёхзвенка Desktop Client — WCF services on IIS, EF — MS Sql Server, нет горизонтального масштабирования.
Здравствуйте, scf, Вы писали:
scf>LMAX disruptor архтитектура. Если вкратце, то всю информация хранится в памяти, обработчик однопоточный. Операции (забронировать билет) читаются из блокирующей очереди. Затем меняются данные в памяти, без блокировок, т.к. обработчик однопоточный. Дальше CQRS: информация о том, что поменялось, пишется в persistent queue. При рестарте приложения данные в памяти восстанавливаются путем "перепроигрывания" всех записанный событий над некоторым начальным состоянием.
Спасибо, интересно.
Если я правильно понял, то суть решения в исключении источника проблемы — многопоточного исполнения. Все операции выполняются последовательно, вокруг этого накручивается некая инфраструктура, повышающая производительность.
Боюсь что для проекта в его текущем состоянии переход на такую модель потребует слишком много сил и времени.
Здравствуйте, Sinix, Вы писали:
S>Кэп: решается "правильным" проектированием. Т.е. таким, чтобы место, в котором осуществляется блокировка / синхронизация была как можно меньшим, в идеале — один атомарный инкремент/обновление поля.
Круто. Я как бэ и прошу примеры "правильно" спроектированного решения.
S>В высоконагруженных системах может сработать регистрация пулов ресурсов за каждой отдельной машиной (скажем, пул регистрируется на 5 минут, каждые 30 секунд продлевается).
Нашу систему я бы не отнёс к высоконагруженным. О каких отдельных машинах вы говорите? Всё на одном сервере, написано же в первом сообщении...
S>Это может быть установка флага бронирования на отдельном ресурсе (опять-таки с отметкой времени, когда произошло бронирование).
Что за флаг, какое у него поведение и характеристики, зачем отметка времени?
S>Это может быть try-update loop — optimistic concurrency + retry, если не получилось.
Optimistic concurrency работает в пределах изменяемого агрегата, а в моём случае проблема совсем в другом. Либо моё понимание optimistic concurrency отличается от вашего.
S>В особо отчаянных раскладах можно даже прикрутить distributed lock или реализовать обработку силами только одного потока на одной из машин (ну и держать наготове второй инстанс на подхвате).
Что это такое? Опять какая-то распределённость? Её нет у меня.
S>Всё зависит от конкретных сценариев и от опыта/предпочтений в команде.
Ясен перец Похоже наши и ваши сценарии/предпочтения очень сильно различаются.
Здравствуйте, Qulac, Вы писали:
Q>Смотри блокировка с низкой степенью детализации.
Дельное предложение. Если я его правильно понимаю, то суть в организации своего механизма блокировок, оперирующего не отдельными сущностями, а какими-то более общими понятиями, как бы "покрывающими" логически связанный набор сущностей. Грубо говоря, при продаже билета в кино мы блокируем весь кинозал, а не отдельное место.
Известны какие-то библиотеки / референсные решения / best practices для этого?
Здравствуйте, Poul_Ko, Вы писали:
P_K>Здравствуйте, Qulac, Вы писали:
Q>>Смотри блокировка с низкой степенью детализации.
P_K>Дельное предложение. Если я его правильно понимаю, то суть в организации своего механизма блокировок, оперирующего не отдельными сущностями, а какими-то более общими понятиями, как бы "покрывающими" логически связанный набор сущностей. Грубо говоря, при продаже билета в кино мы блокируем весь кинозал, а не отдельное место.
Классическим примером тут является Order и OrderItem. При правке OrderItem блокируется Order и все его OderItem. Блокировка может быть как оптимистической так и пессимистической.
P_K>Известны какие-то библиотеки / референсные решения / best practices для этого?
Там ни чего сложного, см. решение у Фаулера в книге по архитектуре.
Здравствуйте, Qulac, Вы писали:
Q>Классическим примером тут является Order и OrderItem. При правке OrderItem блокируется Order и все его OderItem. Блокировка может быть как оптимистической так и пессимистической.
Вот это по-моему как раз не то. Это один агрегат. Агрегат есть единица обеспечения целостности и поэтому он всегда должен обрабатываться целиком, в том числе блокироваться тем или иным способом.
Я же говорю о другом.
Например, вы создаёте заказ. Заказ для клиента, у клиента есть какой-то свой процент скидки, и соответственно в заказе вы должны на все позиции проставить эту скидку.
Сам по себе клиент не является частью агрегата заказа. Но на время операции создания заказа мы должны обеспечить неизменность процента скидки клиента. Иначе есть риск создать заказ со скидкой, которая уже не действительна (была изменена параллельно с созданием заказа).
Вопрос в том как обеспечить эту неизменность. Возьмём решение посредством блокировок. Объектом блокировки у нас будет клиент. То есть, на время создания заказа блокируем клиента, для которого создаётся заказ.
Теперь разовьём тему. Стоимость строки заказа складывается из цены и скидки клиента. Со скидкой разобрались, цены же берутся из прайс-листа. Значит на время создания заказа нужно блокировать ещё и прайс-лист (целиком — в этом смысл подхода "крупногранулярные блокировки").
В реальности получится что на время создания заказа нужно будет блокировать кучу разных вещей, и не исключено необходимость блокировки некоторых станет ясна только в процессе. Это значит что могут возникать взаимоблокировки, что усложняет решение...
Так что с фразой "Там ни чего сложного" я бы поспорил
Здравствуйте, Poul_Ko, Вы писали:
P_K>Здравствуйте, Qulac, Вы писали:
Q>>Классическим примером тут является Order и OrderItem. При правке OrderItem блокируется Order и все его OderItem. Блокировка может быть как оптимистической так и пессимистической.
P_K>Вот это по-моему как раз не то. Это один агрегат. Агрегат есть единица обеспечения целостности и поэтому он всегда должен обрабатываться целиком, в том числе блокироваться тем или иным способом. P_K>Я же говорю о другом. P_K>Например, вы создаёте заказ. Заказ для клиента, у клиента есть какой-то свой процент скидки, и соответственно в заказе вы должны на все позиции проставить эту скидку. P_K>Сам по себе клиент не является частью агрегата заказа. Но на время операции создания заказа мы должны обеспечить неизменность процента скидки клиента. Иначе есть риск создать заказ со скидкой, которая уже не действительна (была изменена параллельно с созданием заказа). P_K>Вопрос в том как обеспечить эту неизменность. Возьмём решение посредством блокировок. Объектом блокировки у нас будет клиент. То есть, на время создания заказа блокируем клиента, для которого создаётся заказ.
P_K>Теперь разовьём тему. Стоимость строки заказа складывается из цены и скидки клиента. Со скидкой разобрались, цены же берутся из прайс-листа. Значит на время создания заказа нужно блокировать ещё и прайс-лист (целиком — в этом смысл подхода "крупногранулярные блокировки").
P_K>В реальности получится что на время создания заказа нужно будет блокировать кучу разных вещей, и не исключено необходимость блокировки некоторых станет ясна только в процессе. Это значит что могут возникать взаимоблокировки, что усложняет решение...
P_K>Так что с фразой "Там ни чего сложного" я бы поспорил
Проблемы конкурентного доступа это естественная проблема доступа к данным. Пользователь (заказчик) должен озвучить, как ваша программа в таких случаях должна работать. Изолированность бизнес-транзакций являются частью т.з. на вашу программу. А так размышлять об абстрактных вещах — пустое дело.
Здравствуйте, Qulac, Вы писали:
P_K>>Например, вы создаёте заказ. Заказ для клиента, у клиента есть какой-то свой процент скидки, и соответственно в заказе вы должны на все позиции проставить эту скидку. P_K>>Сам по себе клиент не является частью агрегата заказа. Но на время операции создания заказа мы должны обеспечить неизменность процента скидки клиента. Иначе есть риск создать заказ со скидкой, которая уже не действительна (была изменена параллельно с созданием заказа).
Q>Проблемы конкурентного доступа это естественная проблема доступа к данным. Пользователь (заказчик) должен озвучить, как ваша программа в таких случаях должна работать. Изолированность бизнес-транзакций являются частью т.з. на вашу программу. А так размышлять об абстрактных вещах — пустое дело.
Вашу мысль не понял.
Вернёмся к примеру. При создании заказа должна быть использована скидка клиента. Это и есть постановка задачи, и странно было если бы она звучала как-то так: "При создании заказа должна быть использована скидка клиента, но допустимо если система глюкнет и будет использована другая скидка, которая была до момента создания заказа."
Здравствуйте, Poul_Ko, Вы писали:
S>>В высоконагруженных системах может сработать регистрация пулов ресурсов за каждой отдельной машиной (скажем, пул регистрируется на 5 минут, каждые 30 секунд продлевается). P_K>Нашу систему я бы не отнёс к высоконагруженным. О каких отдельных машинах вы говорите? Всё на одном сервере, написано же в первом сообщении...
Ну, в таком случае кмк, нет смысла вообще выносить блокировки и проч как нечто заслуживающее отдельного внимания со стороны бизнес-кода.
Для подавляющего числа записей число конфликтов либо сводится к 0, либо достаточно last wins (к примеру, данные профиля пользователя или словарные записи, которые практически не правятся).
Остаются
* ресурсы, которые по определению исчерпаемы (то же бронирование номеров, к примеру)
* т.н. бизнес-транзакции (перевод с счёта на счёт, скажем)
* dumbass-требования вида последовательной нумерации заказов без дырок
Для этих редких случаев — да, имеет смысл заморочиться, набросать текущий биз-процесс в виде диаграммы, прикинуть возможные варианты гонок и уже исходя из этих знаний попробовать что-то переписать в плане архитектуры.
Общего решения нет, если есть конкретный пример — можно рассмотреть.
Здравствуйте, Sinix, Вы писали:
S>Общего решения нет, если есть конкретный пример — можно рассмотреть.
Типичный пример был озвучен в ветке общения с тов. Qulac.
Итак, есть бизнес-операция "Создать заказ". Заказ для какого-то клиента на какой-то товар из справочника. Клиент имеет какую-то свою скидку, товар в справочнике имеет какую-то цену.
Бизнес-правило: при создании заказа брать скидку клиента и применять её к текущей цене товара в справочнике.
Далее, заказ может находиться в состоянии "неподтверждён" и "подтверждён". Создаётся он в состоянии "неподтверждён".
Бизнес-требования: при изменении скидки клиента необходимо пересчитать стоимость всех его заказов в статусе "неподтверждён". При изменении цены товара в справочнике необходимо пересчитать стоимость всех заказов в статусе "неподтверждён", в которые входит этот товар.
Концептуально состояние сущности "заказ" зависит от состояния связанных сущностей "клиент" и "товар".
Вернёмся к операции создания заказа. Как она реализована в коде: нашли клиента, нашли товар, посчитали стоимость, создали и сохранили сущность заказа.
Вроде всё хорошо, но представим что параллельно выполняется, скажем, изменение цены товара. Транзакции идут параллельно: первая прочитала товар со старой ценой и сохранила заказ с ней, вторая обновила цену в справочнике, но не увидела ещё не созданный заказ. В итоге имеем заказ со старой ценой. Бизнес-правила нарушены.
Каковы будут ваши варианты решения?
Данный пример очень упрощён, на самом деле факторов много и варианты их влияния бывают самыми разными (например, наличие у клиента другого неподтверждённого заказа запрещает создавать новый).
Здравствуйте, Poul_Ko, Вы писали:
P_K>Итак, есть бизнес-операция "Создать заказ". Заказ для какого-то клиента на какой-то товар из справочника ... P_K>Каковы будут ваши варианты решения?
А чем не устраивает упомянутая оптимистическая блокировка? Перед переводом заказа в состояние "Подтвержден" проверять, что ничего не изменилось? И если изменилось, показывать пользователю обновлённую информацию и спрашивать подтверждение опять?
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, Poul_Ko, Вы писали:
P_K>>Итак, есть бизнес-операция "Создать заказ". Заказ для какого-то клиента на какой-то товар из справочника ... P_K>>Каковы будут ваши варианты решения?
MC>А чем не устраивает упомянутая оптимистическая блокировка? Перед переводом заказа в состояние "Подтвержден" проверять, что ничего не изменилось? И если изменилось, показывать пользователю обновлённую информацию и спрашивать подтверждение опять?
Это не соответствует бизнес-требованиям, да и перевод заказа в статус "Подтверждён" мы здесь не рассматриваем.
Давайте считать что есть эдакое глобальное требование: у неподтверждённых заказов всегда размер скидки и цена должны соответствовать этим значениям из данных клиента и товара.
Обходных манёвров можно придумать много. Например, можно скидку и цену не хранить в заказе, а подтягивать из связанных сущностей каждый раз когда нужно... Но вопрос не об этом, повторюсь, пример упрощён.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Вернёмся к операции создания заказа. Как она реализована в коде: нашли клиента, нашли товар, посчитали стоимость, создали и сохранили сущность заказа. P_K>Вроде всё хорошо, но представим что параллельно выполняется, скажем, изменение цены товара. Транзакции идут параллельно: первая прочитала товар со старой ценой и сохранила заказ с ней, вторая обновила цену в справочнике, но не увидела ещё не созданный заказ. В итоге имеем заказ со старой ценой. Бизнес-правила нарушены.
А что от чего зависит? Если у тебя заказ зависит от цены, а цена зависит от заказа — ну тут да, феерверк и бардак — как карты лягут.
Но обычно же цена товара устанавливается независимо, производителем или поставщиком. А по какой цене попадёт товар в заказ — это зависит только от конкретной ситуации. Скажем, в типичном интернет-магазине — цена товара должна быть такой, какая она была в момент генерации странички описания товара. А то юзеры будут беситься — "было указано 100ру, а купилось за 110ру! Верните мне мои кровные!!!". Зато получается, что разные клиенты в итоге могут купить тот же товар в одно и то же время по разной цене.
В таком случае делают цены неизменяемыми и добавляют цену как новую запись, делая старую "устаревшей", ордер ссылается на цену.
Вот, кстати, вроде простой случай, один сервер — но есть клиенты — и у них данные могут быть несогласованными — и получается те же проблемы, что и в распределённой системе, хочется тебе того или нет.
P_K>Каковы будут ваши варианты решения? P_K>Данный пример очень упрощён, на самом деле факторов много и варианты их влияния бывают самыми разными (например, наличие у клиента другого неподтверждённого заказа запрещает создавать новый).
Оптимистичная блокировка же.
Как видишь — универсального решения тут нет, делать можно что угодно как угодно — зависит только от конкретных бизнес-требований.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Poul_Ko, Вы писали:
P_K>>Вернёмся к операции создания заказа. Как она реализована в коде: нашли клиента, нашли товар, посчитали стоимость, создали и сохранили сущность заказа. P_K>>Вроде всё хорошо, но представим что параллельно выполняется, скажем, изменение цены товара. Транзакции идут параллельно: первая прочитала товар со старой ценой и сохранила заказ с ней, вторая обновила цену в справочнике, но не увидела ещё не созданный заказ. В итоге имеем заказ со старой ценой. Бизнес-правила нарушены. ·>А что от чего зависит? Если у тебя заказ зависит от цены, а цена зависит от заказа — ну тут да, феерверк и бардак — как карты лягут.
Заказ в зависит от цены.
Цена в справочнике от заказов не зависит. При изменении цены мы должны обновить некоторые заказы, зависимостью это считать нельзя на мой взгляд. Это следствие первой зависимости, скорее даже метод её поддержания.
·>Но обычно же цена товара устанавливается независимо...
Опять же, давайте не будем углубляться в конкретный смысл, пример искусственный.
P_K>>Данный пример очень упрощён, на самом деле факторов много и варианты их влияния бывают самыми разными (например, наличие у клиента другого неподтверждённого заказа запрещает создавать новый). ·>Оптимистичная блокировка же.
Всё равно не пойму причём тут оптимистическая блокировка. Она решает проблему изменения состояния в долгом промежутке времени (между обращениями к серверу), а я говорю об изменении состояния за время выполнения одной операции.
На примере, мы показываем пользователю что заказ будет с ценой такой-то, он жмёт кнопку "Создать заказ", данные уходят на сервер, там проверятся соответствует ли цена в справочнике той, которая была на экране — это и есть оптимистичная блокировка. Цена не соответствует — показываем ошибку "Цена поменялась". Цена соответствует — создаём заказ. И в это время цена в справочнике меняется. Заказ создаётся со старой ценой в своей транзакции, цена в справочнике меняется в своей. В итоге бизнес-правило нарушено.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Бизнес-требования: при изменении скидки клиента необходимо пересчитать стоимость всех его заказов в статусе "неподтверждён". При изменении цены товара в справочнике необходимо пересчитать стоимость всех заказов в статусе "неподтверждён", в которые входит этот товар.
Покупки немного не так работают. Типовое требование (обусловленное законодательством): заказ оплачивается строго по той цене, которая была показана пользователю в момент нажатия кнопки подтвердить / пересылки на провайдер оплаты.
Изменение прайса (например, при необходимости оплатить допуслуги / доставку) — это отдельная бизнес-операция, которая также должна быть подтверждена клиентом (почтой или звонком — неважно).
Иначе никак. Цены к моменту _фактического_ поступления денег (а это ни разу не момент фиксации оплаты провайдером) могут 10 раз поменяться.
Отсюда элементарное решение: в момент фиксации заказа к нему добавляется сущность OrderPurchase, которая и хранит фактическую стоимость товара. Запись этой информации — last wins в чистом виде.
Для эстетов делаем данные справочников immutable и храним в ордере id снапшотов, а не сами id записей.
P_K>Вроде всё хорошо, но представим что параллельно выполняется, скажем, изменение цены товара. Транзакции идут параллельно: первая прочитала товар со старой ценой и сохранила заказ с ней, вторая обновила цену в справочнике, но не увидела ещё не созданный заказ. В итоге имеем заказ со старой ценой. Бизнес-правила нарушены.
Ничего страшного. Чем ситуация "стоимость поменялась за секунду до оплаты товара" отличается от "на секунду позже"?
Здравствуйте, Sinix, Вы писали:
S>Покупки немного не так работают...
Блин, пример искусственный, давайте думать по факту проблемы.
P_K>>Вроде всё хорошо, но представим что параллельно выполняется, скажем, изменение цены товара. Транзакции идут параллельно: первая прочитала товар со старой ценой и сохранила заказ с ней, вторая обновила цену в справочнике, но не увидела ещё не созданный заказ. В итоге имеем заказ со старой ценой. Бизнес-правила нарушены.
S>Ничего страшного. Чем ситуация "стоимость поменялась за секунду до оплаты товара" отличается от "на секунду позже"?
Речь не об оплате, а о создании некого заказа. Цена в нём должна строго соответствовать цене в справочнике, до определённого момента в жизни заказа.
Если цена поменялась на секунду раньше — она подтянется из справочника и заказ создастся уже с новой ценой.
Если цена поменялась на секунду позже — обработчик изменения цены найдёт заказ и проапдейтит его.
Если цена поменялась в то же самое время — вот где проблема! Можем получить заказ со старой ценой, вот что нужно побороть.
Здравствуйте, Poul_Ko, Вы писали:
P_K>·>А что от чего зависит? Если у тебя заказ зависит от цены, а цена зависит от заказа — ну тут да, феерверк и бардак — как карты лягут. P_K>Заказ в зависит от цены. P_K>Цена в справочнике от заказов не зависит. При изменении цены мы должны обновить некоторые заказы, зависимостью это считать нельзя на мой взгляд. Это следствие первой зависимости, скорее даже метод её поддержания.
Ну да, это значит что некоторые заказы зависят от цены. Всё.
P_K>>>Данный пример очень упрощён, на самом деле факторов много и варианты их влияния бывают самыми разными (например, наличие у клиента другого неподтверждённого заказа запрещает создавать новый). P_K>·>Оптимистичная блокировка же. P_K>Всё равно не пойму причём тут оптимистическая блокировка. Она решает проблему изменения состояния в долгом промежутке времени (между обращениями к серверу), а я говорю об изменении состояния за время выполнения одной операции. P_K>На примере, мы показываем пользователю что заказ будет с ценой такой-то, он жмёт кнопку "Создать заказ", данные уходят на сервер, там проверятся соответствует ли цена в справочнике той, которая была на экране — это и есть оптимистичная блокировка. Цена не соответствует — показываем ошибку "Цена поменялась". Цена соответствует — создаём заказ. И в это время цена в справочнике меняется. Заказ создаётся со старой ценой в своей транзакции, цена в справочнике меняется в своей. В итоге бизнес-правило нарушено.
Это же транзакция — должно быть понятие точки коммита, которая даёт OK или FAIL. Мы не "создаём" заказ (что это вообще конкретно значит?), а _атомарно_ коммитим его — с подтвержёнными на момент коммита ценами. Любые последующие изменения цены "официально" считаются, что произошли после, а значит не должны влиять на заказ. Если коммит провалился (цены поменялись до), то транзакция фейлится и, при желании, идёт на второй круг, до тех пор, пока не даст OK.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай