refactor: Restructure Application layer with vertical slices and AutoMapper

Vertical slice architecture:

  - Move Features/Documents/{UseCase}/ to {UseCase}/Queries/

  - Query + Handler in SAME file (co-located)

  - Validator in separate file (single responsibility)

New structure:

  - ValidatePdf/Queries/ValidatePdfQuery.cs (Query + Handler)

  - ValidatePdf/Queries/ValidatePdfQueryValidator.cs

  - SwissQrCode/Queries/ExtractSwissQrCodeQuery.cs (Query + Handler)

  - SwissQrCode/Queries/ExtractSwissQrCodeQueryValidator.cs

AutoMapper integration:

  - Add Common/Mapping/MappingProfile.cs

  - Map PdfMetadata -> PdfValidationResult (domain -> DTO)

  - Map SwissQrCodeData -> SwissQrCodeExtractionResult (domain -> DTO)

  - Controllers now thin: pass request to MediatR, AutoMapper handles mapping

DTO improvements:

  - Rename: ValidatePdfResponse -> PdfValidationResult (business-friendly)

  - Rename: ExtractSwissQrCodeResponse -> SwissQrCodeExtractionResult

  - Support BOTH byte[] and Base64Pdf string (XOR validation)

  - Use modern C# 12 collection expressions

Code quality:

  - Use PascalCase for primary constructor parameters

  - Fix LoggingBehavior logging format

Deleted old structure:

  - Features/Documents/ValidatePdf/ (old horizontal structure)

  - Features/Documents/ExtractSwissQrCode/ (old horizontal structure)

  - Common/DTOs/{Request|Response} (replaced with {Result})

Result: Vertical slices, AutoMapper v16.2.0, thin controllers
This commit is contained in:
2026-07-07 19:01:05 +02:00
parent b460d8df39
commit d6e3a5fda1
16 changed files with 253 additions and 185 deletions

View File

@@ -8,16 +8,9 @@ namespace DocumentOperator.Application.Common.Behaviors;
/// MediatR Pipeline Behavior that logs requests and tracks performance
/// Executes AFTER ValidationBehavior, BEFORE Handler
/// </summary>
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> Logger) : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
@@ -25,21 +18,18 @@ public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest,
{
var requestName = typeof(TRequest).Name;
// Request Start
_logger.LogInformation("Handling {RequestName}: {@Request}", requestName, request);
// Performance Tracking
var stopwatch = Stopwatch.StartNew();
try
{
// Handler ausführen
var response = await next();
var response = await next(cancellationToken);
stopwatch.Stop();
// Request Success
_logger.LogInformation(
Logger.LogInformation(
"Handled {RequestName} in {ElapsedMs}ms",
requestName,
stopwatch.ElapsedMilliseconds
@@ -52,7 +42,7 @@ public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest,
stopwatch.Stop();
// Request Failed
_logger.LogError(
Logger.LogError(
ex,
"Error handling {RequestName} after {ElapsedMs}ms: {ErrorMessage}",
requestName,