diff --git a/deployments/docker-compose/docker-compose.yaml b/deployments/docker-compose/docker-compose.yaml index c89fa8a..80a6848 100644 --- a/deployments/docker-compose/docker-compose.yaml +++ b/deployments/docker-compose/docker-compose.yaml @@ -1,24 +1,27 @@ version: "3.3" services: - ###################################################### - # Postgres - ###################################################### + ####################################################### + # Postgres + ###################################################### postgres: - image: postgres:latest - container_name: postgres - restart: on-failure - ports: - - '5432:5432' - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - command: - - "postgres" - - "-c" - - "wal_level=logical" - networks: - - booking + image: postgres:latest + container_name: postgres + restart: on-failure + ports: + - '5432:5432' + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + command: + - "postgres" + - "-c" + - "wal_level=logical" + - "-c" + - "max_prepared_transactions=10" + networks: + - booking + ####################################################### diff --git a/deployments/docker-compose/infrastracture.yaml b/deployments/docker-compose/infrastracture.yaml index 75f0748..de9c139 100644 --- a/deployments/docker-compose/infrastracture.yaml +++ b/deployments/docker-compose/infrastracture.yaml @@ -31,6 +31,8 @@ services: - "postgres" - "-c" - "wal_level=logical" + - "-c" + - "max_prepared_transactions=10" networks: - booking diff --git a/deployments/kubernetes/booking-microservices.yml b/deployments/kubernetes/booking-microservices.yml index de4e79f..74f1ff5 100644 --- a/deployments/kubernetes/booking-microservices.yml +++ b/deployments/kubernetes/booking-microservices.yml @@ -390,7 +390,13 @@ metadata: name: postgres spec: containers: - - env: + - args: + - postgres + - -c + - wal_level=logical + - -c + - max_prepared_transactions=10 + env: - name: POSTGRES_PASSWORD value: postgres - name: POSTGRES_USER diff --git a/src/BuildingBlocks/EFCore/AppDbContextBase.cs b/src/BuildingBlocks/EFCore/AppDbContextBase.cs index 4ffcccc..eb9d69c 100644 --- a/src/BuildingBlocks/EFCore/AppDbContextBase.cs +++ b/src/BuildingBlocks/EFCore/AppDbContextBase.cs @@ -1,16 +1,18 @@ namespace BuildingBlocks.EFCore; using System.Collections.Immutable; -using BuildingBlocks.Core.Event; -using BuildingBlocks.Core.Model; +using Core.Event; +using Core.Model; using Microsoft.EntityFrameworkCore; -using System.Data; +using Microsoft.EntityFrameworkCore.Storage; using Web; using Exception = System.Exception; +using IsolationLevel = System.Data.IsolationLevel; public abstract class AppDbContextBase : DbContext, IDbContext { private readonly ICurrentUserProvider _currentUserProvider; + private IDbContextTransaction? _currentTransaction; protected AppDbContextBase(DbContextOptions options, ICurrentUserProvider currentUserProvider) : base(options) @@ -22,13 +24,53 @@ public abstract class AppDbContextBase : DbContext, IDbContext { } + public async Task BeginTransactionalAsync(CancellationToken cancellationToken = default) + { + _currentTransaction ??= await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); + } + + //ref: https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency#execution-strategies-and-transactions + public async Task CommitTransactionalAsync(CancellationToken cancellationToken = default) + { + try + { + await SaveChangesAsync(cancellationToken); + await _currentTransaction?.CommitAsync(cancellationToken)!; + } + catch + { + await _currentTransaction?.RollbackAsync(cancellationToken)!; + throw; + } + finally + { + _currentTransaction?.Dispose(); + _currentTransaction = null; + } + } + + + public async Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + { + try + { + await _currentTransaction?.RollbackAsync(cancellationToken)!; + } + finally + { + _currentTransaction?.Dispose(); + _currentTransaction = null; + } + } + //ref: https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency#execution-strategies-and-transactions public Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default) { var strategy = Database.CreateExecutionStrategy(); return strategy.ExecuteAsync(async () => { - await using var transaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); + await using var transaction = + await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); try { await SaveChangesAsync(cancellationToken); diff --git a/src/BuildingBlocks/EFCore/EfTxBehavior.cs b/src/BuildingBlocks/EFCore/EfTxBehavior.cs index f58e7b1..87a09ec 100644 --- a/src/BuildingBlocks/EFCore/EfTxBehavior.cs +++ b/src/BuildingBlocks/EFCore/EfTxBehavior.cs @@ -5,21 +5,27 @@ using Microsoft.Extensions.Logging; namespace BuildingBlocks.EFCore; +using System.Transactions; +using PersistMessageProcessor; + public class EfTxBehavior : IPipelineBehavior where TRequest : notnull, IRequest where TResponse : notnull { private readonly ILogger> _logger; private readonly IDbContext _dbContextBase; + private readonly IPersistMessageDbContext _persistMessageDbContext; private readonly IEventDispatcher _eventDispatcher; public EfTxBehavior( ILogger> logger, IDbContext dbContextBase, + IPersistMessageDbContext persistMessageDbContext, IEventDispatcher eventDispatcher) { _logger = logger; _dbContextBase = dbContextBase; + _persistMessageDbContext = persistMessageDbContext; _eventDispatcher = eventDispatcher; } @@ -42,6 +48,10 @@ public class EfTxBehavior : IPipelineBehavior), typeof(TRequest).FullName); + // ref: https://learn.microsoft.com/en-us/ef/core/saving/transactions#using-systemtransactions + using var scope = new TransactionScope(TransactionScopeOption.Required, + new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }, + TransactionScopeAsyncFlowOption.Enabled); var response = await next(); @@ -55,11 +65,16 @@ public class EfTxBehavior : IPipelineBehavior Set() where TEntity : class; IReadOnlyList GetDomainEvents(); + public Task BeginTransactionalAsync(CancellationToken cancellationToken = default); + public Task CommitTransactionalAsync(CancellationToken cancellationToken = default); + public Task RollbackTransactionAsync(CancellationToken cancellationToken = default); Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs b/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs index fe57458..56fd600 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs @@ -8,7 +8,6 @@ using Configurations; using Core.Model; using global::Polly; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Exception = System.Exception; public class PersistMessageDbContext : DbContext, IPersistMessageDbContext diff --git a/src/BuildingBlocks/TestBase/TestContainers.cs b/src/BuildingBlocks/TestBase/TestContainers.cs index 222a665..6b68c35 100644 --- a/src/BuildingBlocks/TestBase/TestContainers.cs +++ b/src/BuildingBlocks/TestBase/TestContainers.cs @@ -1,4 +1,5 @@ namespace BuildingBlocks.TestBase; + using Testcontainers.EventStoreDb; using Testcontainers.MongoDb; using Testcontainers.PostgreSql; @@ -7,21 +8,25 @@ using Web; public static class TestContainers { - public static RabbitMqContainerOptions RabbitMqContainerConfiguration { get;} - public static PostgresContainerOptions PostgresContainerConfiguration { get;} - public static PostgresPersistContainerOptions PostgresPersistContainerConfiguration { get;} - public static MongoContainerOptions MongoContainerConfiguration { get;} - public static EventStoreContainerOptions EventStoreContainerConfiguration { get;} + public static RabbitMqContainerOptions RabbitMqContainerConfiguration { get; } + public static PostgresContainerOptions PostgresContainerConfiguration { get; } + public static PostgresPersistContainerOptions PostgresPersistContainerConfiguration { get; } + public static MongoContainerOptions MongoContainerConfiguration { get; } + public static EventStoreContainerOptions EventStoreContainerConfiguration { get; } static TestContainers() { var configuration = ConfigurationHelper.GetConfiguration(); - RabbitMqContainerConfiguration = configuration.GetOptions(nameof(RabbitMqContainerOptions)); - PostgresContainerConfiguration = configuration.GetOptions(nameof(PostgresContainerOptions)); - PostgresPersistContainerConfiguration = configuration.GetOptions(nameof(PostgresPersistContainerOptions)); + RabbitMqContainerConfiguration = + configuration.GetOptions(nameof(RabbitMqContainerOptions)); + PostgresContainerConfiguration = + configuration.GetOptions(nameof(PostgresContainerOptions)); + PostgresPersistContainerConfiguration = + configuration.GetOptions(nameof(PostgresPersistContainerOptions)); MongoContainerConfiguration = configuration.GetOptions(nameof(MongoContainerOptions)); - EventStoreContainerConfiguration = configuration.GetOptions(nameof(EventStoreContainerOptions)); + EventStoreContainerConfiguration = + configuration.GetOptions(nameof(EventStoreContainerOptions)); } public static PostgreSqlContainer PostgresTestContainer() @@ -34,6 +39,7 @@ public static class TestContainers var builder = baseBuilder .WithImage(PostgresContainerConfiguration.ImageName) .WithName(PostgresContainerConfiguration.Name) + .WithCommand(new string[2] { "-c", "max_prepared_transactions=10" }) .WithPortBinding(PostgresContainerConfiguration.Port, true) .Build(); @@ -50,6 +56,7 @@ public static class TestContainers var builder = baseBuilder .WithImage(PostgresPersistContainerConfiguration.ImageName) .WithName(PostgresPersistContainerConfiguration.Name) + .WithCommand(new string[2] { "-c", "max_prepared_transactions=10" }) .WithPortBinding(PostgresPersistContainerConfiguration.Port, true) .Build(); diff --git a/src/Services/Flight/src/Flight/CreateFlightConsumerHandler.cs b/src/Services/Flight/src/Flight/FlightCreatedConsumerHandler.cs similarity index 80% rename from src/Services/Flight/src/Flight/CreateFlightConsumerHandler.cs rename to src/Services/Flight/src/Flight/FlightCreatedConsumerHandler.cs index 5d9caaf..50f2dfb 100644 --- a/src/Services/Flight/src/Flight/CreateFlightConsumerHandler.cs +++ b/src/Services/Flight/src/Flight/FlightCreatedConsumerHandler.cs @@ -5,7 +5,7 @@ using MassTransit; namespace Flight; -public class CreateFlightConsumerHandler : IConsumer +public class FlightCreatedConsumerHandler : IConsumer { public Task Consume(ConsumeContext context) { diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs b/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs index b180f03..17266d2 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs @@ -54,7 +54,7 @@ public class CreateFlightEndpoint : IMinimalEndpoint return Results.CreatedAtRoute("GetFlightById", new { id = result.Id }, response); }) - // .RequireAuthorization() + .RequireAuthorization() .WithName("CreateFlight") .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) .Produces(StatusCodes.Status201Created) diff --git a/src/Services/Identity/src/Identity/Data/IdentityContext.cs b/src/Services/Identity/src/Identity/Data/IdentityContext.cs index 3d2a132..6178a4e 100644 --- a/src/Services/Identity/src/Identity/Data/IdentityContext.cs +++ b/src/Services/Identity/src/Identity/Data/IdentityContext.cs @@ -9,18 +9,19 @@ using BuildingBlocks.Core.Event; using BuildingBlocks.Core.Model; using BuildingBlocks.EFCore; using Identity.Identity.Models; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; namespace Identity.Data; using System; +using Microsoft.EntityFrameworkCore.Storage; public sealed class IdentityContext : IdentityDbContext, IDbContext { + private IDbContextTransaction? _currentTransaction; + public IdentityContext(DbContextOptions options) : base(options) { } @@ -33,6 +34,45 @@ public sealed class IdentityContext : IdentityDbContext() diff --git a/src/Services/Identity/src/Identity/UserCreatedConsumerHandler.cs b/src/Services/Identity/src/Identity/UserCreatedConsumerHandler.cs new file mode 100644 index 0000000..ded340d --- /dev/null +++ b/src/Services/Identity/src/Identity/UserCreatedConsumerHandler.cs @@ -0,0 +1,14 @@ +namespace Identity; + +using System; +using System.Threading.Tasks; +using BuildingBlocks.Contracts.EventBus.Messages; +using MassTransit; + +public class UserCreatedConsumerHandler : IConsumer +{ + public Task Consume(ConsumeContext context) + { + Console.WriteLine("It's for test"); return Task.CompletedTask; + } +}