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:
@@ -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,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace DocumentOperator.Application.Common.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Request to extract Swiss QR Code from a PDF document.
|
||||
/// The QR code must be located on the last page of the document.
|
||||
/// </summary>
|
||||
/// <param name="References">Array of reference strings to pass through in the response</param>
|
||||
/// <param name="Base64Pdf">PDF document encoded as Base64 string</param>
|
||||
/// <example>
|
||||
/// {
|
||||
/// "references": ["REF-001", "REF-002"],
|
||||
/// "base64Pdf": "JVBERi0xLjQK..."
|
||||
/// }
|
||||
/// </example>
|
||||
public record ExtractSwissQrCodeRequest(
|
||||
IReadOnlyList<string> References,
|
||||
string Base64Pdf
|
||||
);
|
||||
@@ -4,12 +4,12 @@ namespace DocumentOperator.Application.Common.DTOs;
|
||||
/// Response mit PDF-Metadaten
|
||||
/// </summary>
|
||||
/// <param name="PageCount">Anzahl der Seiten</param>
|
||||
/// <param name="FileSizeBytes">Dateigröße in Bytes</param>
|
||||
/// <param name="FileSizeMB">Dateigröße in MB (gerundet auf 2 Dezimalstellen)</param>
|
||||
/// <param name="FileSizeBytes">Dateigröße in Bytes</param>
|
||||
/// <param name="FileSizeMB">Dateigröße in MB (gerundet auf 2 Dezimalstellen)</param>
|
||||
/// <param name="PdfVersion">PDF-Version (z.B. "1.4")</param>
|
||||
/// <param name="HasAttachments">Hat das PDF Anhänge?</param>
|
||||
/// <param name="AttachmentCount">Anzahl der Anhänge</param>
|
||||
public record ValidatePdfResponse(
|
||||
/// <param name="HasAttachments">Hat das PDF Anhänge?</param>
|
||||
/// <param name="AttachmentCount">Anzahl der Anhänge</param>
|
||||
public record PdfValidationResult(
|
||||
int PageCount,
|
||||
long FileSizeBytes,
|
||||
double FileSizeMB,
|
||||
@@ -29,7 +29,7 @@ namespace DocumentOperator.Application.Common.DTOs;
|
||||
/// }
|
||||
/// }
|
||||
/// </example>
|
||||
public record ExtractSwissQrCodeResponse(
|
||||
public record SwissQrCodeExtractionResult(
|
||||
IReadOnlyList<string> References,
|
||||
SwissQrCodeDataDto QrCodeData
|
||||
);
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace DocumentOperator.Application.Common.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Request für PDF-Validierung
|
||||
/// </summary>
|
||||
/// <param name="Base64Pdf">Base64-encodiertes PDF-Dokument</param>
|
||||
public record ValidatePdfRequest(string Base64Pdf);
|
||||
@@ -0,0 +1,24 @@
|
||||
using AutoMapper;
|
||||
using DocumentOperator.Application.Common.DTOs;
|
||||
using DocumentOperator.Domain.Models.ValueObjects;
|
||||
using DocumentOperator.Domain.ValueObjects;
|
||||
|
||||
namespace DocumentOperator.Application.Common.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// AutoMapper profile for mapping domain entities to DTOs
|
||||
/// </summary>
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
// PdfMetadata -> PdfValidationResult
|
||||
CreateMap<PdfMetadata, PdfValidationResult>();
|
||||
|
||||
// SwissQrCodeData -> SwissQrCodeDataDto
|
||||
CreateMap<SwissQrCodeData, SwissQrCodeDataDto>();
|
||||
|
||||
// AddressData -> AddressDataDto
|
||||
CreateMap<AddressData, AddressDataDto>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user