Skip to content

Editing conflict

Use this when the client loaded a document, someone else may have changed it, and a save must not overwrite that change.

RuleA rename is rejected if the document changed after the editor loaded it
MechanismAn explicit scope built from the revision the client sent, returned with EventsWithConcurrencyScopes
AlternativesA read model parameter when the conflict only matters within one request
Notconcurrency: true alone: it reads the tail after Handle(), so it cannot carry the revision the user saw
using Cratis.Arc.Commands.ModelBound;
using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
using Cratis.Chronicle.EventSequences.Concurrency;
public record DocumentId(string Value) : EventSourceId(Value);
[EventType]
public record DocumentTitleChanged(string Title);
[Command]
public record RenameDocument(DocumentId Document, string Title, ulong ExpectedRevision)
{
public EventsWithConcurrencyScopes Handle() => new(
[new(Document, new DocumentTitleChanged(Title))],
[new(Document, new ConcurrencyScope(
new EventSequenceNumber(ExpectedRevision),
EventSourceId: Document,
EventTypes: [typeof(DocumentTitleChanged).GetEventType()]))]);
}
guard document: types=[DocumentTitleChanged], after <ExpectedRevision>

The scope fails the append if any DocumentTitleChanged for this document has a sequence number above ExpectedRevision. How the client learns the revision is part of your read model and query.

If the command also reads a model of the same document, the scope must cover what the read watches and must not have been captured after it. Otherwise the command is refused with ExplicitScopeDoesNotCoverReads. The scope above covers a read of DocumentTitleChanged events. A read model that folds more types than the scope names needs a wider scope. ARCCHR0036 reports it at build time.