La velocidad de desarrollo y la productividad de los programadores pueden variar según su nivel y las tecnologías utilizadas en los proyectos. No existe un estándar para el diseño de software
arts y GOSTs, solo tú eliges cómo desarrollarás tu programa. Una de las mejores formas de mejorar la eficiencia de su trabajo es aplicar el patrón de diseño CQRS.
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
Creemos un Pipeline para la validación:
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();
}
}
}
Y regístrelo con DI, agregue la inicialización de todos los validadores para FluentValidation.
namespace CQRS.Sample
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
...
}
}
}
Ahora escribamos nuestro validador.
public class AddProductCommandValidator : AbstractValidator<AddProductCommand>
{
public AddProductCommandValidator()
{
RuleFor(c => c.Name).NotEmpty();
RuleFor(c => c.Alias).NotEmpty();
}
}
Gracias a las capacidades de C #, FluentValidation y MediatR, pudimos encapsular nuestra lógica de solicitud / equipo dentro de una sola clase.
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();
}
}
}
}
Esto simplificó enormemente el trabajo con la API y resolvió todos los problemas principales.
El resultado es un hermoso código encapsulado que es comprensible para todos los empleados. Entonces, podemos introducir rápidamente a una persona en el proceso de desarrollo, reducir costos y tiempo para su implementación.
Los resultados actuales se pueden ver en GitHub .