Die Entwicklungsgeschwindigkeit und Produktivität von Programmierern kann abhängig von ihrem Niveau und den in Projekten verwendeten Technologien variieren. Es gibt keinen Standard für das Software-Design
Kunst und GOSTs, nur Sie entscheiden, wie Sie Ihr Programm entwickeln möchten. Eine der besten Möglichkeiten zur Verbesserung Ihrer Arbeitseffizienz ist die Anwendung des CQRS-Entwurfsmusters.
CQRS: Regular, Progressive Deluxe. — Regular CQRS, DD Planet - «.». Progressive Deluxe — .
: CQRS - , .
Onion
, CQRS, , .
«» :
— .
-, .
— .
: UI, .
, , .
«.». -, . , . , — . , , .
, — , . CQRS.
CQRS
CQRS (Command Query Responsibility Segregation)— , :
— ;
— , .
. , (tiers), .
, , . -, «.», :
;
;
;
;
.
CQRS , ( , ), , , , , /, .
, CQRS , .
ASP.NET Core 5.0, .
ASP.NET Core 5.0, :
MediatR— , Mediator, / .
FluentValidation— .NET, Fluent- - .
REST API CQRS
REST API:
get — ;
post, put, delete — .
MediatR:
, :
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
ConfigureServices Startup:
namespace CQRS.Sample
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddControllers();
...
}
}
}
, . , MediatR IRequest<TResponse>, .
namespace CQRS.Sample.Features
{
public class AddProductCommand : IRequest<Product>
{
/// <summary>
///
/// </summary>
public string Alias { get; set; }
/// <summary>
///
/// </summary>
public string Name { get; set; }
/// <summary>
///
/// </summary>
public ProductType Type { get; set; }
}
}
IRequestHandler<TCommand, TResponse>.
, , -, — .
namespace CQRS.Sample.Features
{
public class AddProductCommand : IRequest<Product>
{
/// <summary>
///
/// </summary>
public string Alias { get; set; }
/// <summary>
///
/// </summary>
public string Name { get; set; }
/// <summary>
///
/// </summary>
public ProductType Type { get; set; }
public class AddProductCommandHandler : IRequestHandler<AddProductCommand, Product>
{
private readonly IProductsRepository _productsRepository;
public AddProductCommandHandler(IProductsRepository productsRepository)
{
_productsRepository = productsRepository ?? throw new ArgumentNullException(nameof(productsRepository));
}
public async Task<Product> Handle(AddProductCommand command, CancellationToken cancellationToken)
{
Product product = new Product();
product.Alias = command.Alias;
product.Name = command.Name;
product.Type = command.Type;
await _productsRepository.Add(product);
return product;
}
}
}
}
, Action , IMediator . , ASP.Net Core . MediatR .
namespace CQRS.Sample.Controllers
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly ILogger<ProductsController> _logger;
private readonly IMediator _mediator;
public ProductsController(IMediator mediator)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
...
/// <summary>
///
/// </summary>
/// <param name="client"></param>
/// <param name="apiVersion"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesDefaultResponseType]
public async Task<IActionResult> Post([FromBody] AddProductCommand client, ApiVersion apiVersion,
CancellationToken token)
{
Product entity = await _mediator.Send(client, token);
return CreatedAtAction(nameof(Get), new {id = entity.Id, version = apiVersion.ToString()}, entity);
}
}
}
MediatR /, , , Middlewares ASP.Net Core . , .
FluentValidation.
FluentValidation :
dotnet add package FluentValidation.AspNetCore
Erstellen wir eine Pipeline zur Validierung:
namespace CQRS.Sample.Behaviours
{
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<ValidationBehaviour<TRequest, TResponse>> _logger;
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators,
ILogger<ValidationBehaviour<TRequest, TResponse>> logger)
{
_validators = validators;
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
string typeName = request.GetGenericTypeName();
_logger.LogInformation("----- Validating command {CommandType}", typeName);
ValidationContext<TRequest> context = new ValidationContext<TRequest>(request);
ValidationResult[] validationResults =
await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
List<ValidationFailure> failures = validationResults.SelectMany(result => result.Errors)
.Where(error => error != null).ToList();
if (failures.Any())
{
_logger.LogWarning(
"Validation errors - {CommandType} - Command: {@Command} - Errors: {@ValidationErrors}",
typeName, request, failures);
throw new CQRSSampleDomainException(
$"Command Validation Errors for type {typeof(TRequest).Name}",
new ValidationException("Validation exception", failures));
}
}
return await next();
}
}
}
Registrieren Sie es bei DI und fĂĽgen Sie die Initialisierung aller Validatoren fĂĽr FluentValidation hinzu.
namespace CQRS.Sample
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
...
}
}
}
Jetzt schreiben wir unseren Validator.
public class AddProductCommandValidator : AbstractValidator<AddProductCommand>
{
public AddProductCommandValidator()
{
RuleFor(c => c.Name).NotEmpty();
RuleFor(c => c.Alias).NotEmpty();
}
}
Dank der Funktionen von C #, FluentValidation und MediatR konnten wir unsere Team- / Anforderungslogik in einer einzigen Klasse zusammenfassen.
namespace CQRS.Sample.Features
{
public class AddProductCommand : IRequest<Product>
{
/// <summary>
///
/// </summary>
public string Alias { get; set; }
/// <summary>
///
/// </summary>
public string Name { get; set; }
/// <summary>
///
/// </summary>
public ProductType Type { get; set; }
public class AddProductCommandHandler : IRequestHandler<AddProductCommand, Product>
{
private readonly IProductsRepository _productsRepository;
public AddProductCommandHandler(IProductsRepository productsRepository)
{
_productsRepository = productsRepository ?? throw new ArgumentNullException(nameof(productsRepository));
}
public async Task<Product> Handle(AddProductCommand command, CancellationToken cancellationToken)
{
Product product = new Product();
product.Alias = command.Alias;
product.Name = command.Name;
product.Type = command.Type;
await _productsRepository.Add(product);
return product;
}
}
public class AddProductCommandValidator : AbstractValidator<AddProductCommand>
{
public AddProductCommandValidator()
{
RuleFor(c => c.Name).NotEmpty();
RuleFor(c => c.Alias).NotEmpty();
}
}
}
}
Dies vereinfachte die Arbeit mit der API erheblich und löste alle Hauptprobleme.
Das Ergebnis ist ein wunderschön gekapselter Code, der für alle Mitarbeiter verständlich ist. So können wir eine Person schnell in den Entwicklungsprozess einführen, Kosten und Zeit für dessen Implementierung reduzieren.
Die aktuellen Ergebnisse können auf GitHub eingesehen werden .