/// <summary>
        /// The algorithm stores metadata about messages that are going to be sent before the claim check entries are stored
        /// in order to prevent accumulation of garbage messages in the claim check. First, the IDs of messages that are about
        /// to be sent are stored in the transaction record with assigned attempt ID and incoming message ID. Then, the message
        /// bodies are actually uploaded to the claim check. Next, the transaction record is committed.
        ///
        /// At any point in time the OutgoingMessages collection of the transaction record may contain messages produced by
        /// processing different incoming messages and different attempts but they don't interfere with each other e.g.
        /// - Message A processing attempt 1 is started, resulting in message records stored as A-1-1 and A-1-2. Then it fails.
        /// - Message B processing attempt 1 is started, resulting in B-1-1 and B-1-2
        /// - Message B processing attempt 1 is finished. No claim check message are removed.
        /// - Message A processing attempt 2 is started, resulting in message records stored as A-2-1.
        /// - Message A processing attempt 2 is finished. Messages A-1-1 and A-1-2 are deleted.
        /// </summary>
        public async Task <ProcessingResult <TResult> > Process <TResult>(string currentMessageId, ITransactionRecordContainer transaction, TContext context,
                                                                          Func <TContext, ITransactionContext, Task <ProcessingResult <TResult> > > invokeMessageHandlers)
        {
            await transaction.Load().ConfigureAwait(false);

            var previousTransactionId = transaction.MessageId;

            if (previousTransactionId != null)
            {
                log.Log($"Unfinished transaction {previousTransactionId} detected. Attempting to complete that transaction.");
                await FinishProcessing(transaction).ConfigureAwait(false);

                if (previousTransactionId == currentMessageId)
                {
                    log.Log($"Duplicate message {currentMessageId} detected. Ignoring.");
                    return(ProcessingResult <TResult> .Duplicate);
                }
            }

            var attemptId = Guid.NewGuid();

            log.Log($"Beginning attempt {attemptId} to process message {currentMessageId}.");

            await transaction.BeginStateTransition().ConfigureAwait(false);

            var result = await invokeMessageHandlers(context, new TransactionContext(attemptId, transaction)).ConfigureAwait(false);

            if (result.IsDuplicate)
            {
                log.Log($"Duplicate message {currentMessageId} detected. Ignoring.");
                return(result);
            }
            log.Log($"Committing transaction for attempt {attemptId} message {currentMessageId}.");

            await transaction.CommitStateTransition(currentMessageId, attemptId).ConfigureAwait(false);

            await FinishProcessing(transaction).ConfigureAwait(false);

            return(result);
        }
 public Task BeginStateTransition()
 {
     return(impl.BeginStateTransition());
 }