Skip to content

Commit 49c6951

Browse files
Daniel Marbachtimbussmann
andauthored
Implement TransactionalSession as part of the persistence (#353)
* Update to Core 7.8 beta * Make the session a first class citizen * Drop no longer required target framework * Make sure the persistence assembly is loaded * Remove dotsettings * Tweak readme * prevent exceptions when calling dispose multiple times (#347) (cherry picked from commit 357e7ae) * Add test to verify synchronized storage integration works * Switch to the stable version * backport transaction session support * add missing di test for tx session acceptance tests * Align the namespace (#359) * Align session option name * update to tx session rtm package Co-authored-by: Tim Bussmann <timbussmann@users.noreply.github.com>
1 parent bf4a903 commit 49c6951

File tree

50 files changed

+1139
-661
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1139
-661
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ jobs:
2828
- name: Setup .NET SDK
2929
uses: actions/setup-dotnet@v2.1.0
3030
with:
31-
dotnet-version: |
32-
6.0.x
33-
3.1.x
31+
dotnet-version: 6.0.x
3432
- name: Build
3533
run: dotnet build src --configuration Release
3634
- name: Upload packages

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ For developers using Docker containers, the following docker command will quickl
4949

5050
Once started, initialize the replication set (required for transaction support) by connecting to the database using a mongo shell. You can connect directly from your local machine using `mongo.exe` or use the following docker command to start a mongo shell inside the container and initialize the replication set:
5151

52-
`docker exec -it TestMongoDB mongo --eval 'rs.initiate()'`
52+
`docker exec -it TestMongoDB mongosh --eval 'rs.initiate()'`

src/NServiceBus.Storage.MongoDB.AcceptanceTests/ConfigureEndpointMongoPersistence.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Threading.Tasks;
34
using MongoDB.Driver;
5+
using MongoDB.Driver.Core.Events;
46
using NServiceBus;
57
using NServiceBus.AcceptanceTesting.Support;
8+
using NServiceBus.Configuration.AdvancedExtensibility;
69

710
class ConfigureEndpointMongoPersistence : IConfigureEndpointTestExecution
811
{
9-
const string databaseName = "AcceptanceTests";
12+
public const string DatabaseName = "AcceptanceTests";
13+
public const string InterceptedCommands = "MongoDB.AcceptanceTests.InterceptedCommands";
1014
IMongoClient client;
1115

1216
public Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings settings, PublisherMetadata publisherMetadata)
1317
{
1418
var containerConnectionString = Environment.GetEnvironmentVariable("NServiceBusStorageMongoDB_ConnectionString");
1519

16-
client = string.IsNullOrWhiteSpace(containerConnectionString) ? new MongoClient() : new MongoClient(containerConnectionString);
20+
var commands = new ConcurrentQueue<string>();
21+
configuration.GetSettings().Set(InterceptedCommands, commands);
1722

18-
configuration.UsePersistence<MongoPersistence>().MongoClient(client).DatabaseName(databaseName);
23+
var mongoClientSettings = string.IsNullOrWhiteSpace(containerConnectionString)
24+
? new MongoClientSettings()
25+
: MongoClientSettings.FromConnectionString(containerConnectionString);
26+
mongoClientSettings.ClusterConfigurator = cb =>
27+
{
28+
cb.Subscribe<CommandSucceededEvent>(commandSucceededEvent =>
29+
{
30+
commands.Enqueue($"{commandSucceededEvent.RequestId}-{commandSucceededEvent.CommandName.ToUpper()}");
31+
});
32+
};
33+
34+
client = new MongoClient(mongoClientSettings);
35+
36+
configuration.UsePersistence<MongoPersistence>().MongoClient(client).DatabaseName(DatabaseName);
1937

2038
return Task.FromResult(0);
2139
}
@@ -24,7 +42,7 @@ public async Task Cleanup()
2442
{
2543
try
2644
{
27-
await client.DropDatabaseAsync(databaseName);
45+
await client.DropDatabaseAsync(DatabaseName);
2846
}
2947
// ReSharper disable once EmptyGeneralCatchClause
3048
catch (Exception)

src/NServiceBus.Storage.MongoDB.AcceptanceTests/NServiceBus.Storage.MongoDB.AcceptanceTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net461;netcoreapp3.1;net6.0</TargetFrameworks>
4+
<TargetFrameworks>net461;net6.0</TargetFrameworks>
55
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
66
<NoWarn>$(NoWarn);SYSLIB0021</NoWarn>
77
</PropertyGroup>
@@ -12,7 +12,7 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
15-
<PackageReference Include="NServiceBus.AcceptanceTests.Sources" Version="7.5.0" />
15+
<PackageReference Include="NServiceBus.AcceptanceTests.Sources" Version="7.8.0" />
1616
<PackageReference Include="NUnit" Version="3.12.0" />
1717
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
1818
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using NServiceBus;
2+
using NUnit.Framework;
3+
4+
[SetUpFixture]
5+
public class TestSetup
6+
{
7+
[OneTimeSetUp]
8+
public void SetUp() =>
9+
// ensure the persistence assembly is loaded into the AppDomain because it needs its features to be scanned to work properly.
10+
typeof(MongoPersistence).ToString();
11+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
namespace NServiceBus.AcceptanceTests
2+
{
3+
using System.Threading.Tasks;
4+
using AcceptanceTesting;
5+
using EndpointTemplates;
6+
using NUnit.Framework;
7+
using Storage.MongoDB;
8+
9+
[TestFixture]
10+
public partial class When_using_outbox_synchronized_session_via_container : NServiceBusAcceptanceTest
11+
{
12+
[Test]
13+
public async Task Should_inject_synchronized_session_into_handler()
14+
{
15+
var context = await Scenario.Define<Context>()
16+
.WithEndpoint<Endpoint>(b => b.When(s => s.SendLocal(new MyMessage())))
17+
.Done(c => c.Done)
18+
.Run()
19+
.ConfigureAwait(false);
20+
21+
Assert.True(context.RepositoryHasMongoSession);
22+
AssertPartitionPart(context);
23+
}
24+
25+
partial void AssertPartitionPart(Context scenarioContext);
26+
27+
public class Context : ScenarioContext
28+
{
29+
public bool Done { get; set; }
30+
public bool RepositoryHasMongoSession { get; set; }
31+
}
32+
33+
public class Endpoint : EndpointConfigurationBuilder
34+
{
35+
public Endpoint()
36+
{
37+
EndpointSetup<DefaultServer>(config =>
38+
{
39+
config.ConfigureTransport().Transactions(TransportTransactionMode.ReceiveOnly);
40+
41+
config.EnableOutbox();
42+
config.RegisterComponents(c =>
43+
{
44+
c.ConfigureComponent<MyRepository>(DependencyLifecycle.InstancePerUnitOfWork);
45+
});
46+
});
47+
}
48+
49+
public class MyMessageHandler : IHandleMessages<MyMessage>
50+
{
51+
public MyMessageHandler(MyRepository repository, Context context)
52+
{
53+
this.context = context;
54+
this.repository = repository;
55+
}
56+
57+
58+
public Task Handle(MyMessage message, IMessageHandlerContext handlerContext)
59+
{
60+
repository.DoSomething();
61+
context.Done = true;
62+
return Task.CompletedTask;
63+
}
64+
65+
Context context;
66+
MyRepository repository;
67+
}
68+
}
69+
70+
public class MyRepository
71+
{
72+
public MyRepository(IMongoSynchronizedStorageSession storageSession, Context context)
73+
{
74+
this.storageSession = storageSession;
75+
this.context = context;
76+
}
77+
78+
public void DoSomething() => context.RepositoryHasMongoSession = storageSession.MongoSession != null;
79+
80+
IMongoSynchronizedStorageSession storageSession;
81+
Context context;
82+
}
83+
84+
public class MyMessage : IMessage
85+
{
86+
public string Property { get; set; }
87+
}
88+
}
89+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
namespace NServiceBus.AcceptanceTests
2+
{
3+
using System.Threading.Tasks;
4+
using AcceptanceTesting;
5+
using EndpointTemplates;
6+
using NUnit.Framework;
7+
using Storage.MongoDB;
8+
9+
[TestFixture]
10+
public class When_using_synchronized_session_via_container : NServiceBusAcceptanceTest
11+
{
12+
[Test]
13+
public async Task Should_inject_synchronized_session_into_handler()
14+
{
15+
var context = await Scenario.Define<Context>()
16+
.WithEndpoint<Endpoint>(b => b.When(s => s.SendLocal(new MyMessage())))
17+
.Done(c => c.Done)
18+
.Run()
19+
.ConfigureAwait(false);
20+
21+
Assert.True(context.HandlerHasMongoSession);
22+
}
23+
24+
public class Context : ScenarioContext
25+
{
26+
public bool Done { get; set; }
27+
public bool HandlerHasMongoSession { get; set; }
28+
}
29+
30+
public class Endpoint : EndpointConfigurationBuilder
31+
{
32+
public Endpoint() => EndpointSetup<DefaultServer>();
33+
34+
public class MyHandler : IHandleMessages<MyMessage>
35+
{
36+
public MyHandler(IMongoSynchronizedStorageSession session, Context context)
37+
{
38+
this.session = session;
39+
this.context = context;
40+
}
41+
42+
public Task Handle(MyMessage message, IMessageHandlerContext handlerContext)
43+
{
44+
context.Done = true;
45+
context.HandlerHasMongoSession = session.MongoSession != null;
46+
47+
return Task.CompletedTask;
48+
}
49+
50+
Context context;
51+
IMongoSynchronizedStorageSession session;
52+
}
53+
}
54+
55+
public class MyMessage : IMessage
56+
{
57+
public string Property { get; set; }
58+
}
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
namespace NServiceBus.AcceptanceTests
2+
{
3+
using System.Collections.Concurrent;
4+
using System.Threading.Tasks;
5+
using AcceptanceTesting;
6+
using Configuration.AdvancedExtensibility;
7+
using EndpointTemplates;
8+
using NUnit.Framework;
9+
using MongoDB.Bson;
10+
using MongoDB.Bson.Serialization.Attributes;
11+
using Storage.MongoDB;
12+
13+
[TestFixture]
14+
public class When_using_synchronized_session_via_container_and_storage_session_extension : NServiceBusAcceptanceTest
15+
{
16+
[Test]
17+
public async Task Should_commit_all_operations_using_the_same_batch()
18+
{
19+
var context = await Scenario.Define<Context>()
20+
.WithEndpoint<Endpoint>(b =>
21+
{
22+
b.CustomConfig((cfg, ctx) =>
23+
{
24+
ctx.InterceptedCommands = cfg.GetSettings()
25+
.Get<ConcurrentQueue<string>>(ConfigureEndpointMongoPersistence.InterceptedCommands);
26+
});
27+
b.When(s => s.SendLocal(new MyMessage()));
28+
})
29+
.Done(c => c.FirstHandlerIsDone && c.SecondHandlerIsDone)
30+
.Run()
31+
.ConfigureAwait(false);
32+
33+
Assert.That(context.InterceptedCommands, Has.One.Items.Match("COMMITTRANSACTION"));
34+
}
35+
36+
public class Context : ScenarioContext
37+
{
38+
public bool FirstHandlerIsDone { get; set; }
39+
public bool SecondHandlerIsDone { get; set; }
40+
public ConcurrentQueue<string> InterceptedCommands { get; set; }
41+
}
42+
43+
public class Endpoint : EndpointConfigurationBuilder
44+
{
45+
public Endpoint() => EndpointSetup<DefaultServer>();
46+
47+
public class MyHandlerUsingStorageSession : IHandleMessages<MyMessage>
48+
{
49+
public MyHandlerUsingStorageSession(IMongoSynchronizedStorageSession session, Context context)
50+
{
51+
this.session = session;
52+
this.context = context;
53+
}
54+
55+
public async Task Handle(MyMessage message, IMessageHandlerContext handlerContext)
56+
{
57+
var entity = new MyEntity
58+
{
59+
Id = ObjectId.GenerateNewId(),
60+
Data = "MyCustomData"
61+
};
62+
var collection = session.MongoSession.Client.GetDatabase(ConfigureEndpointMongoPersistence.DatabaseName).GetCollection<MyEntity>("myentity");
63+
await collection.InsertOneAsync(session.MongoSession, entity);
64+
context.FirstHandlerIsDone = true;
65+
}
66+
67+
Context context;
68+
IMongoSynchronizedStorageSession session;
69+
}
70+
71+
public class MyHandlerUsingExtensionMethod : IHandleMessages<MyMessage>
72+
{
73+
public MyHandlerUsingExtensionMethod(Context context)
74+
{
75+
this.context = context;
76+
}
77+
78+
public async Task Handle(MyMessage message, IMessageHandlerContext handlerContext)
79+
{
80+
var session = handlerContext.SynchronizedStorageSession.MongoPersistenceSession();
81+
82+
var entity = new MyEntity
83+
{
84+
Id = ObjectId.GenerateNewId(),
85+
Data = "MyCustomData"
86+
};
87+
var collection = session.MongoSession.Client.GetDatabase(ConfigureEndpointMongoPersistence.DatabaseName).GetCollection<MyEntity>("myentity");
88+
await collection.InsertOneAsync(session.MongoSession, entity);
89+
context.SecondHandlerIsDone = true;
90+
}
91+
92+
Context context;
93+
}
94+
}
95+
96+
public class MyEntity
97+
{
98+
[BsonId]
99+
public ObjectId Id { get; set; }
100+
public string Data { get; set; }
101+
}
102+
103+
public class MyMessage : IMessage
104+
{
105+
public string Property { get; set; }
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)