Optimistisches Locking für Admin-Tools: stille Überschreibungen verhindern
Lerne optimistisches Locking für Admin-Tools mit Versionsspalten und `updated_at`-Prüfungen kennen – plus einfache UI-Patterns, um Edit-Konflikte ohne stille Überschreibungen zu behandeln.

Das Problem: stille Überschreibungen, wenn viele Leute editieren
Eine „stille Überschreibung“ passiert, wenn zwei Personen denselben Datensatz öffnen, beide Änderungen vornehmen und die zuletzt speichernde Person gewinnt. Die Änderungen der ersten Person verschwinden ohne Warnung und oft ohne einfache Möglichkeit zur Wiederherstellung.
In einem stark genutzten Admin-Panel kann das den ganzen Tag passieren, ohne dass es jemand bemerkt. Leute haben mehrere Tabs offen, springen zwischen Tickets und kehren zu einem Formular zurück, das 20 Minuten lang offen war. Wenn sie dann speichern, aktualisieren sie nicht die neueste Version des Datensatzes. Sie überschreiben sie.
Das tritt häufiger in Back-Office-Tools als in öffentlichen Apps auf, weil die Arbeit kollaborativ und datensatzbasiert ist. Interne Teams bearbeiten dieselben Kunden, Bestellungen, Produkte und Anfragen wiederholt, oft in kurzen Sessions. Öffentliche Apps sind häufiger „ein Nutzer bearbeitet seine eigenen Daten“, während Admin-Tools eher „viele Nutzer bearbeiten geteilte Daten“ sind.
Der Schaden ist selten in einem Moment dramatisch, aber er summiert sich schnell:
- Ein Produktpreis wird direkt nach einer Promo fälschlich auf einen alten Wert zurückgesetzt.
- Die interne Notiz eines Support-Agenten verschwindet, sodass der nächste Agent die gleiche Fehlerbehebung wiederholt.
- Ein Bestellstatus fällt zurück (z. B. von „Versandt“ zu „Verpackt“) und löst falsche Folgeaktionen aus.
- Eine Kunden-Telefonnummer oder Adresse wird durch veraltete Infos ersetzt.
Stille Überschreibungen sind schmerzhaft, weil alle denken, das System habe korrekt gespeichert. Es gibt keinen klaren „etwas ist schiefgelaufen“-Moment, sondern Verwirrung später, wenn Reports nicht stimmen oder ein Kollege fragt: „Wer hat das geändert?“
Konflikte dieser Art sind normal. Sie zeigen, dass das Tool geteilt und nützlich ist, nicht dass dein Team etwas falsch macht. Das Ziel ist nicht, zwei Leute vom Editieren abzuhalten, sondern zu erkennen, dass sich der Datensatz geändert hat, während jemand editiert hat, und diesen Moment sicher zu behandeln.
Wenn du ein internes Tool in einer No-Code-Plattform wie AppMaster baust, lohnt es sich, das früh einzuplanen. Admin-Tools wachsen schnell, und sobald Teams davon abhängig sind, wird gelegentlicher Datenverlust zu einem dauerhaften Vertrauensproblem.
Optimistisches Locking in einfachen Worten
Wenn zwei Personen denselben Datensatz öffnen und beide auf Speichern klicken, hast du Konkurrenz. Jede Person startete von einem älteren Snapshot, aber nur eine kann beim Speichern „die neueste“ sein.
Ohne Schutz gewinnt der letzte Save. So entstehen stille Überschreibungen: der zweite Save ersetzt leise die Änderungen der ersten Person.
Optimistisches Locking ist eine einfache Regel: „Ich speichere meine Änderungen nur, wenn der Datensatz noch im gleichen Zustand ist wie beim Start meiner Bearbeitung.“ Wenn sich der Datensatz inzwischen geändert hat, wird das Speichern abgelehnt und der Nutzer sieht einen Konflikt.
Das unterscheidet sich vom pessimistischen Locking, das eher „Ich bearbeite das, also darf sonst keiner“ bedeutet. Pessimistisches Locking führt meist zu harten Sperren, Timeouts und blockierten Nutzern. Es kann in seltenen Fällen sinnvoll sein (zum Beispiel bei Geldüberweisungen), ist aber in geschäftigen Admin-Tools, wo viele kleine Änderungen den ganzen Tag passieren, oft frustrierend.
Optimistisches Locking ist meist die bessere Voreinstellung, weil es den Arbeitsfluss erhält. Leute können parallel arbeiten und das System greift nur bei echten Kollisionen ein.
Es passt am besten, wenn:
- Konflikte möglich, aber nicht permanent sind.
- Änderungen schnell sind (ein paar Felder, kurzes Formular).
- Blockieren anderer die Arbeit verlangsamen würde.
- Du eine klare Meldung wie „jemand hat das aktualisiert“ zeigen kannst.
- Deine API bei jedem Update eine Version (oder Zeitstempel) prüfen kann.
Was es verhindert, ist das „leise Überschreiben“-Problem. Statt Datenverlust gibt es einen sauberen Stopp: „Dieser Datensatz wurde seit deinem Öffnen geändert.“
Was es nicht kann, ist auch wichtig. Es stoppt nicht, dass zwei Personen aufgrund gleicher alter Informationen unterschiedliche, aber gültige Entscheidungen treffen, und es führt keine automatische Zusammenführung für dich durch. Und wenn du die Prüfung nicht serverseitig machst, hast du nichts wirklich gelöst.
Typische Grenzen:
- Es löst Konflikte nicht automatisch (du brauchst weiterhin eine Entscheidung).
- Es hilft nicht, wenn Nutzer offline bearbeiten und später ohne Prüfungen syncen.
- Es behebt keine fehlerhaften Berechtigungen (jemand kann weiterhin falsche Dinge bearbeiten).
- Es erkennt Konflikte nicht, wenn du die Prüfung nur im Client machst.
In der Praxis ist optimistisches Locking nur ein zusätzlicher Wert, den das Edit mitträgt, plus eine serverseitige Regel „nur aktualisieren, wenn es übereinstimmt“. Wenn du ein Admin-Panel in AppMaster baust, lebt diese Prüfung typischerweise in deiner Business Logic genau dort, wo Updates ausgeführt werden.
Zwei gängige Ansätze: Versionsspalte vs updated_at
Um zu erkennen, dass sich ein Datensatz geändert hat, während jemand ihn bearbeitet, wählst du normalerweise eines von zwei Signalen: eine Versionsnummer oder einen updated_at-Zeitstempel.
Ansatz 1: Versionsspalte (inkrementierende Ganzzahl)
Füge ein Feld version (meist Integer) hinzu. Wenn du das Formular lädst, lieferst du auch die aktuelle version. Beim Speichern schickst du denselben Wert zurück.
Das Update gelingt nur, wenn die gespeicherte Version noch mit derjenigen übereinstimmt, mit der der Nutzer angefangen hat. Wenn sie passt, aktualisierst du den Datensatz und erhöhst version um 1. Wenn nicht, gibst du statt eines Überschreibens einen Konflikt zurück.
Das ist leicht nachzuvollziehen: Version 12 heißt „das ist die 12. Änderung“. Es vermeidet auch zeitbezogene Randfälle.
Ansatz 2: updated_at (Zeitstempel-Vergleich)
Die meisten Tabellen haben bereits ein updated_at-Feld. Die Idee ist die gleiche: lese updated_at, wenn das Formular geöffnet wird, und sende es beim Speichern mit. Der Server aktualisiert nur, wenn updated_at unverändert ist.
Das kann gut funktionieren, aber Zeitstempel haben Fallen. Verschiedene Datenbanken speichern unterschiedliche Genauigkeit. Manche runden auf Sekunden, was schnelle Änderungen übersehen kann. Wenn mehrere Systeme auf dieselbe Datenbank schreiben, können Clock-Drift und Zeitzonenhandling für verwirrende Randfälle sorgen.
Einfacher Vergleich:
- Versionsspalte: klares Verhalten, portabel über Datenbanken, keine Zeitprobleme.
updated_at: oft „kostenlos“, weil es schon existiert, aber Präzision und Zeitbehandlung können Probleme machen.
Für die meisten Teams ist eine Versionsspalte das bessere primäre Signal. Sie ist explizit, vorhersehbar und leicht in Logs und Support-Tickets referenzierbar.
Wenn du in AppMaster baust, bedeutet das typischerweise, ein Integer-version-Feld im Data Designer hinzuzufügen und sicherzustellen, dass deine Update-Logik es vor dem Speichern prüft. Du kannst updated_at weiterhin für Auditing behalten, aber lass die Versionsnummer entscheiden, ob eine Änderung sicher angewendet werden kann.
Was zu speichern ist und was mit jedem Edit gesendet werden muss
Optimistisches Locking funktioniert nur, wenn jede Änderung ein „zuletzt gesehen“-Marker aus dem Moment trägt, in dem der Nutzer das Formular geöffnet hat. Dieser Marker kann version oder updated_at sein. Ohne ihn kann der Server nicht erkennen, ob sich der Datensatz während des Tippens geändert hat.
Am Datensatz selbst behältst du deine normalen Business-Felder plus ein Concurrency-Feld, das der Server steuert. Das Minimum sieht so aus:
id(stabile Kennung)- Business-Felder (Name, Status, Preis, Notizen etc.)
version(Integer, erhöht sich bei jedem erfolgreichen Update) oderupdated_at(serverseitig gesetzter Timestamp)
Wenn der Edit-Screen lädt, muss das Formular den zuletzt gesehenen Wert dieses Concurrency-Felds speichern. Nutzer sollten ihn nicht bearbeiten können; halte ihn als Hidden-Feld oder im Form-State. Beispiel: die API liefert version: 12 und das Formular behält 12, bis auf Save geklickt wird.
Beim Klick auf Save schickst du zwei Dinge: die Änderungen und den zuletzt gesehenen Marker. Die einfachste Form ist, ihn im Request-Body zu senden — id, geänderte Felder und expected_version (oder expected_updated_at). Wenn du die UI in AppMaster baust, behandle es wie jeden anderen gebundenen Wert: lade es mit dem Datensatz, halte es unverändert und sende es mit dem Update.
Auf dem Server muss das Update bedingt sein. Du aktualisierst nur, wenn der erwartete Marker mit dem aktuellen Datenbankwert übereinstimmt. Merge nicht stillschweigend.
Eine Konflikt-Antwort sollte klar und einfach im UI zu handhaben sein. Eine praktische Konflikt-Antwort enthält:
- HTTP-Status
409 Conflict - eine kurze Meldung wie „Dieser Datensatz wurde von jemand anderem aktualisiert.“
- den aktuellen Serverwert (
current_versionodercurrent_updated_at) - optional den aktuellen Serverdatensatz (damit das UI sehen kann, was sich geändert hat)
Beispiel: Sam öffnet einen Kunden-Datensatz bei Version 12. Priya speichert eine Änderung, damit wird es Version 13. Später klickt Sam auf Save mit expected_version: 12. Der Server liefert 409 mit dem aktuellen Datensatz auf Version 13. Jetzt kann das UI Sam auffordern, die neuesten Werte zu prüfen, anstatt Priyas Änderungen zu überschreiben.
Schritt-für-Schritt: optimistisches Locking Ende-zu-Ende implementieren
Optimistisches Locking läuft größtenteils auf eine Regel hinaus: Jeder Edit muss beweisen, dass er auf der zuletzt gespeicherten Version des Datensatzes basiert.
1) Ein Concurrency-Feld hinzufügen
Wähle ein Feld, das sich bei jedem Write ändert.
Eine dedizierte Integer-version ist am einfachsten zu verstehen. Starte bei 1 und erhöhe bei jedem Update. Wenn du bereits ein verlässliches updated_at hast, das bei jedem Write aktualisiert wird, kannst du das stattdessen verwenden — stelle aber sicher, dass es bei allen Writes (inklusive Background-Jobs) aktualisiert wird.
2) Diesen Wert beim Lesen an den Client senden
Wenn das UI einen Edit-Screen öffnet, liefere die aktuelle version (oder updated_at) in der Antwort. Speichere sie im Form-State als Hidden-Wert.
Denk daran: es ist eine Quittung, die sagt: „Ich bearbeite das, was ich zuletzt gelesen habe.“
3) Den Wert beim Update verlangen
Beim Speichern sendet der Client die editierten Felder plus den zuletzt gesehenen Concurrency-Wert.
Auf dem Server mache das Update bedingt. In SQL-Ausdrucksform ist es:
UPDATE tickets
SET status = $1,
version = version + 1
WHERE id = $2
AND version = $3;
Wenn das Update 1 Zeile betrifft, war das Speichern erfolgreich. Wenn 0 Zeilen betroffen sind, hat jemand anderes den Datensatz bereits geändert.
4) Nach Erfolg den neuen Wert zurückgeben
Nach einem erfolgreichen Save gib den aktualisierten Datensatz mit der neuen version (oder dem neuen updated_at) zurück. Der Client sollte den Form-State mit dem zurückgegebenen Datensatz ersetzen. Das verhindert doppelte Saves mit einer alten Version.
5) Konflikte als normale Möglichkeit behandeln
Wenn das bedingte Update fehlschlägt, gib eine klare Konflikt-Antwort (oft HTTP 409) zurück, die enthält:
- den aktuellen Datensatz, so wie er jetzt existiert
- die versuchten Änderungen des Clients (oder genug Info, um sie zu rekonstruieren)
- welche Felder abweichen (wenn du das berechnen kannst)
In AppMaster passt das gut zu einem PostgreSQL-Modellfeld im Data Designer, einem Read-Endpoint, der die Version zurückgibt, und einem Business Process, der das bedingte Update ausführt und je nach Ausgang in Erfolg oder Konflikt verzweigt.
UI-Patterns, die Konflikte ohne Frust behandeln
Optimistisches Locking ist nur die halbe Miete. Die andere Hälfte ist, was der Nutzer sieht, wenn sein Save abgelehnt wird, weil jemand anderes den Datensatz geändert hat.
Gute Konflikt-UI hat zwei Ziele: stille Überschreibungen verhindern und dem Nutzer helfen, seine Aufgabe schnell zu beenden. Gut umgesetzt fühlt es sich wie eine hilfreiche Leitplanke an, nicht wie ein Hindernis.
Pattern 1: Der einfache blockierende Dialog (am schnellsten)
Nutze das, wenn Änderungen klein sind und Nutzer ihre Änderung nach einem Neuladen leicht wieder anwenden können.
Halte die Meldung kurz und konkret: „Dieser Datensatz hat sich während deiner Bearbeitung geändert. Neu laden, um die aktuelle Version zu sehen.“ Dann zwei klare Aktionen anbieten:
- Neu laden und weitermachen (primär)
- Meine Änderungen kopieren (optional, hilfreich)
„Meine Änderungen kopieren“ kann die ungespeicherten Werte in die Zwischenablage legen oder sie nach dem Reload im Formular behalten, sodass Leute nicht alles neu tippen müssen.
Das funktioniert gut für Einzel-Feld-Updates, Toggles, Statusänderungen oder kurze Notizen. Es ist auch am einfachsten in vielen Buildern umzusetzen, inklusive AppMaster-basierten Admin-Screens.
Pattern 2: „Änderungen prüfen“ (am besten für wertvolle Datensätze)
Nutze das, wenn der Datensatz wichtig ist (Preise, Berechtigungen, Auszahlungen, Kontoeinstellungen) oder das Formular lang ist. Anstatt einer Sackgassen-Fehlermeldung leite den Nutzer zu einem Konflikt-Screen, der vergleicht:
- „Deine Änderungen“ (was du zu speichern versucht hast)
- „Aktuelle Werte“ (das Neueste aus der Datenbank)
- „Was sich seit dem Öffnen geändert hat“ (die konfligierenden Felder)
Halte es fokussiert. Zeige nicht jedes Feld, wenn nur drei in Konflikt sind.
Für jedes konfligierende Feld biete einfache Optionen an:
- Meine Änderung behalten
- Ihre Änderung übernehmen
- Merge (nur wenn sinnvoll, z. B. bei Tags oder Notizen)
Nachdem der Nutzer Konflikte gelöst hat, speichere erneut mit dem neuesten Versionswert. Bei Rich-Text oder langen Notizen ist eine kleine Diff-Ansicht (hinzugefügt/entfernt) hilfreich, damit Nutzer schnell entscheiden können.
Wann ein erzwungenes Überschreiben erlaubt sein sollte (und wer es darf)
Manchmal ist ein Forced Overwrite nötig, aber es sollte selten und kontrolliert sein. Wenn du es erlaubst, mach es bewusst: fordere einen kurzen Grund, logge, wer es gemacht hat, und beschränke es auf Rollen wie Admins oder Supervisoren.
Für reguläre Nutzer ist „Änderungen prüfen“ die Vorgabe. Forced Overwrite ist am ehesten vertretbar, wenn der Nutzer Eigentümer des Datensatzes ist, der Datensatz geringes Risiko trägt oder das System unter Aufsicht falsche Daten korrigiert.
Beispiel-Szenario: zwei Teammitglieder bearbeiten denselben Datensatz
Zwei Support-Agenten, Maya und Jordan, arbeiten im selben Admin-Tool. Beide öffnen dasselbe Kundenprofil, um den Kundenstatus zu aktualisieren und Notizen nach separaten Anrufen hinzuzufügen.
Zeitlicher Ablauf (mit optimistischem Locking über version oder updated_at):
- 10:02 – Maya öffnet Kunde #4821. Formular lädt Status = "Needs follow-up", Notes = "Called yesterday" und Version = 7.
- 10:03 – Jordan öffnet denselben Kunden. Er sieht dieselben Daten, ebenfalls Version = 7.
- 10:05 – Maya ändert Status zu "Resolved" und fügt eine Notiz hinzu: "Issue fixed, confirmed by customer." Sie klickt Save.
- 10:05 – Der Server aktualisiert den Datensatz, erhöht Version auf 8 (oder updated_at) und speichert einen Audit-Eintrag: wer wann was geändert hat.
- 10:09 – Jordan tippt eine andere Notiz: "Customer asked for a receipt" und klickt Save.
Ohne Konkurrenzprüfung könnte Jordans Save Mayas Status und Notiz leise überschreiben. Mit optimistischem Locking lehnt der Server Jordans Update ab, weil er versucht, mit Version = 7 zu speichern, während der Datensatz bereits Version = 8 hat.
Jordan sieht eine klare Konflikt-Meldung. Das UI zeigt, was passiert ist, und bietet sichere nächste Schritte:
- Die neueste Version neu laden (meine Änderungen verwerfen)
- Meine Änderungen auf die neueste Version anwenden (wenn möglich)
- Unterschiede prüfen ("Meine" vs "Neueste") und auswählen, was behalten werden soll
Ein einfacher Screen kann anzeigen:
- „Dieser Kunde wurde von Maya um 10:05 aktualisiert“
- Welche Felder sich geändert haben (Status und Notes)
- Eine Vorschau von Jordans ungespeicherter Notiz, damit er sie kopieren oder wieder anwenden kann
Jordan wählt „Unterschiede prüfen“, behält Mayas Status = "Resolved" und hängt seine Notiz an die bestehenden Notizen an. Er speichert erneut, nun mit Version = 8, und das Update gelingt (jetzt Version = 9).
Endzustand: kein Datenverlust, kein Rätselraten, wer wen überschrieben hat, und ein sauberer Audit-Trace, der Mayas Status-Änderung und beide Notizen als separate, nachverfolgbare Edits zeigt. In einem mit AppMaster erstellten Tool entspricht das einer einzigen Prüfung beim Update plus einem kleinen Konflikt-Dialog im Admin-UI.
Häufige Fehler, die weiterhin Datenverlust verursachen
Die meisten „optimistischen Locking“-Bugs liegen nicht in der Idee selbst, sondern in der Übergabe zwischen UI, API und Datenbank. Wenn eine Schicht die Regeln vergisst, kannst du trotzdem stille Überschreibungen bekommen.
Ein klassischer Fehler ist, die Version (oder den Zeitstempel) beim Laden zu sammeln, sie aber beim Speichern nicht zurückzusenden. Das passiert oft, wenn ein Formular über Seiten wiederverwendet wird und das Hidden-Feld verloren geht, oder wenn ein API-Client nur „geänderte“ Felder sendet.
Eine weitere Falle ist, Prüfungen nur im Browser durchzuführen. Der Nutzer sieht vielleicht eine Warnung, aber wenn der Server das Update trotzdem annimmt, kann ein anderer Client (oder ein Retry) die Daten überschreiben. Der Server muss die finale Gatekeeper-Rolle übernehmen.
Muster, die am meisten Datenverlust verursachen:
- Fehlendes Concurrency-Token in der Save-Request (
version,updated_atoder ETag), sodass der Server nichts vergleichen kann. - Updates ohne atomare Bedingung, z. B. nur nach
idstatt nachid + versionaktualisieren. updated_atmit niedriger Präzision (z. B. Sekunden). Zwei Änderungen in derselben Sekunde können identisch aussehen.- Große Felder (Notizen, Beschreibungen) oder ganze Arrays (Tags, Positionen) ersetzen, ohne zu zeigen, was sich verändert hat.
- Jeden Konflikt einfach mit „nochmal versuchen“ zu beantworten, was veraltete Werte wieder oben drauf legen kann.
Ein konkretes Beispiel: Zwei Support-Leads öffnen denselben Kunden-Datensatz. Der eine fügt eine lange interne Notiz hinzu, der andere ändert den Status und speichert. Wenn dein Save die gesamte Payload ersetzt, kann die Status-Änderung versehentlich die Notiz löschen.
Wenn ein Konflikt passiert, verlieren Teams weiterhin Daten, wenn die API-Antwort zu dünn ist. Gib nicht nur „409 Conflict“ zurück. Gib genug, damit ein Mensch das Problem lösen kann:
- Die aktuelle Server-Version (oder
updated_at) - Die neuesten Serverwerte der betroffenen Felder
- Eine klare Liste der Felder, die abweichen
- Wer es wann geändert hat (falls getrackt)
Wenn du das in AppMaster umsetzt, wende dieselbe Disziplin an: behalte die Version im UI-State, sende sie mit dem Update und erzwinge die Prüfung in der Backend-Logik bevor du in PostgreSQL schreibst.
Kurze Checks bevor du live gehst
Bevor du das ausrollst, prüfe die Ausfallmodi, die „es wurde gespeichert“ erzeugen, während jemand anderes leise Arbeit überschreibt.
Daten- und API-Checks
Stelle sicher, dass der Datensatz ein Concurrency-Token durchgängig trägt. Dieses Token kann version (Integer) oder updated_at (Timestamp) sein, aber es muss als Teil des Datensatzes behandelt werden, nicht als optionales Metadatum.
- Reads beinhalten das Token (und das UI speichert es im Form-State, nicht nur auf dem Screen).
- Jedes Update sendet das zuletzt gesehene Token zurück, und der Server prüft es vor dem Schreiben.
- Bei Erfolg liefert der Server das neue Token zurück, damit das UI synchron bleibt.
- Bulk-Edits und Inline-Edits folgen derselben Regel, kein Sonderweg.
- Hintergrund-Jobs, die dieselben Zeilen ändern, prüfen ebenfalls das Token (sonst entstehen zufällige Konflikte).
Wenn du in AppMaster baust, prüfe nochmals, dass das Data Designer-Feld existiert (version oder updated_at) und dass dein Business Process den Vergleich macht, bevor das eigentliche Update ausgeführt wird.
UI-Checks
Ein Konflikt ist nur „sicher“, wenn der nächste Schritt offensichtlich ist.
Wenn der Server ein Update ablehnt, zeige eine klare Meldung wie: „Dieser Datensatz hat sich seit deinem Öffnen geändert.“ Biete dann eine sichere Aktion zuerst an: neu laden. Wenn möglich, füge einen Pfad „neu laden und erneut anwenden“ hinzu, der die ungespeicherten Eingaben behält und sie nach dem Refresh wieder anwendet, damit eine kleine Änderung nicht zum Neutippen wird.
Wenn dein Team es wirklich braucht, füge eine kontrollierte „Force Save“-Option hinzu. Gate sie nach Rolle, bestätige sie und logge, wer die Force-Save ausgeführt hat und was sich geändert hat. So bleiben Notfälle möglich, ohne dass Datenverlust zur Norm wird.
Nächste Schritte: Locking in einem Workflow hinzufügen und ausrollen
Fang klein an. Wähle einen Admin-Screen, wo Leute sich häufig in die Quere kommen, und füge dort zuerst optimistisches Locking hinzu. Bereiche mit hoher Kollisionswahrscheinlichkeit sind meist Tickets, Bestellungen, Preise und Inventar. Wenn Konflikte auf einem belebten Screen sicher gemacht sind, erkennst du schnell das Muster zum Wiederholen.
Wähle dein Standard-Verhalten für Konflikte früh, denn es prägt Backend-Logik und UI:
- Block-and-reload: Stoppe den Save, lade den neuesten Datensatz und bitte den Nutzer, seine Änderung erneut anzuwenden.
- Review-and-merge: Zeige „deine Änderungen“ vs „neueste Änderungen“ und lass den Nutzer entscheiden, was bleibt.
Block-and-reload ist schneller zu bauen und eignet sich, wenn Änderungen kurz sind (Status, Zuordnung, kurze Notizen). Review-and-merge lohnt sich bei langen oder kritischen Datensätzen (Preistabellen, mehrfeldrige Bestell-Edits).
Dann implementiere und teste einen kompletten Flow, bevor du ausrollst:
- Wähle einen Screen und liste die Felder, die Nutzer am meisten bearbeiten.
- Füge ein
version(oderupdated_at) Feld zur Form-Payload hinzu und fordere es beim Save. - Mach das Update in der Datenbank bedingt (nur update, wenn die Version passt).
- Entwerfe die Konflikt-Meldung und die nächste Aktion (Reload, Copy my text, Compare-View).
- Teste mit zwei Browsern: Speichere in Tab A, versuche dann veraltete Daten in Tab B zu speichern.
Füge leichtes Logging für Konflikte hinzu. Schon ein einfacher Event „Konflikt aufgetreten“ mit Datensatztyp, Screen-Name und Nutzerrolle hilft, Hotspots zu erkennen.
Wenn du Admin-Tools mit AppMaster baust, lassen sich die Hauptbausteine sauber abbilden: ein Version-Feld im Data Designer, bedingte Updates in Business Processes und ein kleiner Konflikt-Dialog im UI-Builder. Sobald der erste Workflow stabil ist, wiederhole das Muster Screen für Screen und halte das Konflikt-UI konsistent, damit Nutzer es einmal lernen und überall darauf vertrauen können.
FAQ
Eine stille Überschreibung passiert, wenn zwei Personen denselben Datensatz in verschiedenen Tabs oder Sessions bearbeiten und der zuletzt gespeicherte Datensatz die vorherigen Änderungen ohne Warnung ersetzt. Das Problem ist, dass beide Nutzer oft eine „Erfolg“-Meldung sehen, sodass die verlorenen Änderungen erst später bemerkt werden.
Optimistisches Locking bedeutet, dass die App deine Änderungen nur speichert, wenn sich der Datensatz seit dem Öffnen nicht verändert hat. Wenn jemand anderes zuerst gespeichert hat, wird dein Save mit einem Konflikt abgelehnt, damit du die aktuelle Version prüfen kannst, statt sie zu überschreiben.
Pessimistisches Locking sperrt andere Nutzer während deiner Bearbeitung und führt oft zu Wartezeiten, Timeouts und der Frage „Wer hat das gesperrt?“. Optimistisches Locking passt besser zu Admin-Panels, weil mehrere Personen parallel arbeiten können und das System nur bei echten Kollisionen eingreift.
Eine Versionsspalte ist meist die einfachste und verlässlichste Option, weil sie Probleme mit Zeitstempeln und deren Genauigkeit vermeidet. Ein updated_at-Vergleich kann funktionieren, aber Timestamp-Präzision oder Zeitabweichungen zwischen Systemen können zu Problemen führen.
Du brauchst ein servergesteuertes Concurrency-Token im Datensatz, typischerweise version (Integer) oder updated_at (Timestamp). Der Client muss dieses Token beim Laden lesen, unverändert halten und beim Speichern als erwarteten Wert zurücksenden.
Weil der Client nicht zuverlässig ist, um geteilte Daten zu schützen. Der Server muss ein bedingtes Update wie „update where id = X and version = Y“ durchsetzen; sonst kann ein anderer Client, ein Retry oder ein Hintergrundjob immer noch Daten leise überschreiben.
Eine gute Default-Antwort ist ein blockierender Hinweis: „Dieser Datensatz wurde seit dem Öffnen geändert.“ Biete als sichere erste Aktion das Neuladen der aktuellen Daten an. Wenn Nutzer viel getippt haben, bewahre ihre ungespeicherten Eingaben, damit sie nach dem Neuladen wieder angewendet werden können.
Gib eine klare Konfliktantwort (häufig 409) plus genug Kontext, um wiederherzustellen: die aktuelle Server-Version und die neuesten Serverwerte. Wenn möglich, nenne auch, wer wann geändert hat, damit der Nutzer versteht, warum sein Save abgelehnt wurde.
Achte auf fehlende Tokens beim Speichern, Updates, die nur nach id filtern statt nach id + version, und Timestamp-Checks mit niedriger Genauigkeit. Ein weiterer Fehler ist das Überschreiben großer Felder (Notizen, Arrays), was leicht die Änderungen anderer löscht.
In AppMaster füge ein version-Feld im Data Designer hinzu und lade es in den Form-State. Erzwinge dann in einem Business Process ein bedingtes Update, das nur bei passender erwarteter Version schreibt, und behandle die Konflikt-Branch im UI mit einem Reload-/Review-Flow.


