Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mongo session provider support #89

Merged
merged 12 commits into from
Jan 19, 2024
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>
Loading
Loading