Регистрация  |  Вход

Параллельные процессы приводящие к созданию дублированных данных

Параллельные процессы на клиенте.

вот такой пример: есть объект Анкета и у нее чайлд объект Ответы.

работа с ответами происходит на специальной ВФ странице, которая открывается с кнопки на стандартном лейауте.

и конструктор контроллера делает проверку типа ЕСЛИ статус у Анкеты == "Новая", то при первом сохранении создай новые Ответы и переведи Статус на "В процессе", НУ А ЕСЛИ статус у Анкеты != "Новая" просто апдатирую Ответы, так как они уже должны быть созданы.

и все выглядит хорошо, пока юзер дважды подряд не кликнет на кнопку открывающую нашу ВФ страницу (и каждая в новом своем собственном окне) и выполняет сохранение в обоих окнах паралельно. В таком случае в каждом окне контроллер уверен что работает с "Новой" анкетой и создают новые Ответы. И мы получаем двойной комплект ответов у одной анкеты.

Решение напрашивает само собой: перед каждым Insert Ответов делай проверку на то, что если их уже кто-то успел создать, пока ты заполнял страницу. Более того НИКОГДА не открывай эту ВФ страницу в новом окне, заставь кнопку открыть ее только в текущем окне, что уменьшит саму возможность случайного (но не преднамеренного) открытия паралельных окон.

Кажется это все? но, наверное, вы уже заметели, что ниже есть текст, и это значит, что еще не все

Параллельные процессы на сервере.

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

Единственно что я смог придумать, так на onClick джаваскриптом скрывать кнопку (вообще все командные кнопки) на странице, и это вроде работает.

Данная проблема не выглядит слишком уникальной, как бы вы ее решили?

[b]Параллельные процессы на клиенте.[/b]

вот такой пример: есть объект Анкета и у нее чайлд объект Ответы.

работа с ответами происходит на специальной ВФ странице, которая открывается с кнопки на стандартном лейауте.

и конструктор контроллера делает проверку типа ЕСЛИ статус у Анкеты == "Новая", то при первом сохранении создай новые Ответы и переведи Статус на "В процессе", НУ А ЕСЛИ статус у Анкеты != "Новая"   просто апдатирую Ответы, так как они уже должны быть созданы.

и все выглядит хорошо, пока юзер дважды подряд не кликнет на кнопку открывающую нашу ВФ страницу (и каждая в новом своем собственном окне) и выполняет сохранение в обоих окнах паралельно. В таком случае в каждом окне контроллер уверен что работает с "Новой" анкетой и создают новые Ответы. И мы получаем двойной комплект ответов у одной анкеты.

Решение напрашивает само собой: перед каждым Insert Ответов делай проверку на то, что если их уже кто-то успел создать, пока ты заполнял страницу. Более того НИКОГДА не открывай эту ВФ страницу в новом окне, заставь кнопку открыть ее только в текущем окне, что уменьшит саму возможность случайного (но не преднамеренного) открытия паралельных окон.

Кажется это все? но, наверное, вы уже заметели, что ниже есть текст, и это значит, что еще не все :(

[b]Параллельные процессы на сервере.[/b]

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

Единственно что я смог придумать, так на onClick джаваскриптом скрывать кнопку (вообще все командные кнопки) на странице, и это вроде работает.

Данная проблема не выглядит слишком уникальной, как бы вы ее решили?

Ну да, первый простейший вариант - дизейблить кнопку после нажатия.
Да, тестировщики любят подрочить мышкой.
Но если логика сложная и возможно что с записью работают 2 человека одновременно, то тут надо немного заморочиться.
- Простейший вариант проводить проверку условий перед сохранением и если условия не совпали то выдавать пользователю сообщение - "УПС! Вы не успели!"
- Есть вроде бы какая-то стандартная хрень, но я никогда с ней не работал, не знаю подходит или нет этот вариант в этом случае (http://releasenotes.docs.salesforce.com/en-us/winter16/release-notes/rn_apex_approval_locks_unlocks.htm#rn_apex_approval_locks_unlocks)
- Можно вручную при открытии страницы ставить на записи какой-то маркер что запись заблокирована. Но есть большая вероятность что пользователь тупо не завершит свои действия и запись так и останется заблокированной на вечно (что тоже можно предусмотреть, но лишний гемор).
- На одном проекте со сложной JS страницей (которая висит часами без перезагрузки) один коллега замутил оповещение об изменениях (тоже сложных) в базе через Stream API. Прикольно получилось. Такой подход позволил немного минимизировать время потраченной впустую при редактировании данных.

Ну да, первый простейший вариант - дизейблить кнопку после нажатия. 
Да, тестировщики любят подрочить мышкой.
Но если логика сложная и возможно что с записью работают 2 человека одновременно, то тут надо немного заморочиться. 
- Простейший вариант проводить проверку условий перед сохранением и если условия не совпали то выдавать пользователю сообщение - "УПС! Вы не успели!"
- Есть вроде бы какая-то стандартная хрень, но я никогда с ней не работал, не знаю подходит или нет этот вариант в этом случае (http://releasenotes.docs.salesforce.com/en-us/winter16/release-notes/rn_apex_approval_locks_unlocks.htm#rn_apex_approval_locks_unlocks)
- Можно вручную при открытии страницы ставить на записи какой-то маркер что запись заблокирована. Но есть большая вероятность что пользователь тупо не завершит свои действия и запись так и останется заблокированной на вечно (что тоже можно предусмотреть, но лишний гемор).
- На одном проекте со сложной JS страницей (которая висит часами без перезагрузки) один коллега замутил оповещение об изменениях (тоже сложных) в базе через Stream API. Прикольно получилось. Такой подход позволил немного минимизировать время потраченной впустую при редактировании данных.

Вот тут статейка попалась по теме. Вроде новая
https://sfdcfanboy.com/2017/04/26/7-ways-to-lock-a-record-in-salesforce/

Вот тут статейка попалась по теме. Вроде новая
https://sfdcfanboy.com/2017/04/26/7-ways-to-lock-a-record-in-salesforce/

FOR UPDATE тут точно не подойдет? Делать проверку не в конструкторе, а непосредственно перед сохранением записи. Т.е. юзер нажал кнопку - записи начали сохранятся - SOQL анкету с FOR UPDATE - проверяешь Статус - если == "Новая", то создаешь ответы, если != "Новая", то не создаешь.

[url=https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_locking_statements.htm]FOR UPDATE[/url] тут точно не подойдет? Делать проверку не в конструкторе, а непосредственно перед сохранением записи. Т.е. юзер нажал кнопку - записи начали сохранятся - SOQL анкету с FOR UPDATE - проверяешь Статус - если == "Новая", то создаешь ответы, если != "Новая", то не создаешь. 

Элементарный FOR UPDATE тут сработает, + блокировка кнопки на клиенте при срабатывании.

Элементарный FOR UPDATE тут сработает, + блокировка кнопки на клиенте при срабатывании.

всем спасибо за идеи

Gres
Элементарный FOR UPDATE тут сработает

вот теперь понятно, что FOR UPDATE нужная, серьезно нужная вещь.

И здесь у меня только один вопрос, почему я прочитал столько СФ мануалов, сдал столько сертификатов, и мне никто ничего не сказал и ни разу не спросил про FOR UPDATE?

всем спасибо за идеи

[quote="Gres"]Элементарный FOR UPDATE тут сработает[/quote]

вот теперь понятно, что FOR UPDATE нужная, серьезно нужная вещь. 

И здесь у меня только один вопрос, почему я прочитал столько СФ мануалов, сдал столько сертификатов, и мне никто ничего не сказал и ни разу не спросил про FOR UPDATE?

Потому что сертификаты бесполезны?

Потому что сертификаты бесполезны?

Gres
Потому что сертификаты бесполезны?

вовсе нет, просто они этот момент как-то упустили, а зря

[quote="Gres"]Потому что сертификаты бесполезны?[/quote]

вовсе нет, просто они этот момент как-то упустили, а зря

Den Brown
просто они этот момент как-то упустили, а зря

Они врядли упустили. Просто эта тема наверное затрагивается в Advanced Developer (или как он там сейчас называется). Ты сдавал на Advanced Developer?

[quote="Den Brown"]просто они этот момент как-то упустили, а зря[/quote]
Они врядли упустили. Просто эта тема наверное затрагивается в Advanced Developer (или как он там сейчас называется). Ты сдавал на Advanced Developer?

Dmitry Shnyrev
Ты сдавал на Advanced Developer?

нет, не сдавал.

просто не верю, что проблему, которую может с легкостью спровоцировать самый что ни на есть НЕ Advanced пользователь, нужно рабирать аж на Advanced Developer уровне

[quote="Dmitry Shnyrev"]Ты сдавал на Advanced Developer?[/quote]
нет, не сдавал.

просто не верю, что проблему, которую может с легкостью спровоцировать самый что ни на есть НЕ Advanced пользователь, нужно рабирать аж на Advanced Developer уровне

Ну вообще насколько я помню свой 401 сертификат там про код ВООБЩЕ ничего не было.
Я тоже тогда поражался что смысл называть сертификат Developer.

Ну вообще насколько я помню свой 401 сертификат там про код ВООБЩЕ ничего не было.
Я тоже тогда поражался что смысл называть сертификат Developer.

Dmitry Shnyrev
401 сертификат

теперь это App Builder,

в новом начальном уже много вопросов про програмирование, хоть и не сложных

[quote="Dmitry Shnyrev"] 401 сертификат[/quote]

теперь это App Builder,

в новом начальном уже много вопросов про програмирование, хоть и не сложных

Den Brown
уже много вопросов про програмирование, хоть и не сложных

Ну я бы не назвал "FOR UPDATE" не сложным. Поэтому его там и нет.

Кстати вопрос а когда собственно освобождается Lock?
Я нашел вот такую инфу
"The lock gets released when the transaction completes."

Что это означает? FOR UPDATE лочит запись между запросами? Если я сделал запрос с FOR UPDATE в конструторе а DML операция только в методе Save который может вызываться а может и не вызываться. Или запись лочится в пределах одного запроса и освобождается по его окончанию?

[quote="Den Brown"]уже много вопросов про програмирование, хоть и не сложных[/quote]
Ну я бы не назвал "FOR UPDATE" не сложным. Поэтому его там и нет.

Кстати вопрос а когда собственно освобождается Lock?
Я нашел вот такую инфу
"The lock gets released when the transaction completes."

Что это означает? FOR UPDATE лочит запись между запросами? Если я сделал запрос с FOR UPDATE в конструторе а DML операция только в методе Save который может вызываться а может и не вызываться. Или запись лочится в пределах одного запроса и освобождается по его окончанию?


Вот пока у тебя идет транзакция, вот пока у тебя и залочена запись.
Не подходит FOR UPDATE в этом случае, ибо на кнопку ты нажал и когда на экране у тебя загрузилась страничка, то транзакция уже закончилась и запись отпустило.

Вот пока у тебя идет транзакция, вот пока у тебя и залочена запись.
Не подходит FOR UPDATE в этом случае, ибо на кнопку ты нажал и когда на экране у тебя загрузилась страничка, то транзакция уже закончилась и запись отпустило.

Вот и я к чему.
Не часто приходилось реально заниматься блокированием записи во время работы пользователя, но когда делали это делали полностью кастомно. К примеру был отдельный объект lock куда забивался ID заблокированной записи и кем она заблокирована. К примеру это реализовывали в Call Center на базе Angular. Оператор открывал карточку клиента и она программно блочилась. Другой оператор при открытии карторочки того же контакта видел что запись заблокирована другим оператором. После закрытия карточки лок программно снимался (запись о блокировке из базы сносилась). Были случаи что запись блокировалась "навечно" (в основном при зависании), но опять же был предусмотрен механизм при загрузке SPA блогировки пользователя снимались (если вдруг они подвисли).
Отличный механизм. Можно много потенциально полезной статистики собрать на основе этих блокировок. К примеру был задумка (но дело не дошло) сделать страницу где "главный" мог наблюдать все блокировки по контактам и косвенно судить о продуктивности.

Вот и я к чему.
Не часто приходилось реально заниматься блокированием записи во время работы пользователя, но когда делали это делали полностью кастомно. К примеру был отдельный объект lock куда забивался ID заблокированной записи и кем она заблокирована. К примеру это реализовывали в Call Center на базе Angular. Оператор открывал карточку клиента и она программно блочилась. Другой оператор при открытии карторочки того же контакта видел что запись заблокирована другим оператором. После закрытия карточки лок программно снимался (запись о блокировке из базы сносилась). Были случаи что запись блокировалась "навечно" (в основном при зависании), но опять же был предусмотрен механизм при загрузке SPA блогировки пользователя снимались (если вдруг они подвисли). 
Отличный механизм. Можно много потенциально полезной статистики собрать на основе этих блокировок. К примеру был задумка (но дело не дошло) сделать страницу где "главный" мог наблюдать все блокировки по контактам и косвенно судить о продуктивности.

Опять же это больше подходит для полностью кастомных страниц и тем более SPA. Для стандартного SF преимущество сомнительное

Опять же это больше подходит для полностью кастомных страниц и тем более SPA. Для стандартного SF преимущество сомнительное :)