Changes between Initial Version and Version 1 of P8


Ignore:
Timestamp:
04/28/26 13:55:00 (4 days ago)
Author:
211099
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • P8

    v1 v1  
     1= Advanced Application Development =
     2
     3== Transactions ==
     4
     5=== User Registration ===
     6
     7{{{
     8public async Task<RegisterResponse> Handle(RegisterRequest request, CancellationToken cancellationToken)
     9{
     10    var existing = await _userRepository.GetByEmailAsync(request.Email, cancellationToken);
     11    if (existing != null)
     12        throw new InvalidOperationException("Email already in use.");
     13
     14    await using var transaction = await _context.BeginTransactionAsync(cancellationToken);
     15    try
     16    {
     17        var user = new Domain.Entities.User
     18        {
     19            Username = request.Username,
     20            Email = request.Email,
     21            Name = request.Name,
     22            Surname = request.Surname,
     23            Password = BCrypt.Net.BCrypt.HashPassword(request.Password),
     24            CreatedAt = DateTime.UtcNow,
     25            UpdatedAt = DateTime.UtcNow
     26        };
     27
     28        await _userRepository.AddAsync(user, cancellationToken);
     29
     30        var writer = new Domain.Entities.Writer { Id = user.Id };
     31        await _writerRepository.AddAsync(writer, cancellationToken);
     32
     33        await transaction.CommitAsync(cancellationToken);
     34        return new RegisterResponse(user.Id, user.Username, user.Email);
     35    }
     36    catch
     37    {
     38        await transaction.RollbackAsync(cancellationToken);
     39        throw;
     40    }
     41}
     42}}}
     43
     44=== Publishing a Story ===
     45
     46{{{
     47public async Task<AddResponse> Handle(AddRequest request, CancellationToken cancellationToken)
     48{
     49    await using var transaction = await _context.BeginTransactionAsync(cancellationToken);
     50    try
     51    {
     52        var story = new Domain.Entities.Story
     53        {
     54            MatureContent = request.MatureContent,
     55            ShortDescription = request.ShortDescription,
     56            Image = request.Image,
     57            Content = request.Content,
     58            UserId = request.UserId,
     59            CreatedAt = DateTime.UtcNow,
     60            UpdatedAt = DateTime.UtcNow
     61        };
     62
     63        await _storyRepository.AddAsync(story, cancellationToken);
     64
     65        foreach (var genreName in request.Genres ?? [])
     66        {
     67            var genre = await _genreRepository.GetByNameAsync(genreName, cancellationToken);
     68            if (genre == null) continue;
     69            await _hasGenreRepository.AddAsync(new Domain.Entities.HasGenre { StoryId = story.Id, GenreId = genre.Id }, cancellationToken);
     70        }
     71
     72        await transaction.CommitAsync(cancellationToken);
     73        return new AddResponse(story.Id);
     74    }
     75    catch
     76    {
     77        await transaction.RollbackAsync(cancellationToken);
     78        throw;
     79    }
     80}
     81}}}
     82
     83== Database Connection Pooling ==
     84
     85Because we use .NET 9 with EF Core and Npgsql for the backend, we do not manage database connections manually. Connection pooling in Npgsql is handled automatically by the driver.
     86
     87Npgsql is a transitive dependency of `Npgsql.EntityFrameworkCore.PostgreSQL`, so adding the following to the project is sufficient:
     88
     89{{{
     90<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
     91}}}
     92
     93The EF Core context is registered using `AddDbContextPool` instead of `AddDbContext`, which enables context instance pooling on top of Npgsql's own connection pool:
     94
     95{{{
     96services.AddDbContextPool<ApplicationDbContext>(options =>
     97    options.UseNpgsql(connectionString));
     98}}}
     99
     100The pool parameters are read from `appsettings.json` at startup and appended to the Npgsql connection string:
     101
     102{{{
     103"ConnectionPool": {
     104  "MinPoolSize": 1,
     105  "MaxPoolSize": 20,
     106  "ConnectionIdleLifetime": 300,
     107  "ConnectionPruningInterval": 10,
     108  "CommandTimeout": 30,
     109  "Timeout": 15
     110}
     111}}}
     112
     113These values can be changed as needed. The established connections can be observed in the SSH tunnel logs when the application starts:
     114
     115{{{
     116debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     117debug1: channel 2: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     118debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     119debug1: channel 3: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     120debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     121debug1: channel 4: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     122debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     123debug1: channel 5: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     124debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     125debug1: channel 6: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     126debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     127debug1: channel 7: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     128debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     129debug1: channel 8: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     130debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     131debug1: channel 9: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     132debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     133debug1: channel 10: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     134debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     135debug1: channel 11: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     136}}}