Skip to main content
Table of Contents

Interface ICommandIntentStore

Namespace
Stratara.Abstractions.Outbox
Assembly
Stratara.Abstractions.dll

Where an execution model that records every accepted command keeps the bookkeeping of its resume: the command with the aggregate it names and whether it is heavy, how often it has been handed over, how many of its attempts ended in a concurrency conflict, when it was last handed over, the failure of its last attempt, and whether it has been kept for an operator. The resume is bounded only as far as an implementation persists these; an execution model that requires this port fails at start when none is registered, rather than resuming without a bound.

public interface ICommandIntentStore
Extension Methods

Methods

ClaimAsync(IReadOnlyList<RecordedIntent>, DateTimeOffset, CancellationToken)

Claims the hand-over of several due commands read together: for each, what TryClaimAsync(Guid, DateTimeOffset?, DateTimeOffset, CancellationToken) does, and returns the ones this call claimed.

Task<IReadOnlyList<Guid>> ClaimAsync(IReadOnlyList<RecordedIntent> due, DateTimeOffset now, CancellationToken cancellationToken)

Parameters

due IReadOnlyList<RecordedIntent>

The due commands, as GetDueAsync(DateTimeOffset, int, CancellationToken) returned them.

now DateTimeOffset

The time of this hand-over.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task<IReadOnlyList<Guid>>

The identities of the commands this call claimed.

Remarks

The default claims one command at a time through TryClaimAsync(Guid, DateTimeOffset?, DateTimeOffset, CancellationToken), one round trip each. A store that overrides it claims the batch in a number of round trips that does not grow with the batch, so that a drain resuming a backlog pays per pass, not per command.

GetDueAsync(DateTimeOffset, int, CancellationToken)

Returns the recorded commands due for resumption, oldest first, commands recorded at the same time in the order of their identity: not kept, recorded at or before handedOverBefore, and not handed over since then.

Task<IReadOnlyList<RecordedIntent>> GetDueAsync(DateTimeOffset handedOverBefore, int batchSize, CancellationToken cancellationToken)

Parameters

handedOverBefore DateTimeOffset

The moment a command's last hand-over must precede for it to be due.

batchSize int

The maximum number of commands to return.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task<IReadOnlyList<RecordedIntent>>

The due commands, or an empty list.

KeepAsync(Guid, DateTimeOffset, CancellationToken)

Keeps the command for an operator; a kept command is not due until an operator returns it.

Task KeepAsync(Guid intentId, DateTimeOffset now, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

now DateTimeOffset

The time it was kept.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task

A task that completes when the command is kept.

RecordAsync(Guid, CommandEnvelope, Guid?, bool, DateTimeOffset, CancellationToken)

Records an accepted command durably, with the aggregate it names, whether it is heavy, and the time its dispatch began, which orders it among the commands due with it.

Task RecordAsync(Guid intentId, CommandEnvelope envelope, Guid? aggregateId, bool heavy, DateTimeOffset recordedAt, CancellationToken cancellationToken)

Parameters

intentId Guid

The identity of the recorded command.

envelope CommandEnvelope

The serialised command and the session it was dispatched under.

aggregateId Guid?

The aggregate the command names, or null for a command that names none.

heavy bool

Whether the command declared itself long-running.

recordedAt DateTimeOffset

When the dispatch began, taken before anything was awaited; a later dispatch of the same scope to the same aggregate carries a later time, however long either takes to be recorded.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task

A task that completes when the command is durable.

Remarks

A store that overrides it counts the hand-over of the dispatch that follows the record as the command's first attempt, as a bus message's first delivery counts, so a record whose host died before that hand-over has used one attempt. The default records through the overload without recordedAt, so a store that does not override it orders commands by the time it stamps them itself and counts from zero, as before.

RecordAsync(Guid, CommandEnvelope, Guid?, bool, CancellationToken)

Records an accepted command durably, with the aggregate it names and whether it is heavy.

Task RecordAsync(Guid intentId, CommandEnvelope envelope, Guid? aggregateId, bool heavy, CancellationToken cancellationToken)

Parameters

intentId Guid

The identity of the recorded command.

envelope CommandEnvelope

The serialised command and the session it was dispatched under.

aggregateId Guid?

The aggregate the command names, or null for a command that names none.

heavy bool

Whether the command declared itself long-running.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task

A task that completes when the command is durable.

RecordConflictAsync(Guid, string, CancellationToken)

Records that the command's latest attempt ended in a concurrency conflict: records the failure, counts one conflict, and gives back the attempt the hand-over counted, never below zero — so that the delivery bound measures failures other than conflicts, and the conflict bound measures conflicts.

Task RecordConflictAsync(Guid intentId, string failure, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

failure string

A description of the conflict.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task

A task that completes when the conflict is durable.

Remarks

The default records the conflict through RecordFailureAsync(Guid, string, CancellationToken), so a store that does not override it counts a conflict against the delivery bound like any other failure.

Exceptions

ArgumentNullException

failure is null.

RecordFailureAsync(Guid, string, CancellationToken)

Records the failure of the command's latest attempt.

Task RecordFailureAsync(Guid intentId, string failure, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

failure string

A description of the failure.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task

A task that completes when the failure is durable.

RenewAsync(Guid, DateTimeOffset, CancellationToken)

Renews the hand-over of a command whose handler is running, so it does not become due again while it runs.

Task RenewAsync(Guid intentId, DateTimeOffset now, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

now DateTimeOffset

The time of the renewal.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task

A task that completes when the renewal is durable.

ReturnAttemptAsync(Guid, CancellationToken)

Gives back the attempt the command's latest hand-over counted, never below zero, because its handler was stopped with its silo rather than failing.

Task ReturnAttemptAsync(Guid intentId, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task

A task that completes when the attempt is given back.

Remarks

The default gives nothing back, so a store that does not override it counts the stop as an attempt.

TryClaimAsync(Guid, DateTimeOffset?, DateTimeOffset, CancellationToken)

Claims a hand-over of a due command: stamps now as its last hand-over and increments its attempt count, only if it is not kept and its last hand-over is still expectedLastHandedOverAt.

Task<bool> TryClaimAsync(Guid intentId, DateTimeOffset? expectedLastHandedOverAt, DateTimeOffset now, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

expectedLastHandedOverAt DateTimeOffset?

The last hand-over read with the command, or null for one never handed over.

now DateTimeOffset

The time of this hand-over.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task<bool>

true when this call claimed the hand-over.

TryRenewAsync(Guid, DateTimeOffset, CancellationToken)

Renews the hand-over of a command that is still recorded and not kept, and reports whether there was one: a late hand-over that finds the command gone — it completed through another run — or kept is not run.

Task<bool> TryRenewAsync(Guid intentId, DateTimeOffset now, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

now DateTimeOffset

The time of the renewal.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task<bool>

true when a recorded, not kept command was renewed.

Remarks

The default renews through RenewAsync(Guid, DateTimeOffset, CancellationToken) and returns true, so a store that does not override it runs every such hand-over.

TryRenewFromAsync(Guid, DateTimeOffset, DateTimeOffset, CancellationToken)

Takes over a resumed hand-over: renews it only if the command is not kept and its last hand-over is still claimedAt, the stamp of the claim that handed it over, and moves that stamp to now or, where now is not later, one millisecond past claimedAt. A second hand-over of the same claim therefore finds the stamp moved, and a hand-over of a command that completed finds no record.

Task<bool> TryRenewFromAsync(Guid intentId, DateTimeOffset claimedAt, DateTimeOffset now, CancellationToken cancellationToken)

Parameters

intentId Guid

The recorded command.

claimedAt DateTimeOffset

The last hand-over the claim stamped, as the hand-over carries it.

now DateTimeOffset

The time of the renewal.

cancellationToken CancellationToken

Cancels the operation.

Returns

Task<bool>

true when this call took the hand-over over; false when another runner already did or the command is gone, and the hand-over is to be dropped without running the handler.

Remarks

The default renews through RenewAsync(Guid, DateTimeOffset, CancellationToken) and returns true, so a store that does not override it runs every hand-over it receives.