Pola CQRS: teori dan praktek di ASP.Net Core 5

Kecepatan pengembangan dan produktivitas pemrogram dapat bervariasi tergantung pada level mereka dan teknologi yang digunakan dalam proyek. Tidak ada standar untuk desain perangkat lunak





seni dan GOST, hanya Anda yang memilih bagaimana Anda akan mengembangkan program Anda. Salah satu cara terbaik untuk meningkatkan efisiensi kerja Anda adalah dengan menerapkan pola desain CQRS. 





CQRS: Regular, Progressive Deluxe. โ€” Regular CQRS, DD Planet - ยซ.ยป. Progressive Deluxe โ€” .





: CQRS - , . 





Onion

, CQRS, , .





ยซยป :





  1. โ€” .





  2. -, .





  3. โ€” .





  4. : 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





Mari buat Pipeline untuk validasi:





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();
       }
   }
}
      
      



Dan daftarkan dengan DI, tambahkan inisialisasi semua validator untuk FluentValidation.





namespace CQRS.Sample
{
   public class Startup
   {
       ...
       public void ConfigureServices(IServiceCollection services)
       {
           ...
           services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
           services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
           ...
       }
   }
}
      
      



Sekarang mari tulis validator kita.





public class AddProductCommandValidator : AbstractValidator<AddProductCommand>
{
   public AddProductCommandValidator()
   {
       RuleFor(c => c.Name).NotEmpty();
       RuleFor(c => c.Alias).NotEmpty();
   }
}
      
      



Berkat kemampuan C #, FluentValidation, dan MediatR, kami dapat merangkum logika tim / permintaan kami dalam satu kelas.





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();
           }
       }
   }
}
      
      



Ini sangat menyederhanakan pekerjaan dengan API dan menyelesaikan semua masalah utama.





Hasilnya adalah kode enkapsulasi yang indah yang dapat dimengerti oleh semua karyawan. Jadi, kami dapat dengan cepat memperkenalkan seseorang ke dalam proses pengembangan, mengurangi biaya dan waktu untuk implementasinya. 





Hasil saat ini dapat dilihat di GitHub .








All Articles