Skip to content

Commit

Permalink
Add mongo session provider support (#89)
Browse files Browse the repository at this point in the history
Add session provider

Co-authored-by: Normen Scheiber <nscheibe@live.com>
  • Loading branch information
glucaci and nscheibe committed Jan 19, 2024
1 parent 2366108 commit 9fc7ca2
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## [![Nuget](https://img.shields.io/nuget/v/MongoDB.Extensions.Context.svg?style=flat)](https://www.nuget.org/packages/MongoDB.Extensions.Context) [![GitHub Release](https://img.shields.io/github/release/SwissLife-OSS/mongo-extensions.svg?style=flat)](https://github.com/SwissLife-OSS/Mongo-extensions/releases/latest) [![Build Status](https://dev.azure.com/swisslife-oss/swisslife-oss/_apis/build/status/MongoDB.Extensions.Release?branchName=master)](https://dev.azure.com/swisslife-oss/swisslife-oss/_build/latest?definitionId=11&branchName=master)
## [![Nuget](https://img.shields.io/nuget/v/MongoDB.Extensions.Context.svg?style=flat)](https://www.nuget.org/packages/MongoDB.Extensions.Context) [![GitHub Release](https://img.shields.io/github/release/SwissLife-OSS/mongo-extensions.svg?style=flat)](https://github.com/SwissLife-OSS/Mongo-extensions/releases/latest) [![Build Status](https://github.com/SwissLife-OSS/mongo-extensions/actions/workflows/release.yml/badge.svg)](https://github.com/SwissLife-OSS/mongo-extensions/actions/workflows/release.yml)

**MongoDB.Extensions provides a set of utility libraries for MongoDB.**

Expand Down
142 changes: 142 additions & 0 deletions src/Session.Tests/MongoSessionProviderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Extensions.Context;
using Squadron;
using Xunit;

namespace MongoDB.Extensions.Session.Tests;

public class MongoSessionProviderTests : IClassFixture<MongoReplicaSetResource>
{
private readonly IServiceProvider _serviceProvider;

public MongoSessionProviderTests(MongoReplicaSetResource mongoResource)
{
var mongoOptions = new MongoOptions
{
ConnectionString = mongoResource.ConnectionString,
DatabaseName = mongoResource.CreateDatabase().DatabaseNamespace.DatabaseName
};

_serviceProvider = new ServiceCollection()
.AddSingleton(mongoOptions)
.AddMongoSessionProvider<TestDbContext, ITestScope>()
.BuildServiceProvider();
}

[Fact]
public async Task BeginTransactionAsync_ShouldBeginTransaction()
{
// Arrange
ISessionProvider<ITestScope> sessionProvider = _serviceProvider
.GetRequiredService<ISessionProvider<ITestScope>>();

// Act
ITransactionSession transactionSession = await sessionProvider
.BeginTransactionAsync(CancellationToken.None);

// Assert
transactionSession.Should().NotBeNull();
IClientSessionHandle clientSessionHandle = transactionSession.GetSessionHandle();
clientSessionHandle.ServerSession.Id["id"].AsGuid.Should().NotBeEmpty();
clientSessionHandle.IsInTransaction.Should().BeTrue();
}

[Fact]
public async Task StartSessionAsync_ShouldStartSession()
{
// Arrange
ISessionProvider<ITestScope> sessionProvider = _serviceProvider
.GetRequiredService<ISessionProvider<ITestScope>>();

// Act
ISession session = await sessionProvider
.StartSessionAsync(CancellationToken.None);

// Assert
session.Should().NotBeNull();
IClientSessionHandle clientSessionHandle = session.GetSessionHandle();
clientSessionHandle.ServerSession.Id["id"].AsGuid.Should().NotBeEmpty();
clientSessionHandle.IsInTransaction.Should().BeFalse();
}

[Fact]
public async Task MongoSession_Dispose_ShouldDisposeSession()
{
// Arrange
ISessionProvider<ITestScope> sessionProvider = _serviceProvider
.GetRequiredService<ISessionProvider<ITestScope>>();

ISession session = await sessionProvider
.StartSessionAsync(CancellationToken.None);

// Act
session.Dispose();

// Assert
IClientSessionHandle clientSessionHandle = session.GetSessionHandle();
Assert.Throws<ObjectDisposedException>(() => clientSessionHandle.ServerSession);
}

[Fact]
public async Task MongoTransactionSession_Dispose_ShouldDisposeSession()
{
// Arrange
ISessionProvider<ITestScope> sessionProvider = _serviceProvider
.GetRequiredService<ISessionProvider<ITestScope>>();

ITransactionSession transactionSession = await sessionProvider
.BeginTransactionAsync(CancellationToken.None);

// Act
transactionSession.Dispose();

// Assert
IClientSessionHandle clientSessionHandle = transactionSession.GetSessionHandle();
Assert.Throws<ObjectDisposedException>(() => clientSessionHandle.ServerSession);
}

[Fact]
public async Task MongoTransactionSession_NotCommitting_ShouldNotAffectDatabase()
{
// Arrange
ISessionProvider<ITestScope> sessionProvider = _serviceProvider
.GetRequiredService<ISessionProvider<ITestScope>>();

ITransactionSession transactionSession = await sessionProvider
.BeginTransactionAsync(CancellationToken.None);
TestDbContext context = _serviceProvider.GetRequiredService<TestDbContext>();
IMongoCollection<BsonDocument> collection = context.CreateCollection<BsonDocument>();
await collection.InsertOneAsync(transactionSession.GetSessionHandle(), new BsonDocument());

// Act
// Not committing the transaction

// Assert
(await collection
.Find(FilterDefinition<BsonDocument>.Empty)
.ToListAsync())
.Count.Should().Be(0);
}

private class TestDbContext : MongoDbContext
{
public TestDbContext(MongoOptions mongoOptions)
: base(mongoOptions)
{
}

protected override void OnConfiguring(IMongoDatabaseBuilder mongoDatabaseBuilder)
{
}
}

private interface ITestScope
{
}
}
12 changes: 12 additions & 0 deletions src/Session/ISession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MongoDB.Extensions.Session;

public interface ISession : IDisposable
{
Task<T> WithTransactionAsync<T>(
Func<ISession, CancellationToken, Task<T>> action,
CancellationToken cancellationToken);
}
13 changes: 13 additions & 0 deletions src/Session/ISessionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Threading;
using System.Threading.Tasks;

namespace MongoDB.Extensions.Session;

public interface ISessionProvider<TScope>
{
Task<ITransactionSession> BeginTransactionAsync(
CancellationToken cancellationToken);

Task<ISession> StartSessionAsync(
CancellationToken cancellationToken);
}
9 changes: 9 additions & 0 deletions src/Session/ITransactionSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;
using System.Threading.Tasks;

namespace MongoDB.Extensions.Session;

public interface ITransactionSession : IDisposable
{
Task CommitAsync();
}
53 changes: 53 additions & 0 deletions src/Session/Internal/MongoSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;

namespace MongoDB.Extensions.Session;

internal sealed class MongoSession : ISession
{
private bool _disposed;

private static TransactionOptions TransactionOptions { get; } = new(
ReadConcern.Majority,
ReadPreference.Primary,
WriteConcern.WMajority.With(journal: true),
TimeSpan.FromSeconds(180));

public MongoSession(IClientSessionHandle clientSession)
{
Session = clientSession;
}

public IClientSessionHandle Session { get; }

public Task<T> WithTransactionAsync<T>(
Func<ISession, CancellationToken, Task<T>> action,
CancellationToken cancellationToken)
{
return Session.WithTransactionAsync<T>(
(_, ct) => action(this, ct),
TransactionOptions,
cancellationToken);
}

private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
Session.Dispose();
}

_disposed = true;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
51 changes: 51 additions & 0 deletions src/Session/Internal/MongoSessionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using MongoDB.Extensions.Context;

namespace MongoDB.Extensions.Session;

public class MongoSessionProvider<TContext, TScope> : ISessionProvider<TScope>
where TContext : IMongoDbContext
{
private readonly IMongoClient _mongoClient;

public MongoSessionProvider(TContext context)
{
_mongoClient = context.Client;
}

public Task<ITransactionSession> BeginTransactionAsync(
CancellationToken cancellationToken)
{
return BeginTransactionAsync(true, cancellationToken);
}

private async Task<ITransactionSession> BeginTransactionAsync(
bool safeModeEnabled,
CancellationToken cancellationToken)
{
IClientSessionHandle clientSession = await _mongoClient
.StartSessionAsync(cancellationToken: cancellationToken);

var transactionOptions = new TransactionOptions(
ReadConcern.Majority,
ReadPreference.Primary,
WriteConcern.WMajority.With(journal: safeModeEnabled),
TimeSpan.FromSeconds(180));

clientSession.StartTransaction(transactionOptions);

return new MongoTransactionSession(clientSession, cancellationToken);
}

public async Task<ISession> StartSessionAsync(
CancellationToken cancellationToken)
{
IClientSessionHandle clientSession = await _mongoClient
.StartSessionAsync(cancellationToken: cancellationToken);

return new MongoSession(clientSession);
}
}
46 changes: 46 additions & 0 deletions src/Session/Internal/MongoTransactionSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;

namespace MongoDB.Extensions.Session;

internal class MongoTransactionSession : ITransactionSession
{
private readonly CancellationToken _cancellationToken;
private bool _disposed;

public MongoTransactionSession(
IClientSessionHandle clientSession,
CancellationToken cancellationToken)
{
Session = clientSession;
_cancellationToken = cancellationToken;
}

public IClientSessionHandle Session { get; }

public async Task CommitAsync()
{
await Session.CommitTransactionAsync(_cancellationToken);
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
Session.Dispose();
}

_disposed = true;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
18 changes: 18 additions & 0 deletions src/Session/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using MongoDB.Extensions.Context;

namespace MongoDB.Extensions.Session;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMongoSessionProvider<TContext, TScope>(
this IServiceCollection services)
where TContext : class, IMongoDbContext
{
services.TryAddSingleton<TContext>();
services.TryAddSingleton<ISessionProvider<TScope>, MongoSessionProvider<TContext, TScope>>();

return services;
}
}
5 changes: 5 additions & 0 deletions src/Session/Session.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

<ItemGroup>
<PackageReference Include="MongoDB.Driver" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Context\Context.csproj" />
</ItemGroup>

</Project>

0 comments on commit 9fc7ca2

Please sign in to comment.