Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
rasmus committed Apr 17, 2019
2 parents c54f13a + ea05842 commit 25d4f04
Show file tree
Hide file tree
Showing 7 changed files with 537 additions and 6 deletions.
25 changes: 24 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
### New in 0.70 (not released yet)
### New in 0.71 (not released yet)

* Breaking: Commands published from AggregateSaga which return `false`
in `IExecutionResult.IsSuccess` will newly lead to an exception being thrown.
For disabling all new changes just set protected property
`AggregateSaga.ThrowExceptionsOnFailedPublish` to `false` in your AggregateSaga constructor.
Also an Exception thrown from any command won't prevent other commands from being executed.
All exceptions will be collected and then re-thrown in SagaPublishException (even in case
of just one Exception). The exception structure is following:
- SagaPublishException : AggregateException
- .InnerExceptions
- CommandException : Exception
- .CommandType
- .SourceId
- .InnerException # in case of an exception thrown from the command
- CommandException : Exception
- .CommandType
- .SourceId
- .ExecutionResult # in case of returned `false` in `IExecutionResult.IsSuccess`
You need to update your `ISagaErrorHandler` implementation to reflect new exception structure,
unless you disable this new feature.
* Fix: MongoDB read store no longer throws an exception on non-existing read models (#625)

### New in 0.70.3824 (released 2019-04-11)

* Breaking: Changed target framework to to .NET Framework 4.5.2 for the following NuGet packages,
as Microsoft has [discontinued](https://github.com/Microsoft/dotnet/blob/master/releases/README.md)
Expand Down
7 changes: 6 additions & 1 deletion Source/EventFlow.MongoDB/ReadStores/MongoDbReadModelStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ public async Task<ReadModelEnvelope<TReadModel>> GetAsync(string id, Cancellatio

var collection = _mongoDatabase.GetCollection<TReadModel>(readModelDescription.RootCollectionName.Value);
var filter = Builders<TReadModel>.Filter.Eq(readModel => readModel.Id, id);
var result = await collection.Find(filter).FirstAsync(cancellationToken);
var result = await collection.Find(filter).FirstOrDefaultAsync(cancellationToken);

if(result == null){
return ReadModelEnvelope<TReadModel>.Empty(id);
}

return ReadModelEnvelope<TReadModel>.With(id, result);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
using EventFlow.Aggregates;
using EventFlow.Aggregates.ExecutionResults;
using EventFlow.Commands;
using EventFlow.Exceptions;
using EventFlow.Sagas;
using EventFlow.TestHelpers;
using EventFlow.TestHelpers.Aggregates;
using EventFlow.TestHelpers.Aggregates.Commands;
using EventFlow.TestHelpers.Aggregates.Events;
using EventFlow.TestHelpers.Aggregates.Sagas;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace EventFlow.Tests.UnitTests.Sagas.AggregateSagas
{
[Category(Categories.Unit)]
public class AggregateSagaTests : TestsFor<ThingySaga>
{
private ThingySagaId _thingySagaId;
private Mock<ICommandBus> _commandBus;
private Mock<ISagaContext> _sagaContext;

[SetUp]
public async Task SetUp()
{
_thingySagaId = A<ThingySagaId>();
_commandBus = InjectMock<ICommandBus>();
_sagaContext = InjectMock<ISagaContext>();

await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingySagaStartRequestedEvent>>(),
_sagaContext.Object,
CancellationToken.None);
}

[Test]
public async Task AggregateSaga_PublishAsync_ExecutionResultIsSuccessTrueDoesNotThrow()
{
// Arrange
_commandBus.Setup(
a => a.PublishAsync(
It.IsAny<ICommand<ThingyAggregate, ThingyId, IExecutionResult>>(),
It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(ExecutionResult.Success()));
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
var exception = (Exception)null;

// Act
try
{
await Sut.PublishAsync(
_commandBus.Object,
CancellationToken.None);
}
catch (Exception e)
{
exception = e;
}

// Assert
exception.Should().BeNull();
}

[Test]
public async Task AggregateSaga_PublishAsync_ExecutionResultIsNullDoesNotThrow()
{
// Arrange
_commandBus.Setup(
a => a.PublishAsync(
It.IsAny<ICommand<ThingyAggregate, ThingyId, IExecutionResult>>(),
It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult<IExecutionResult>(null));
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
var exception = (Exception)null;

// Act
try
{
await Sut.PublishAsync(
_commandBus.Object,
CancellationToken.None);
}
catch (Exception e)
{
exception = e;
}

// Assert
exception.Should().BeNull();
}

[Test]
public async Task AggregateSaga_PublishAsync_ExecutionResultIsSuccessFalseDisableThrow()
{
// Arrange
Sut.GetType()
.GetProperty("ThrowExceptionsOnFailedPublish", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty)
.SetValue(Sut, false);
var message = Guid.NewGuid().ToString();
_commandBus.Setup(
a => a.PublishAsync(
It.IsAny<ICommand<ThingyAggregate, ThingyId, IExecutionResult>>(),
It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(ExecutionResult.Failed(message)));
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
var exception = (Exception)null;

// Act
try
{
await Sut.PublishAsync(
_commandBus.Object,
CancellationToken.None);
}
catch (Exception e)
{
exception = e;
}

// Assert
exception.Should().BeNull();
}

[Test]
public async Task AggregateSaga_PublishAsync_ExecutionResultIsSuccessFalseThrows()
{
// Arrange
var message = Guid.NewGuid().ToString();
_commandBus.Setup(
a => a.PublishAsync(
It.IsAny<ICommand<ThingyAggregate, ThingyId, IExecutionResult>>(),
It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(ExecutionResult.Failed(message)));
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
var exception = (Exception)null;

// Act
try
{
await Sut.PublishAsync(
_commandBus.Object,
CancellationToken.None);
}
catch (Exception e)
{
exception = e;
}

// Assert
exception.Should().NotBeNull();
exception.Should().BeAssignableTo<SagaPublishException>();
var sagaPublishException = exception as SagaPublishException;
sagaPublishException.InnerExceptions.Count.Should().Be(1);
var innerException = sagaPublishException.InnerExceptions[0];
innerException.Should().BeAssignableTo<CommandException>();
var commandException = innerException as CommandException;
commandException.Message.Should().Contain(message);
commandException.Message.Should().Contain(Sut.Id.ToString());
commandException.CommandType.Should().Be(typeof(ThingyAddMessageCommand));
commandException.ExecutionResult.Should().NotBeNull();
commandException.ExecutionResult.IsSuccess.Should().BeFalse();
commandException.ExecutionResult.ToString().Should().Contain(message);
}

[Test]
public async Task AggregateSaga_PublishAsync_ExecutionResultIsSuccessFalseThrowsTwice()
{
// Arrange
var message = Guid.NewGuid().ToString();
_commandBus.Setup(
a => a.PublishAsync(
It.IsAny<ICommand<ThingyAggregate, ThingyId, IExecutionResult>>(),
It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(ExecutionResult.Failed(message)));
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
var exception = (Exception)null;

// Act
try
{
await Sut.PublishAsync(
_commandBus.Object,
CancellationToken.None);
}
catch (Exception e)
{
exception = e;
}

// Assert
exception.Should().NotBeNull();
exception.Should().BeAssignableTo<SagaPublishException>();
var sagaPublishException = exception as SagaPublishException;
sagaPublishException.InnerExceptions.Count.Should().Be(2);
foreach (var innerException in sagaPublishException.InnerExceptions)
{
innerException.Should().BeAssignableTo<CommandException>();
var commandException = innerException as CommandException;
commandException.Message.Should().Contain(message);
commandException.Message.Should().Contain(Sut.Id.ToString());
commandException.CommandType.Should().Be(typeof(ThingyAddMessageCommand));
commandException.ExecutionResult.Should().NotBeNull();
commandException.ExecutionResult.IsSuccess.Should().BeFalse();
commandException.ExecutionResult.ToString().Should().Contain(message);
}
}

[Test]
public async Task AggregateSaga_PublishAsync_ExceptionIsWrapped()
{
// Arrange
var message = Guid.NewGuid().ToString();
_commandBus.Setup(
a => a.PublishAsync(
It.IsAny<ICommand<ThingyAggregate, ThingyId, IExecutionResult>>(),
It.IsAny<CancellationToken>()))
.Callback(() => throw new ApplicationException(message));
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
var exception = (Exception)null;

// Act
try
{
await Sut.PublishAsync(
_commandBus.Object,
CancellationToken.None);
}
catch (Exception e)
{
exception = e;
}

// Assert
exception.Should().NotBeNull();
exception.Should().BeAssignableTo<SagaPublishException>();
var sagaPublishException = exception as SagaPublishException;
sagaPublishException.InnerExceptions.Count.Should().Be(1);
var innerException = sagaPublishException.InnerExceptions[0];
innerException.Should().BeAssignableTo<CommandException>();
var commandException = innerException as CommandException;
commandException.Message.Should().Contain(message);
commandException.Message.Should().Contain(Sut.Id.ToString());
commandException.CommandType.Should().Be(typeof(ThingyAddMessageCommand));
commandException.InnerException.Should().NotBeNull();
commandException.InnerException.Should().BeAssignableTo<ApplicationException>();
commandException.InnerException.Message.Should().Be(message);
}

[Test]
public async Task AggregateSaga_PublishAsync_TwoExceptionsAreWrapped()
{
// Arrange
var message = Guid.NewGuid().ToString();
_commandBus.Setup(
a => a.PublishAsync(
It.IsAny<ICommand<ThingyAggregate, ThingyId, IExecutionResult>>(),
It.IsAny<CancellationToken>()))
.Callback(() => throw new ApplicationException(message));
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
await Sut.HandleAsync(
A<DomainEvent<ThingyAggregate, ThingyId, ThingyPingEvent>>(),
_sagaContext.Object,
CancellationToken.None);
var exception = (Exception)null;

// Act
try
{
await Sut.PublishAsync(
_commandBus.Object,
CancellationToken.None);
}
catch (Exception e)
{
exception = e;
}

// Assert
exception.Should().NotBeNull();
exception.Should().BeAssignableTo<SagaPublishException>();
var sagaPublishException = exception as SagaPublishException;
sagaPublishException.InnerExceptions.Count.Should().Be(2);
foreach (var innerException in sagaPublishException.InnerExceptions)
{
innerException.Should().BeAssignableTo<CommandException>();
var commandException = innerException as CommandException;
commandException.Message.Should().Contain(message);
commandException.Message.Should().Contain(Sut.Id.ToString());
commandException.CommandType.Should().Be(typeof(ThingyAddMessageCommand));
commandException.InnerException.Should().NotBeNull();
commandException.InnerException.Should().BeAssignableTo<ApplicationException>();
commandException.InnerException.Message.Should().Be(message);
}
}
}
}
Loading

0 comments on commit 25d4f04

Please sign in to comment.