プログラマーの開発速度と生産性は、プログラマーのレベルとプロジェクトで使用されるテクノロジーによって異なる場合があります。ソフトウェア設計の基準はありません
芸術とGOST、あなただけがあなたのプログラムをどのように開発するかを選択します。作業効率を向上させる最良の方法の1つは、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
検証用のパイプラインを作成しましょう。
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();
}
}
}
そしてそれをDIに登録し、FluentValidationのすべてのバリデーターの初期化を追加します。
namespace CQRS.Sample
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
...
}
}
}
それでは、バリデーターを書いてみましょう。
public class AddProductCommandValidator : AbstractValidator<AddProductCommand>
{
public AddProductCommandValidator()
{
RuleFor(c => c.Name).NotEmpty();
RuleFor(c => c.Alias).NotEmpty();
}
}
C#、FluentValidation、MediatRの機能のおかげで、チーム/リクエストロジックを単一のクラスにカプセル化することができました。
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();
}
}
}
}
これにより、APIでの作業が大幅に簡素化され、すべての主要な問題が解決されました。
その結果、すべての従業員が理解できる美しいカプセル化されたコードが得られます。そのため、開発プロセスに人をすばやく紹介し、その実装にかかるコストと時間を削減できます。
現在の結果はGitHubで表示できます。