Skip to content

Commit

Permalink
Merge pull request #8 from Tim-Maes/feature/transactions
Browse files Browse the repository at this point in the history
Add support for DbTransactions
  • Loading branch information
Tim-Maes committed Dec 4, 2023
2 parents 68d2dba + 9f22a94 commit 175c647
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 20 deletions.
2 changes: 1 addition & 1 deletion GraphR.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package >
<metadata>
<id>GraphR</id>
<version>0.1.7</version>
<version>0.1.8</version>
<authors>Tim Maes</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
Expand Down
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,107 @@ The complete solution will be scaffolded inside your folder. Open it in Visual S
Replace the `connectionString` in `appsettings.json` with your own and remove the `Seed` folder in the `Infrastructure` project.
Remove the example queries/mutation/repositories etc and implement your own.

## Implementing Mutation & Query

In the core layer we have a custom lightweight handler implementation. These are `Handler<TOutput>` for querying data without parameters,
and `Handler<TInput, TOutput>` for querying or mutating data with parameters.

Since we don't use `AutoMapper` to map input types to output types, we just write an extension method `ToOutput()` that maps `TInput` to `ToOutput()`.

`FluentValidation` is used to validate input parameter properties.

### Example

```csharp
internal sealed class GetBookByIdQueryHandler : Handler<GetBookByIdParameters, BookDto>, IGetBookByIdHandler
{
private readonly IBookRepository _bookRepository;

public GetBookByIdQueryHandler(IBookRepository bookRepository)
{
_bookRepository = bookRepository;
}

protected override void DefineRules()
{
RuleFor(x => x.Id).GreaterThan(0);
}

protected override async Task<BookDto> HandleValidatedRequest(GetBookByIdParameters request)
=> (await _bookRepository.GetById(request.Id)).ToOutput();
}

public interface IGetBookByIdHandler : IHandler<GetBookByIdParameters, BookDto> { }
```

Inject the `IGetBookByIdQueryHandler` in your GraphApi query using the HotChocolate `[Service]` attribute, do the same for Mutations.

```
public sealed class BooksQuery
{
public async Task<BookDto> Book([Service] IGetBookByIdHandler handler, GetBookByIdParameters parameters)
=> await handler.Handle(parameters);
}
```

This is an example of how your GraphApi application layer could be structured


## Database

Currently supports a `DbConnectionProvider` for a single SQL database connection. Inject the `IDbConnectionProvider` in your repositories.

### Transactions

In the domain folder there is a abstraction of the `DbTransaction` implementation. You can inject the `ITransaction` interface in a `MutationHandler` in your Application layer if you need to mutate data over multiple repositories.

```csharp
internal class ExampleMutationHandler : Handler<ExampleMutationParameters, Result>,
IExampleMutationHandler
{
private readonly IRepository _repository;
private readonly ISecondRepository _secondRepository;
private readonly ITransaction _transaction;

public ExampleMutationHandler(
IRepository secondRepository,
ISecondRepository secondRepository,
ITransaction transaction)
{
_repository = repository;
_secondRepository = secondRepository;
_transaction = transaction;
}

protected override void DefineRules()
{
// FluentValidation RuleSet for your input
}

protected override async Task<Result> HandleValidatedRequest(ExampleMutationParameters input)
{
_transaction.Begin()
try
{
await _repository.Create(...);

await _secondRepository.UpdateSomething( ... );

_transaction.Commit();
}
catch
{
_transaction.RollBack();
throw;
}

return new Result(...);
}
}

public interface IExampleMutationHandler : IHandler<ExampleMutationParameters, Result> { }
```

## Architecture overview

### WebApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using GraphR.Application.Authors.Types.Input;
using GraphR.Application.Authors.Types.Mappings;
using GraphR.Application.Authors.Types.Output;
using GraphR.Domain.Interfaces;
using GraphR.Domain.Interfaces.Repositories;
using GrapR.Core.Handlers;

namespace GraphR.Application.Authors.Handlers;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using GraphR.Application.Authors.Types.Mappings;
using GraphR.Application.Authors.Types.Output;
using GraphR.Domain.Interfaces;
using GraphR.Domain.Interfaces.Repositories;
using GrapR.Core.Handlers;

namespace GraphR.Application.Authors.Handlers;
Expand Down
4 changes: 2 additions & 2 deletions src/GraphR.Application/Books/BooksMutation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ namespace GraphR.Application.Books;

public sealed class BooksMutation
{
public async Task<int> CreateBook([Service] ICreateBookMutationHandler handler, CreateBookParameters request)
=> await handler.Handle(request);
public async Task<int> CreateBook([Service] ICreateBookMutationHandler handler, CreateBookParameters parameters)
=> await handler.Handle(parameters);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using FluentValidation;
using GraphR.Application.Books.Types.Input;
using GraphR.Domain.Enums;
using GraphR.Domain.Interfaces.Repositories;
using GrapR.Core.Handlers;
using GrapR.Domain.Interfaces;

namespace GraphR.Application.Books.Handlers.Mutation;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using GrapR.Core.Handlers;
using GrapR.Domain.Interfaces;
using FluentValidation;
using GraphR.Application.Books.Types.Input;
using GraphR.Application.Books.Types.Output;
using GraphR.Application.Books.Types.Mappings;
using GraphR.Domain.Interfaces.Repositories;

namespace GraphR.Application.Books.Handlers.Query;

Expand All @@ -25,6 +25,4 @@ protected override async Task<BookDto> HandleValidatedRequest(GetBookByIdParamet
=> (await _bookRepository.GetById(request.Id)).ToOutput();
}

public interface IGetBookByIdHandler : IHandler<GetBookByIdParameters, BookDto>
{
}
public interface IGetBookByIdHandler : IHandler<GetBookByIdParameters, BookDto> { }
2 changes: 1 addition & 1 deletion src/GraphR.Core/GraphR.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand Down
2 changes: 1 addition & 1 deletion src/GraphR.Domain/GraphR.Domain.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using GraphR.Domain.Entities;

namespace GraphR.Domain.Interfaces;
namespace GraphR.Domain.Interfaces.Repositories;
public interface IAuthorRepository
{
Task<Author> GetById(int id);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using GraphR.Domain.Entities;
using GraphR.Domain.Enums;

namespace GrapR.Domain.Interfaces;
namespace GraphR.Domain.Interfaces.Repositories;

public interface IBookRepository
{
Expand Down
10 changes: 10 additions & 0 deletions src/GraphR.Domain/Interfaces/Transaction/ITransaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace GraphR.Domain.Interfaces.Transaction;

public interface ITransaction : IDisposable
{
void Begin();

void Commit();

void Rollback();
}
13 changes: 9 additions & 4 deletions src/GraphR.Infrastructure/Database/DbConnectionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,28 @@

namespace GrapR.Infrastructure.Database;

[AddSingleton(typeof(IDbConnectionProvider))]
[AddTransient(typeof(IDbConnectionProvider))]
public class DbConnectionProvider : IDbConnectionProvider
{
private readonly string _connectionString;
private readonly IOptions<InfrastructureOptions> _options;

public DbConnectionProvider(IOptions<InfrastructureOptions> options)
{
_connectionString = options.Value.ConnectionString;
_options = options;
}

public IDbConnection CreateConnection()
{
return new SqlConnection(_connectionString);
var connection = new SqlConnection(_options.Value.ConnectionString);
connection.Open();
return connection;
}

public string Schema { get; }
}

public interface IDbConnectionProvider
{
IDbConnection CreateConnection();
string Schema { get; }
}
73 changes: 73 additions & 0 deletions src/GraphR.Infrastructure/Database/DbTransaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System.Data;
using Bindicate.Attributes;
using GraphR.Domain.Interfaces.Transaction;
using GrapR.Infrastructure.Database;

namespace GraphR.Infrastructure.Database;

[AddTransient(typeof(ITransaction))]
public class DbTransaction : ITransaction
{
private readonly IDbConnectionProvider _connectionProvider;
private IDbConnection _connection;
private IDbTransaction _transaction;
private bool _disposed;

public DbTransaction(IDbConnectionProvider connectionProvider)
{
_connectionProvider = connectionProvider;
}

public void Begin()
{
if (_connection is null) _connection = _connectionProvider.CreateConnection();

if (_connection.State is not ConnectionState.Open) _connection.Open();

_transaction = _connection.BeginTransaction();
}

public void Commit()
{
try
{
_transaction?.Commit();
}
catch
{
Rollback();
throw;
}
finally
{
DisposeTransaction();
}
}

public void Rollback()
{
_transaction?.Rollback();
DisposeTransaction();
}

private void DisposeTransaction()
{
_transaction?.Dispose();
_transaction = null;
}

public void Dispose()
{
if (_disposed)
return;

_disposed = true;
DisposeTransaction();

if (_connection is null)
{
_connection.Dispose();
_connection = null;
}
}
}
2 changes: 1 addition & 1 deletion src/GraphR.Infrastructure/Repositories/AuthorRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Dapper;
using GraphR.Domain.Entities;
using GraphR.Domain.Exceptions;
using GraphR.Domain.Interfaces;
using GraphR.Domain.Interfaces.Repositories;
using GrapR.Infrastructure.Database;

namespace GrapR.Infrastructure.Repositories;
Expand Down
2 changes: 1 addition & 1 deletion src/GraphR.Infrastructure/Repositories/BookRepository.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using Bindicate.Attributes;
using GrapR.Domain.Exceptions;
using GrapR.Domain.Interfaces;
using GraphR.Domain.Entities;
using GrapR.Infrastructure.Database;
using Dapper;
using GraphR.Domain.Enums;
using GraphR.Domain.Interfaces.Repositories;

namespace GrapR.Infrastructure.Repositories;

Expand Down

0 comments on commit 175c647

Please sign in to comment.