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"); }
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(); }
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); }
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}."); } }
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)); }
/// <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(); } }
private static void When_apply_known_but_not_mapped_event_in_state(ISagaInstance sagaInstance) { _gotTiredDomainEvent = new GotTiredEvent(Guid.NewGuid()); sagaInstance.Transit(_gotTiredDomainEvent); }
public int GetHashCode(ISagaInstance <T> message) { return(message.Saga.CorrelationId.GetHashCode()); }
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;
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()); }