Example #1
0
        public async Task Should_handle_in_other_order()
        {
            var reservationId = NewId.NextGuid();
            var bookId        = NewId.NextGuid();
            var memberId      = NewId.NextGuid();

            await TestHarness.Bus.Publish <BookCheckedOut>(new
            {
                CheckOutId = InVar.Id,
                bookId,
                memberId,
                InVar.Timestamp
            });

            Assert.IsTrue(await SagaHarness.Consumed.Any <BookCheckedOut>(x => x.Context.Message.BookId == bookId), "Message not consumed by saga");

            Assert.That(await SagaHarness.Created.Any(x => x.Saga.BookId == bookId), "Saga not created");

            ISagaInstance <ThankYou> instance = SagaHarness.Created.Select(x => x.Saga.BookId == bookId).First();

            await TestHarness.Bus.Publish <BookReserved>(new
            {
                bookId,
                memberId,
                reservationId,
                Duration = TimeSpan.FromDays(14),
                InVar.Timestamp
            });

            Assert.IsTrue(await SagaHarness.Consumed.Any <BookReserved>(x => x.Context.Message.BookId == bookId), "Message not consumed by saga");

            Guid?existsId = await SagaHarness.Exists(instance.Saga.CorrelationId, x => x.Ready);

            Assert.IsTrue(existsId.HasValue, "Saga did not transition to Ready");
        }
Example #2
0
        protected async Task TransitionState(ISagaInstance instance, int expectedVersion, SagaMessageContext context)
        {
            instance.ValidateChanges();

            var logName     = instance.Metadata.SagaType.Name;
            var persistence = Configuration.Persistence.Map(logName);
            var commit      = new Commit()
            {
                Id      = instance.SagaId,
                Version = expectedVersion + 1,
                Saga    = instance,
            };

            foreach (var @event in instance.UncommittedEvents)
            {
                commit.Messages.Add(@event.GetDescriptor());
                commit.Events.Add(@event);
            }

            foreach (var message in context.Messages)
            {
                commit.Messages.Add(message);
            }

            await persistence.Persist(commit, context.CancellationToken).ConfigureAwait(false);

            instance.AcceptChanges();
            instance.Update();
        }
Example #3
0
 Task <SagaSearchResult> ISagaInstanceActivator.GetData(ISagaInstance instance, IStoreSagaData store, object message, CancellationToken cancellationToken)
 {
     Contract.Requires <ArgumentNullException>(instance != null, nameof(instance));
     Contract.Requires <ArgumentNullException>(store != null, nameof(store));
     Contract.Requires <ArgumentNullException>(message != null, nameof(message));
     Contract.Ensures(Contract.Result <Task <SagaSearchResult> >() != null);
     return(null);
 }
Example #4
0
        private T ExpectCommand <T>(ISagaInstance saga) where T : class, ICommand
        {
            var cmd = saga.CommandsToDispatch.FirstOrDefault();

            Assert.NotNull(cmd, $"Cannot find expected {typeof (T).Name} in dispatched messages");
            var typedCommand = cmd as T;

            Assert.NotNull(cmd, $"Dispatched message has wrong type: {cmd.GetType().Name}. Expected: {typeof (T).Name}");
            saga.ClearCommandsToDispatch();
            return(typedCommand);
        }
        /// <summary>
        /// Asserts whether the saga instance is in the expected state.
        /// </summary>
        private async Task AssertInstanceState(Guid correlationId, State expectedState)
        {
            // Wait for the instance to transition to the expected state
            ISagaInstance <LifeState> sagaInstance = await FindSagaInstance(correlationId, expectedState);

            if (sagaInstance == null)
            {
                // Instance is not in the correct state, fetch it anyway
                sagaInstance = _sagaHarness.Sagas.FirstOrDefault(i => i.Saga.CorrelationId == correlationId);
                Assert.Fail($"Saga instance {correlationId} was expected to be in {expectedState} state, but it is in {sagaInstance.Saga.CurrentState}.");
            }
        }
Example #6
0
        Task AttachData(ISagaInstance instance, SagaSearchResult searchResult, object message, CancellationToken cancellationToken)
        {
            Contract.Requires(instance != null);
            Contract.Requires(searchResult != null);
            Contract.Requires(message != null);
            Contract.Ensures(Contract.Result <Task>() != null);

            var data = searchResult.Data;

            if (data != null)
            {
                instance.AttachExisting(data);
                return(CompletedTask);
            }

            var metadata    = instance.Metadata;
            var messageType = message.GetType();

            if (!metadata.CanStartSaga(messageType.FullName))
            {
                instance.MarkAsNotFound();
                return(CompletedTask);
            }

            try
            {
                data = (ISagaData)Activator.CreateInstance(metadata.SagaDataType);
            }
            catch (MissingMemberException)
            {
                data = (ISagaData)FormatterServices.GetUninitializedObject(metadata.SagaDataType);
            }

            var property = metadata.CorrelationProperty;

            if (property != null && searchResult.Properties.TryGetValue(property.Name, out object value))
            {
                property.SetValue(data, value);
            }

            if (data.Id == default(Guid))
            {
                data.Id = configuration.UniqueIdGenerator.NewId();
            }

            instance.AttachNew(data);
            return(storage.Store(data, instance.CorrelationProperty, cancellationToken));
        }
Example #7
0
        /// <summary>
        /// Transitions the state of a saga.
        /// </summary>
        /// <param name="transaction">The current <see cref="DbTransaction">database transaction</see>.</param>
        /// <param name="saga">The <see cref="ISagaInstance">saga instance</see> to perform a state transition on.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken">token</see> that can be used to cancel the operation.</param>
        /// <returns>A <see cref="Task">task</see> representing the asynchronous operation.</returns>
        /// <remarks>If the <paramref name="saga"/> is <c>null</c>, no action is performed.</remarks>
        protected virtual async Task TransitionState(DbTransaction transaction, ISagaInstance saga, CancellationToken cancellationToken)
        {
            Arg.NotNull(transaction, nameof(transaction));

            if (saga == null)
            {
                return;
            }

            var connection = transaction.Connection;

            if (saga.Completed)
            {
                if (!saga.IsNew)
                {
                    using (var command = Configuration.Sagas.NewCompleteCommand(saga.Data))
                    {
                        command.Connection  = connection;
                        command.Transaction = transaction;
                        await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
                    }
                }

                saga.Complete();
            }
            else
            {
                using (var stream = Configuration.Sagas.Serialize(saga.Data))
                    using (var command = Configuration.Sagas.NewStoreCommand(saga.Data, saga.CorrelationProperty, stream))
                    {
                        command.Connection  = connection;
                        command.Transaction = transaction;
                        await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
                    }

                saga.Update();
            }
        }
        /// <summary>
        /// Transitions the state of a saga.
        /// </summary>
        /// <param name="saga">The <see cref="ISagaInstance">saga instance</see> to perform a state transition on.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken">token</see> that can be used to cancel the operation.</param>
        /// <returns>A <see cref="Task">task</see> representing the asynchronous operation.</returns>
        /// <remarks>If the <paramref name="saga"/> is <c>null</c>, no action is performed.</remarks>
        protected virtual async Task TransitionState(ISagaInstance saga, CancellationToken cancellationToken)
        {
            if (saga == null)
            {
                return;
            }

            if (saga.Completed)
            {
                if (!saga.IsNew)
                {
                    await SagaStorage.Complete(saga.Data, cancellationToken).ConfigureAwait(false);
                }

                saga.Complete();
            }
            else
            {
                await SagaStorage.Store(saga.Data, saga.CorrelationProperty, cancellationToken).ConfigureAwait(false);

                saga.Update();
            }
        }
Example #9
0
 private static void When_apply_known_but_not_mapped_event_in_state(ISagaInstance sagaInstance)
 {
     _gotTiredDomainEvent = new GotTiredEvent(Guid.NewGuid());
     sagaInstance.Transit(_gotTiredDomainEvent);
 }
Example #10
0
 public int GetHashCode(ISagaInstance <T> message)
 {
     return(message.Saga.CorrelationId.GetHashCode());
 }
Example #11
0
 public bool Equals(ISagaInstance <T> x, ISagaInstance <T> y)
 {
     return(Equals(x.Saga.CorrelationId, y.Saga.CorrelationId));
 }
 protected override Task TransitionState(DbTransaction transaction, ISagaInstance saga, CancellationToken cancellationToken) => CompletedTask;
Example #13
0
 private static void When_execute_valid_transaction <T>(ISagaInstance sagaInstance, T e = null) where T : DomainEvent
 {
     sagaInstance.Transit(e);
 }
 private static void When_execute_invalid_transaction(ISagaInstance sagaInstance)
 {
     sagaInstance.Transit(new WrongMessage());
 }