Optymistyczne blokowanie w narzędziach administracyjnych: zapobiegaj cichym nadpisaniom
Dowiedz się o optymistycznym blokowaniu w narzędziach administracyjnych: kolumny wersji, sprawdzenia updated_at i proste wzorce UI do obsługi konfliktów edycji bez cichych nadpisywań.

Problem: ciche nadpisania, gdy wiele osób edytuje
„Ciche nadpisanie” zdarza się, gdy dwie osoby otwierają ten sam rekord, obie wprowadzają zmiany, a ostatnia osoba, która kliknie Zapisz, wygrywa. Edycje pierwszej osoby znikają bez ostrzeżenia i często bez prostego sposobu przywrócenia.
W ruchliwym panelu administracyjnym dzieje się to cały dzień, często bez zauważenia. Ludzie mają otwarte wiele kart, przeskakują między zgłoszeniami i wracają do formularza, który „leżał” przez 20 minut. Kiedy w końcu zapisują, nie aktualizują najnowszej wersji rekordu — nadpisują ją.
Dzieje się to częściej w narzędziach back-office niż w aplikacjach publicznych, ponieważ praca jest współdzielona i oparta na rekordach. Zespoły wewnętrzne wielokrotnie edytują tych samych klientów, zamówienia, produkty i zgłoszenia, często w krótkich interwałach. Aplikacje publiczne częściej polegają na „użytkownik edytuje swoje rzeczy”, podczas gdy narzędzia administracyjne to „wielu użytkowników edytuje wspólne dane”.
Szkody rzadko są dramatyczne od razu, ale szybko się kumulują:
- Cena produktu wraca do starej wartości zaraz po aktualizacji promocji.
- Wewnętrzna notatka agenta wsparcia znika i następny agent powtarza te same kroki.
- Status zamówienia cofa się (np. „Wysłane” z powrotem na „Spakowane”), wywołując niewłaściwe działania.
- Numer telefonu lub adres klienta zostaje zastąpiony przestarzałymi danymi.
Ciche nadpisania są bolesne, ponieważ wszyscy myślą, że system poprawnie zapisał. Nie ma jasnego momentu „coś poszło nie tak”, tylko późniejsze zamieszanie, gdy raporty są niepoprawne lub ktoś pyta: „Kto to zmienił?”
Konflikty tego typu są normalne. To znak, że narzędzie jest współdzielone i użyteczne, a nie że zespół robi coś źle. Celem nie jest uniemożliwienie dwóm osobom edycji, lecz wykrycie, kiedy rekord zmienił się w trakcie edycji, i bezpieczne obsłużenie tej sytuacji.
Jeśli budujesz narzędzie wewnętrzne na platformie no-code takiej jak AppMaster, warto zaplanować to od początku. Narzędzia administracyjne szybko rosną, a gdy zespoły zaczną na nich polegać, „czasami” utrata danych stanie się stałym źródłem nieufności.
Optymistyczne blokowanie prosto i po ludzku
Gdy dwie osoby otwierają ten sam rekord i obie klikają Zapisz, masz problem współbieżności. Każda osoba zaczęła od starszego zrzutu, ale tylko jedno zapisanie może być „najnowsze”.
Bez ochrony ostatnie zapisanie wygrywa. Tak powstają ciche nadpisania: drugie zapisanie cicho zastępuje zmiany pierwszej osoby.
Optymistyczne blokowanie to prosta zasada: „Zapiszę moje zmiany tylko wtedy, gdy rekord będzie w tym samym stanie, co wtedy, gdy zacząłem edytować.” Jeśli rekord zmienił się w międzyczasie, zapis jest odrzucany, a użytkownik widzi konflikt.
Różni się to od pesymistycznego blokowania, które towarzyszy podejściu „edytuję, więc nikt inny nie może”. Pesymistyczne blokowanie zwykle oznacza twarde blokady, limit czasu i blokowanie użytkowników. Może być przydatne w rzadkich przypadkach (np. przelewy pieniędzy), ale w ruchliwych panelach administracyjnych często frustruje.
Optymistyczne blokowanie jest zazwyczaj lepszym domyślnym rozwiązaniem, bo utrzymuje płynność pracy. Ludzie mogą edytować równolegle, a system interweniuje tylko przy rzeczywistym zderzeniu.
Najlepiej sprawdza się, gdy:
- Konflikty są możliwe, ale nie ciągłe.
- Edycje są krótkie (kilka pól, krótki formularz).
- Blokowanie innych spowalnia zespół.
- Możesz pokazać jasny komunikat „ktoś zaktualizował ten rekord”.
- Twoje API może sprawdzać wersję (lub znacznik czasu) przy każdej aktualizacji.
To, czego zapobiega, to „ciche nadpisanie”. Zamiast utraty danych, masz czyste zatrzymanie: „Ten rekord zmienił się od czasu, gdy go otworzyłeś.”
Ważne jest też, czego nie zrobi. Nie powstrzyma dwóch osób przed podjęciem różnych, ale poprawnych decyzji na podstawie tych samych, starych informacji i nie zrobi automatycznego mergowania za Ciebie. I jeśli pominiesz sprawdzenie po stronie serwera, nic właściwie nie rozwiązałeś.
Typowe ograniczenia:
- Nie rozwiąże konfliktów automatycznie (wciąż potrzebna decyzja).
- Nie pomoże, jeśli użytkownicy edytują offline i synchronizują bez kontroli.
- Nie naprawi złych uprawnień (ktoś może nadal edytować coś, czego nie powinien).
- Nie wykryje konfliktów, jeśli sprawdzenie robi się tylko po stronie klienta.
W praktyce optymistyczne blokowanie to dodatkowa wartość przesyłana z edycją oraz serwerowa zasada „aktualizuj tylko jeśli pasuje”. Jeśli budujesz panel w AppMaster, ta kontrola zwykle znajduje się w logice biznesowej tam, gdzie wykonywana jest aktualizacja.
Dwa popularne podejścia: kolumna wersji vs updated_at
Aby wykryć, że rekord zmienił się w trakcie edycji, zwykle wybierasz jeden z dwóch wskaźników: numer wersji lub znacznik czasu updated_at.
Podejście 1: kolumna version (inkrementowane całkowite)
Dodaj pole version (zwykle integer). Kiedy ładujesz formularz edycji, pobierasz też aktualne version. Przy zapisie odsyłasz tę samą wartość.
Aktualizacja powiedzie się tylko wtedy, gdy przechowywana wersja nadal będzie taka sama, jaką użytkownik widział. Jeśli pasuje, aktualizujesz rekord i zwiększasz version o 1. Jeśli nie pasuje, zwracasz konflikt zamiast nadpisywania.
To łatwe do zrozumienia: version 12 oznacza „to jest 12-ta zmiana”. Unika też problemów związanych z czasem.
Podejście 2: updated_at (porównanie znaczników czasu)
Większość tabel ma już pole updated_at. Idea jest ta sama: odczytaj updated_at przy otwarciu formularza, potem dołącz je przy zapisie. Serwer aktualizuje tylko wtedy, gdy updated_at się nie zmienił.
To może dobrze działać, ale znaczniki czasu mają pułapki. Różne bazy danych przechowują różną precyzję. Niektóre zaokrąglają do sekund, co może nie wychwycić szybkich zapisów. Jeśli wiele systemów zapisuje do tej samej bazy, dryf zegara i strefy czasowe mogą wprowadzać mylące przypadki brzegowe.
Proste porównanie:
- Kolumna
version: najczystsze zachowanie, przenośne między bazami, brak problemów z zegarem. updated_at: często „darmowe”, bo już istnieje, ale precyzja i obsługa zegara mogą zawieść.
Dla większości zespołów kolumna wersji jest lepszym sygnałem głównym. Jest jawna, przewidywalna i łatwo ją powoływać w logach i zgłoszeniach wsparcia.
W AppMaster zwykle oznacza to dodanie pola integer version w Data Designer i upewnienie się, że logika aktualizacji sprawdza je przed zapisem. Możesz nadal trzymać updated_at do audytu, ale to numer wersji decyduje, czy edycja jest bezpieczna.
Co przechowywać i co wysyłać z każdą edycją
Optymistyczne blokowanie działa tylko wtedy, gdy każda edycja niesie ze sobą „ostatnio widziany” znacznik z momentu otwarcia formularza. Ten znacznik to version lub updated_at. Bez niego serwer nie wie, czy rekord zmienił się podczas edycji.
W samym rekordzie trzymaj normalne pola biznesowe oraz jedno pole współbieżności kontrolowane przez serwer. Minimalny zestaw wygląda tak:
id(stabilny identyfikator)- pola biznesowe (name, status, price, notes itd.)
version(integer inkrementowany przy każdej udanej aktualizacji) lubupdated_at(znacznik czasu ustawiany przez serwer)
Gdy ekran edycji się ładuje, formularz musi zapisać wartość ostatnio widzianego pola współbieżności. Użytkownicy nie powinni jej edytować — trzymaj ją jako pole ukryte lub w stanie formularza. Przykład: API zwraca version: 12, a formularz trzyma 12 aż do zapisania.
Kiedy użytkownik klika Zapisz, wysyłasz dwie rzeczy: zmiany i ostatnio widziany znacznik. Najprostszy kształt to body żądania zawierające id, zmienione pola i expected_version (lub expected_updated_at). W AppMaster traktuj to jak każdy inny powiązany value: załaduj go z rekordu, trzymaj niezmieniony i wyślij z aktualizacją.
Na serwerze aktualizacja musi być warunkowa. Aktualizujesz tylko wtedy, gdy oczekiwany znacznik pasuje do tego w bazie. Nie próbuj „scalić” po cichu.
Odpowiedź konfliktu powinna być jasna i łatwa do obsłużenia w UI. Praktyczna odpowiedź konfliktu zawiera:
- status HTTP
409 Conflict - krótki komunikat typu „Ten rekord został zaktualizowany przez kogoś innego.”
- aktualną wartość serwera (
current_versionlubcurrent_updated_at) - opcjonalnie: aktualny rekord serwera (żeby UI mogło pokazać, co się zmieniło)
Przykład: Sam otwiera rekord Customer z wersją 12. Priya zapisuje zmianę, robiąc wersję 13. Sam później klika Zapisz z expected_version: 12. Serwer zwraca 409 z aktualnym rekordem na wersji 13. Teraz UI może poprosić Sama o przejrzenie najnowszych wartości zamiast nadpisywać edycję Priyi.
Krok po kroku: wdrożenie optymistycznego blokowania end-to-end
Optymistyczne blokowanie sprowadza się do jednej zasady: każda edycja musi udowodnić, że bazuje na najnowszej zapisanej wersji rekordu.
1) Dodaj pole współbieżności
Wybierz jedno pole, które zmienia się przy każdym zapisie.
Dedykowane pole integer version jest najłatwiejsze do zrozumienia. Zacznij od 1 i inkrementuj przy każdej aktualizacji. Jeśli masz niezawodne updated_at, możesz go użyć, ale upewnij się, że zawsze się zmienia (również przy background jobach).
2) Wyślij tę wartość do klienta przy odczycie
Gdy UI otwiera ekran edycji, dołącz aktualne version (lub updated_at) w odpowiedzi. Przechowaj je w stanie formularza jako wartość ukrytą.
Traktuj to jak paragon mówiący: „Edytuję to, co ostatnio odczytałem.”
3) Wymagaj tej wartości przy aktualizacji
Przy zapisie klient wysyła edytowane pola razem z ostatnio widzianą wartością współbieżności.
Na serwerze wymuś warunkową aktualizację. W SQL to może wyglądać tak:
UPDATE tickets
SET status = $1,
version = version + 1
WHERE id = $2
AND version = $3;
Jeśli aktualizacja dotknie 1 wiersza, zapis powiódł się. Jeśli 0 wierszy, ktoś inny już zmienił rekord.
4) Zwróć nową wartość po sukcesie
Po udanym zapisie zwróć zaktualizowany rekord z nowym version (lub nowym updated_at). Klient powinien zastąpić stan formularza tym, co zwrócił serwer. To zapobiega „podwójnym zapisom” z użyciem starej wersji.
5) Traktuj konflikty jako normalny wynik
Gdy warunkowa aktualizacja nie powiedzie się, zwróć czytelną odpowiedź konfliktu (często HTTP 409) zawierającą:
- aktualny rekord w bazie
- zmiany, które klient próbował wprowadzić (lub wystarczającą ilość danych, by je odtworzyć)
- które pola różnią się (jeśli możesz to obliczyć)
W AppMaster mapuje się to dobrze na pole modelu PostgreSQL w Data Designer, endpoint odczytu zwracający wersję i Business Process wykonujący warunkową aktualizację oraz rozgałęziający ścieżkę na sukces lub konflikt.
Wzorce UI, które obsłużą konflikty bez irytowania użytkowników
Optymistyczne blokowanie to połowa zadania. Druga to to, co zobaczy osoba, gdy jej zapis zostanie odrzucony, bo ktoś inny zmienił rekord.
Dobre UI konfliktu ma dwa cele: zatrzymać ciche nadpisania i pomóc użytkownikowi szybko dokończyć zadanie. Dobrze zaprojektowane, wygląda jak pomocna bariera, a nie blokada.
Wzorzec 1: prosty blokujący dialog (najszybszy)
Użyj, gdy edycje są małe i użytkownicy mogą ponownie zastosować zmiany po przeładowaniu.
Krótki, konkretny komunikat: „Ten rekord zmienił się w trakcie Twojej edycji. Przeładuj, aby zobaczyć najnowszą wersję.” Potem dwie jasne akcje:
- Przeładuj i kontynuuj (pierwszorzędna)
- Skopiuj moje zmiany (opcjonalne, pomocne)
„Skopiuj moje zmiany” może wkleić niezapisane wartości do schowka lub zachować je w formularzu po przeładowaniu, żeby ludzie nie musieli pamiętać, co wpisali.
Działa dobrze przy pojedynczych polach, przełącznikach, zmianach statusu lub krótkich notatkach. To też najprostsze do wdrożenia w większości kreatorów, w tym w AppMaster.
Wzorzec 2: „Przejrzyj zmiany” (najlepsze dla ważnych rekordów)
Użyj, gdy rekord jest istotny (ceny, uprawnienia, wypłaty) lub formularz jest długi. Zamiast ślepego błędu, skieruj użytkownika na ekran porównania pokazujący:
- „Twoje zmiany” (co próbował zapisać)
- „Aktualne wartości” (najświeższe z bazy)
- „Co zmieniło się od momentu otwarcia” (konfliktujące pola)
Skup się na tym, co ma znaczenie. Nie pokazuj wszystkich pól, jeśli tylko trzy są w konflikcie.
Dla każdego konfliktującego pola daj proste wybory:
- Zachowaj moje
- Przyjmij ich
- Scal (tylko gdy ma sens, np. tagi lub notatki)
Po rozwiązaniu konfliktów zapisz ponownie z najnowszą wartością wersji. Dla bogatych tekstów pokaż mały widok diff (dodane/usunięte), aby użytkownik szybko podjął decyzję.
Kiedy pozwolić na wymuszone nadpisanie (i kto może to robić)
Czasami wymuszone nadpisanie jest potrzebne, ale powinno być rzadkie i kontrolowane. Jeśli je dodajesz, niech będzie celowe: wymagaj krótkiego powodu, loguj kto to zrobił i ogranicz do ról typu admin lub supervisor.
Dla zwykłych użytkowników domyślnie „Przejrzyj zmiany”. Wymuszenie jest sensowne, gdy użytkownik jest właścicielem rekordu, rekord jest niskiego ryzyka lub system naprawia złe dane pod nadzorem.
Przykładowy scenariusz: dwóch kolegów edytuje ten sam rekord
Dwóch agentów wsparcia, Maya i Jordan, pracuje w tym samym narzędziu administracyjnym. Oboje otwierają ten sam profil klienta, aby zaktualizować status klienta i dodać notatki po oddzielnych rozmowach.
Harmonogram (z optymistycznym blokowaniem używającym version lub updated_at):
- 10:02 - Maya otwiera Customer #4821. Formularz ładuje Status = "Needs follow-up", Notes = "Called yesterday" i Version = 7.
- 10:03 - Jordan otwiera ten sam rekord. Widział te same dane, także Version = 7.
- 10:05 - Maya zmienia Status na "Resolved" i dodaje notatkę: "Issue fixed, confirmed by customer." Klika Zapisz.
- 10:05 - Serwer aktualizuje rekord, inkrementuje Version do 8 (lub aktualizuje
updated_at) i zapisuje wpis audytu: kto co zmienił i kiedy. - 10:09 - Jordan wpisuje inną notatkę: "Customer asked for a receipt" i klika Zapisz.
Bez sprawdzenia współbieżności zapis Jordana mógłby cicho nadpisać status i notatkę Mai, w zależności od tego, jak zbudowano aktualizację. Z optymistycznym blokowaniem serwer odrzuca zapis Jordana, ponieważ próbuje zapisać Version = 7, a rekord jest już na Version = 8.
Jordan widzi jasny komunikat o konflikcie. UI pokazuje, co się stało i daje bezpieczny następny krok:
- Przeładuj najnowszy rekord (porzuć moje edycje)
- Zastosuj moje zmiany do najnowszego rekordu (gdy to możliwe)
- Przejrzyj różnice ("Moje" vs "Najnowsze") i wybierz, co zachować
Prosty ekran może pokazać:
- „Ten klient został zaktualizowany przez Mayę o 10:05”
- Pola, które się zmieniły (Status i Notes)
- Podgląd niezapisanej notatki Jordana, żeby mógł ją skopiować lub ponownie zastosować
Jordan wybiera „Przejrzyj różnice”, zachowuje Status Mai = "Resolved" i dopisuje swoją notatkę do istniejących notatek. Zapisuje ponownie, tym razem używając Version = 8, i aktualizacja się powodzi (teraz Version = 9).
Stan końcowy: brak utraty danych, jasne śledzenie kto nadpisał co i porządny zapis audytu pokazujący zmianę statusu Mai i obie notatki jako oddzielne, śledzone edycje. W narzędziu zbudowanym w AppMaster to pasuje do jednej kontroli przy zapisie plus małego dialogu rozwiązywania konfliktów w UI.
Najczęstsze błędy, które nadal powodują utratę danych
Większość błędów „optymistycznego blokowania” nie dotyczy idei, lecz przekazania stanu między UI, API i bazą. Jeśli którakolwiek warstwa zapomni zasad, nadal możesz dostać ciche nadpisania.
Klasyczny błąd to pobranie wersji (lub znacznika czasu) przy ładowaniu ekranu edycji, ale nieodsyłanie jej przy zapisie. Dzieje się to, gdy formularz jest współdzielony na różnych stronach i pole ukryte znika, albo gdy klient API wysyła tylko „zmienione” pola.
Inna pułapka to robienie sprawdzeń tylko w przeglądarce. Użytkownik może zobaczyć ostrzeżenie, ale jeśli serwer zaakceptuje aktualizację mimo to, inny klient (lub ponowna próba) może nadpisać dane. Serwer musi być ostatecznym strażnikiem.
Wzorce powodujące najwięcej utraty danych:
- Brak tokena współbieżności w żądaniu zapisu (
version,updated_atlub ETag), więc serwer nie ma nic do porównania. - Akceptowanie aktualizacji bez warunku atomowego, np. aktualizowanie tylko po
idzamiast poid + version. - Używanie
updated_ato niskiej precyzji (np. sekundy). Dwa zapisy w tej samej sekundzie mogą wyglądać identycznie. - Zastępowanie dużych pól (notatki, opisy) lub całych tablic (tagi, pozycje) bez pokazywania, co się zmieniło.
- Traktowanie każdego konfliktu jako „po prostu spróbuj ponownie”, co może ponownie zastosować przestarzałe wartości na nowszych danych.
Konkret przykład: dwóch liderów wsparcia otwiera ten sam rekord klienta. Jeden dodaje długą wewnętrzną notatkę, drugi zmienia status i zapisuje. Jeśli zapis zamienia cały ładunek rekordu, zmiana statusu może przypadkowo usunąć notatkę.
Gdy konflikt się zdarzy, zespoły nadal tracą dane, jeśli odpowiedź API jest zbyt skąpa. Nie zwracaj tylko „409 Conflict”. Zwróć wystarczająco dużo informacji do ręcznego rozwiązania:
- aktualna wersja serwera (lub
updated_at) - najnowsze wartości serwera dla zaangażowanych pól
- jasna lista pól, które się różnią
- kto zmienił i kiedy (jeśli śledzisz)
W AppMaster trzymaj tę samą dyscyplinę: zachowaj wersję w stanie UI, wyślij ją przy zapisie i egzekwuj sprawdzenie w backendowej logice przed zapisem do PostgreSQL.
Szybkie kontrole przed wdrożeniem
Zanim opublikujesz, przejrzyj tryby awarii, które tworzą sytuację „zapis zakończył się sukcesem”, a jednocześnie cicho nadpisują czyjąś pracę.
Kontrole danych i API
Upewnij się, że rekord niesie token współbieżności end-to-end. Ten token może być version (integer) lub updated_at (timestamp), ale musi być traktowany jako część rekordu, nie opcjonalne metadane.
- Odczyty zawierają token (a UI trzyma go w stanie formularza, nie tylko na ekranie).
- Każda aktualizacja odsyła ostatnio widziany token, a serwer weryfikuje go przed zapisem.
- Po sukcesie serwer zwraca nowy token, aby UI pozostało zsynchronizowane.
- Operacje zbiorcze i edycje inline stosują tę samą zasadę, bez skrótów.
- Zadania backgroundowe, które edytują te same wiersze, też sprawdzają token (albo będą powodować losowe konflikty).
W AppMaster sprawdź, że pole w Data Designer istnieje (version lub updated_at) i że Twój Business Process porównuje je przed wykonaniem aktualizacji.
Kontrole UI
Konflikt jest „bezpieczny” tylko wtedy, gdy następny krok jest oczywisty.
Gdy serwer odrzuci aktualizację, pokaż jasny komunikat: „Ten rekord zmienił się od czasu, gdy go otworzyłeś.” Następnie zaoferuj jedną bezpieczną akcję jako pierwszą: przeładuj najnowsze dane. Jeśli to możliwe, dodaj ścieżkę „przeładuj i ponownie zastosuj”, która zachowuje niezapisane dane użytkownika i stosuje je do odświeżonego rekordu, żeby drobna poprawka nie zmusiła do przepisywania.
Jeśli zespół tego potrzebuje, dodaj kontrolowane „wymuszone zapisz”. Ogranicz je rolą, potwierdzeniem i logowaniem, aby awarie mogły być naprawione bez generowania nowej przyczyny utraty danych.
Kolejne kroki: dodaj blokowanie do jednego przepływu i rozszerzaj
Zacznij mało. Wybierz jeden ekran administracyjny, gdzie użytkownicy często sobie wchodzą w drogę, i dodaj tam optymistyczne blokowanie jako pierwsze. Obszary o wysokim natężeniu konfliktów to zwykle tickety, zamówienia, ceny i stan magazynowy. Jeśli zabezpieczysz konflikty na jednym ruchliwym ekranie, szybko zobaczysz wzorzec, który można powielać.
Wybierz domyślne zachowanie konfliktu wcześniej, bo to kształtuje zarówno logikę backendu, jak i UI:
- Block-and-reload: zatrzymaj zapis, przeładuj najnowszy rekord i poproś użytkownika o ponowne zastosowanie zmiany.
- Review-and-merge: pokaż „twoje zmiany” vs „najnowsze” i pozwól użytkownikowi zdecydować.
Block-and-reload jest szybszy do zbudowania i działa, gdy edycje są krótkie (status, przypisanie, krótkie notatki). Review-and-merge warto stosować przy długich lub krytycznych rekordach (tabele cen, wielopolowe edycje zamówień).
Następnie zaimplementuj i przetestuj jeden kompletny przepływ przed rozszerzeniem:
- Wybierz ekran i wypisz pola najczęściej edytowane.
- Dodaj wartość
version(lubupdated_at) do payloadu formularza i wymagaj jej przy zapisie. - Zrób aktualizację warunkową w zapisie do bazy (aktualizuj tylko, jeśli wersja pasuje).
- Zaprojektuj komunikat konfliktu i następny krok (przeładuj, skopiuj mój tekst, otwórz widok porównania).
- Przetestuj w dwóch przeglądarkach: zapisz w karcie A, potem spróbuj zapisać przestarzałe dane w karcie B.
Dodaj lekkie logowanie konfliktów. Nawet prosty event „konflikt wystąpił” z typem rekordu, nazwą ekranu i rolą użytkownika pomaga znaleźć gorące punkty.
Jeśli budujesz narzędzia administracyjne w AppMaster (appmaster.io), główne elementy pasują do siebie: zamodeluj pole wersji w Data Designer, wymuś warunkowe aktualizacje w Business Processes i dodaj mały dialog konfliktu w kreatorze UI. Gdy pierwszy przepływ będzie stabilny, powielaj wzorzec ekran po ekranie i utrzymaj spójne UI konfliktu, aby użytkownicy nauczyli się go raz i ufali mu wszędzie.
FAQ
A silent overwrite happens when two people edit the same record from different tabs or sessions, and the last save replaces the earlier changes without any warning. The risky part is that both users see a “successful save,” so the missing edits are only noticed later.
Optimistic locking means the app only saves your changes if the record hasn’t changed since you opened it. If someone else saved first, your save is rejected with a conflict so you can review the latest data instead of overwriting it.
Pessimistic locking blocks others from editing while you’re working, which often creates waiting, timeouts, and “who locked this?” moments in admin teams. Optimistic locking usually fits admin panels better because people can work in parallel and only deal with collisions when they actually happen.
A version column is usually the simplest and most predictable option because it avoids timestamp precision and clock issues. An updated_at check can work, but it can miss rapid edits if the timestamp is stored with low precision or handled inconsistently across systems.
You need a server-controlled concurrency token on the record, typically version (integer) or updated_at (timestamp). The client must read it when the form opens, keep it unchanged while the user edits, and send it back on save as the “expected” value.
Because the client can’t be trusted to protect shared data. The server must enforce a conditional update like “update where id matches and version matches,” otherwise another client, retry, or background job can still overwrite changes silently.
A good default is a blocking message that says the record changed and offers a single safe next step: reload the latest data. If people typed something long, keep their unsaved input so they can reapply it after reload rather than retyping from memory.
Return a clear conflict response (often a 409) plus enough context to recover: the current server version and the latest server values. If you can, include who updated it and when, so the user understands why their save was rejected and what changed.
Watch for missing tokens on save, updates that only filter by id instead of id + version, and timestamp checks with low precision. Another common issue is replacing the whole record payload (like notes or arrays) instead of updating only intended fields, which increases the chance of wiping someone else’s edits.
In AppMaster, add a version field in the Data Designer and include it in the record your UI reads into the form state. Then enforce a conditional update inside your Business Process so the write only succeeds when the expected version matches, and handle the conflict branch in the UI with a reload/review flow.


