Skip to content

Commit

Permalink
Update code to use C# 12 primary constructors (#220)
Browse files Browse the repository at this point in the history
### Summary & Motivation

Refactoring applicable constructors across the project to leverage the
new C# 12 primary constructors. This change simplifies the constructor
syntax and enhances code readability and maintainability.

Additionally, the formatting of existing type declarations has been
standardized to ensure consistency throughout the codebase.

### Checklist

- [x] I have added a Label to the pull-request
- [x] I have added tests, and done manual regression tests
- [x] I have updated the documentation, if necessary
  • Loading branch information
tjementum authored Nov 20, 2023
2 parents b1dbbe2 + 41b9c41 commit f79ca44
Show file tree
Hide file tree
Showing 20 changed files with 68 additions and 177 deletions.
16 changes: 4 additions & 12 deletions application/account-management/Application/Tenants/CreateTenant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,13 @@ public sealed record CreateTenantCommand(string Subdomain, string Name, string?
: ICommand, ITenantValidation, IRequest<Result<TenantId>>;

[UsedImplicitly]
public sealed class CreateTenantHandler : IRequestHandler<CreateTenantCommand, Result<TenantId>>
public sealed class CreateTenantHandler(ITenantRepository tenantRepository, ISender mediator)
: IRequestHandler<CreateTenantCommand, Result<TenantId>>
{
private readonly ISender _mediator;
private readonly ITenantRepository _tenantRepository;

public CreateTenantHandler(ITenantRepository tenantRepository, ISender mediator)
{
_tenantRepository = tenantRepository;
_mediator = mediator;
}

public async Task<Result<TenantId>> Handle(CreateTenantCommand command, CancellationToken cancellationToken)
{
var tenant = Tenant.Create(command.Subdomain, command.Name, command.Phone);
await _tenantRepository.AddAsync(tenant, cancellationToken);
await tenantRepository.AddAsync(tenant, cancellationToken);

await CreateTenantOwnerAsync(tenant.Id, command.Email, cancellationToken);
return tenant.Id;
Expand All @@ -33,7 +25,7 @@ private async Task CreateTenantOwnerAsync(TenantId tenantId, string tenantOwnerE
CancellationToken cancellationToken)
{
var createTenantOwnerUserCommand = new CreateUserCommand(tenantId, tenantOwnerEmail, UserRole.TenantOwner);
var result = await _mediator.Send(createTenantOwnerUserCommand, cancellationToken);
var result = await mediator.Send(createTenantOwnerUserCommand, cancellationToken);

if (!result.IsSuccess) throw new UnreachableException($"Create Tenant Owner: {result.GetErrorSummary()}");
}
Expand Down
14 changes: 4 additions & 10 deletions application/account-management/Application/Tenants/DeleteTenant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,15 @@ namespace PlatformPlatform.AccountManagement.Application.Tenants;
public sealed record DeleteTenantCommand(TenantId Id) : ICommand, IRequest<Result>;

[UsedImplicitly]
public sealed class DeleteTenantHandler : IRequestHandler<DeleteTenantCommand, Result>
public sealed class DeleteTenantHandler(ITenantRepository tenantRepository)
: IRequestHandler<DeleteTenantCommand, Result>
{
private readonly ITenantRepository _tenantRepository;

public DeleteTenantHandler(ITenantRepository tenantRepository)
{
_tenantRepository = tenantRepository;
}

public async Task<Result> Handle(DeleteTenantCommand command, CancellationToken cancellationToken)
{
var tenant = await _tenantRepository.GetByIdAsync(command.Id, cancellationToken);
var tenant = await tenantRepository.GetByIdAsync(command.Id, cancellationToken);
if (tenant is null) return Result.NotFound($"Tenant with id '{command.Id}' not found.");

_tenantRepository.Remove(tenant);
tenantRepository.Remove(tenant);
return Result.Success();
}
}
Expand Down
12 changes: 3 additions & 9 deletions application/account-management/Application/Tenants/GetTenant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,12 @@ namespace PlatformPlatform.AccountManagement.Application.Tenants;
public sealed record GetTenantQuery(TenantId Id) : IRequest<Result<TenantResponseDto>>;

[UsedImplicitly]
public sealed class GetTenantHandler : IRequestHandler<GetTenantQuery, Result<TenantResponseDto>>
public sealed class GetTenantHandler(ITenantRepository tenantRepository)
: IRequestHandler<GetTenantQuery, Result<TenantResponseDto>>
{
private readonly ITenantRepository _tenantRepository;

public GetTenantHandler(ITenantRepository tenantRepository)
{
_tenantRepository = tenantRepository;
}

public async Task<Result<TenantResponseDto>> Handle(GetTenantQuery request, CancellationToken cancellationToken)
{
var tenant = await _tenantRepository.GetByIdAsync(request.Id, cancellationToken);
var tenant = await tenantRepository.GetByIdAsync(request.Id, cancellationToken);
return tenant?.Adapt<TenantResponseDto>()
?? Result<TenantResponseDto>.NotFound($"Tenant with id '{request.Id}' not found.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
namespace PlatformPlatform.AccountManagement.Application.Tenants;

[UsedImplicitly]
public sealed class TenantCreatedEventHandler : INotificationHandler<TenantCreatedEvent>
public sealed class TenantCreatedEventHandler(ILogger<TenantCreatedEventHandler> logger)
: INotificationHandler<TenantCreatedEvent>
{
private readonly ILogger<TenantCreatedEventHandler> _logger;

public TenantCreatedEventHandler(ILogger<TenantCreatedEventHandler> logger)
{
_logger = logger;
}

public Task Handle(TenantCreatedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Raise event to send Welcome mail to tenant");
logger.LogInformation("Raise event to send Welcome mail to tenant");
return Task.CompletedTask;
}
}
14 changes: 4 additions & 10 deletions application/account-management/Application/Tenants/UpdateTenant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,16 @@ public sealed record UpdateTenantCommand : ICommand, ITenantValidation, IRequest
}

[UsedImplicitly]
public sealed class UpdateTenantHandler : IRequestHandler<UpdateTenantCommand, Result>
public sealed class UpdateTenantHandler(ITenantRepository tenantRepository)
: IRequestHandler<UpdateTenantCommand, Result>
{
private readonly ITenantRepository _tenantRepository;

public UpdateTenantHandler(ITenantRepository tenantRepository)
{
_tenantRepository = tenantRepository;
}

public async Task<Result> Handle(UpdateTenantCommand command, CancellationToken cancellationToken)
{
var tenant = await _tenantRepository.GetByIdAsync(command.Id, cancellationToken);
var tenant = await tenantRepository.GetByIdAsync(command.Id, cancellationToken);
if (tenant is null) return Result.NotFound($"Tenant with id '{command.Id}' not found.");

tenant.Update(command.Name, command.Phone);
_tenantRepository.Update(tenant);
tenantRepository.Update(tenant);
return Result.Success();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@

namespace PlatformPlatform.AccountManagement.Infrastructure;

public sealed class AccountManagementDbContext : SharedKernelDbContext<AccountManagementDbContext>
public sealed class AccountManagementDbContext(DbContextOptions<AccountManagementDbContext> options)
: SharedKernelDbContext<AccountManagementDbContext>(options)
{
public AccountManagementDbContext(DbContextOptions<AccountManagementDbContext> options) : base(options)
{
}

public DbSet<Tenant> Tenants => Set<Tenant>();

public DbSet<User> Users => Set<User>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
namespace PlatformPlatform.AccountManagement.Infrastructure.Tenants;

[UsedImplicitly]
internal sealed class TenantRepository : RepositoryBase<Tenant, TenantId>, ITenantRepository
internal sealed class TenantRepository(AccountManagementDbContext accountManagementDbContext)
: RepositoryBase<Tenant, TenantId>(accountManagementDbContext), ITenantRepository
{
public TenantRepository(AccountManagementDbContext accountManagementDbContext) : base(accountManagementDbContext)
{
}

public Task<bool> IsSubdomainFreeAsync(string subdomain, CancellationToken cancellationToken)
{
return DbSet.AllAsync(tenant => tenant.Id != subdomain, cancellationToken);
Expand Down
47 changes: 15 additions & 32 deletions application/shared-kernel/ApiCore/ApiResults/ApiResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,43 @@

namespace PlatformPlatform.SharedKernel.ApiCore.ApiResults;

public partial class ApiResult : IResult
public partial class ApiResult(ResultBase result, string? routePrefix = null) : IResult
{
private readonly ResultBase _result;
private readonly string? _routePrefix;

protected ApiResult(ResultBase result, string? routePrefix = null)
{
_result = result;
_routePrefix = routePrefix;
}

public Task ExecuteAsync(HttpContext httpContext)
{
return _routePrefix == null
return routePrefix == null
? ConvertResult().ExecuteAsync(httpContext)
: ConvertResult(_routePrefix).ExecuteAsync(httpContext);
: ConvertResult(routePrefix).ExecuteAsync(httpContext);
}

protected virtual IResult ConvertResult(string? routePrefix = null)
{
if (!_result.IsSuccess) return GetProblemDetailsAsJson();
if (!result.IsSuccess) return GetProblemDetailsAsJson();

return routePrefix == null
? Results.Ok()
: Results.Created($"{routePrefix}/{_result}", null);
: Results.Created($"{routePrefix}/{result}", null);
}

protected IResult GetProblemDetailsAsJson()
{
return Results.Json(CreateProblemDetails(), statusCode: (int)_result.StatusCode,
return Results.Json(CreateProblemDetails(), statusCode: (int)result.StatusCode,
contentType: "application/problem+json");
}

private ProblemDetails CreateProblemDetails()
{
var statusCode = _result.StatusCode;
var statusCode = result.StatusCode;
var problemDetails = new ProblemDetails
{
Type = $"https://httpstatuses.com/{(int)_result.StatusCode}",
Type = $"https://httpstatuses.com/{(int)result.StatusCode}",
Title = SplitCamelCaseTitle(statusCode.ToString()),
Status = (int)_result.StatusCode
Status = (int)result.StatusCode
};

if (_result.ErrorMessage is not null) problemDetails.Detail = _result.ErrorMessage.Message;
if (result.ErrorMessage is not null) problemDetails.Detail = result.ErrorMessage.Message;

if (_result.Errors?.Length > 0) problemDetails.Extensions[nameof(_result.Errors)] = _result.Errors;
if (result.Errors?.Length > 0) problemDetails.Extensions[nameof(result.Errors)] = result.Errors;

return problemDetails;
}
Expand All @@ -70,23 +61,15 @@ public static implicit operator ApiResult(Result result)
}
}

public class ApiResult<T> : ApiResult
public class ApiResult<T>(Result<T> result, string? routePrefix = null) : ApiResult(result, routePrefix)
{
private readonly Result<T> _result;

public ApiResult(Result<T> result, string? routePrefix = null)
: base(result, routePrefix)
{
_result = result;
}

protected override IResult ConvertResult(string? routePrefix = null)
{
if (!_result.IsSuccess) return GetProblemDetailsAsJson();
if (!result.IsSuccess) return GetProblemDetailsAsJson();

return routePrefix == null
? Results.Ok(_result.Value!.Adapt<T>())
: Results.Created($"{routePrefix}/{_result.Value}", null);
? Results.Ok(result.Value!.Adapt<T>())
: Results.Created($"{routePrefix}/{result.Value}", null);
}

public static implicit operator ApiResult<T>(Result<T> result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,10 @@ namespace PlatformPlatform.SharedKernel.ApplicationCore.Behaviors;
/// services (e.g., send emails) that are not part of the same database transaction. For such tasks, use Integration
/// Events instead.
/// </summary>
public sealed class PublishDomainEventsPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
public sealed class PublishDomainEventsPipelineBehavior<TRequest, TResponse>
(IDomainEventCollector domainEventCollector, IPublisher mediator) : IPipelineBehavior<TRequest, TResponse>
where TRequest : ICommand where TResponse : ResultBase
{
private readonly IDomainEventCollector _domainEventCollector;
private readonly IPublisher _mediator;

public PublishDomainEventsPipelineBehavior(IDomainEventCollector domainEventCollector, IPublisher mediator)
{
_domainEventCollector = domainEventCollector;
_mediator = mediator;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
Expand All @@ -39,7 +31,7 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
// Publish the domain event to the MediatR pipeline. Any registered event handlers will be invoked. These
// event handlers can then carry out any necessary actions, such as managing side effects, updating read
// models, and so forth.
await _mediator.Publish(domainEvent, cancellationToken);
await mediator.Publish(domainEvent, cancellationToken);

// It is possible that a domain event handler creates a new domain event, so we need to check if there are
// any new domain events that need to be published and handled before continuing.
Expand All @@ -54,7 +46,7 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
/// </summary>
private void EnqueueAndClearDomainEvents(Queue<IDomainEvent> domainEvents)
{
foreach (var aggregate in _domainEventCollector.GetAggregatesWithDomainEvents())
foreach (var aggregate in domainEventCollector.GetAggregatesWithDomainEvents())
{
foreach (var domainEvent in aggregate.DomainEvents)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,23 @@ namespace PlatformPlatform.SharedKernel.ApplicationCore.Behaviors;
/// are successfully handled. If an exception occurs the UnitOfWork.Commit will never be called, and all changes
/// will be lost.
/// </summary>
public sealed class UnitOfWorkPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : ICommand where TResponse : ResultBase
public sealed class UnitOfWorkPipelineBehavior<TRequest, TResponse>
(IUnitOfWork unitOfWork, UnitOfWorkPipelineBehaviorConcurrentCounter unitOfWorkPipelineBehaviorConcurrentCounter)
: IPipelineBehavior<TRequest, TResponse> where TRequest : ICommand where TResponse : ResultBase
{
private readonly IUnitOfWork _unitOfWork;
private readonly UnitOfWorkPipelineBehaviorConcurrentCounter _unitOfWorkPipelineBehaviorConcurrentCounter;

public UnitOfWorkPipelineBehavior(IUnitOfWork unitOfWork,
UnitOfWorkPipelineBehaviorConcurrentCounter unitOfWorkPipelineBehaviorConcurrentCounter)
{
_unitOfWork = unitOfWork;
_unitOfWorkPipelineBehaviorConcurrentCounter = unitOfWorkPipelineBehaviorConcurrentCounter;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_unitOfWorkPipelineBehaviorConcurrentCounter.Increment();
unitOfWorkPipelineBehaviorConcurrentCounter.Increment();
var response = await next();

// ReSharper disable once InvertIf
if (response is ResultBase { IsSuccess: true })
{
_unitOfWorkPipelineBehaviorConcurrentCounter.Decrement();
if (_unitOfWorkPipelineBehaviorConcurrentCounter.IsZero())
unitOfWorkPipelineBehaviorConcurrentCounter.Decrement();
if (unitOfWorkPipelineBehaviorConcurrentCounter.IsZero())
{
await _unitOfWork.CommitAsync(cancellationToken);
await unitOfWork.CommitAsync(cancellationToken);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,13 @@ namespace PlatformPlatform.SharedKernel.ApplicationCore.Behaviors;
/// FluentValidation. If the request is not valid, the pipeline will be short-circuited and the request will not be
/// handled. If the request is valid, the next pipeline behavior will be called.
/// </summary>
public sealed class ValidationPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : ICommand where TResponse : ResultBase
public sealed class ValidationPipelineBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse> where TRequest : ICommand where TResponse : ResultBase
{
private readonly IEnumerable<IValidator<TRequest>> _validators;

public ValidationPipelineBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!_validators.Any())
if (!validators.Any())
{
return await next();
}
Expand All @@ -32,7 +25,7 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe

// Run all validators in parallel and await the results
var validationResults =
await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
await Task.WhenAll(validators.Select(v => v.ValidateAsync(context, cancellationToken)));

// Aggregate the results from all validators into a distinct list of errorDetails
var errorDetails = validationResults
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,10 @@ namespace PlatformPlatform.SharedKernel.DomainCore.Entities;
/// The AudibleEntity class extends Entity and implements IAuditableEntity, which adds
/// a readonly CreatedAt and private ModifiedAt properties to derived entities.
/// </summary>
public abstract class AudibleEntity<T> : Entity<T>, IAuditableEntity where T : IComparable<T>
public abstract class AudibleEntity<T>(T id) : Entity<T>(id), IAuditableEntity where T : IComparable<T>
{
protected AudibleEntity(T id) : base(id)
{
CreatedAt = DateTime.UtcNow;
}

[UsedImplicitly]
public DateTime CreatedAt { get; private set; }
public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;

Check warning on line 12 in application/shared-kernel/DomainCore/Entities/AudibleEntity.cs

View workflow job for this annotation

GitHub Actions / Test and Code Coverage

Remove the member initializer, all constructors set an initial value for the member. (https://rules.sonarsource.com/csharp/RSPEC-3604)

[ConcurrencyCheck]
public DateTime? ModifiedAt { get; private set; }
Expand Down
Loading

0 comments on commit f79ca44

Please sign in to comment.