- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我一直在读到异常应该只用于“异常”的东西,而不是用来控制程序的流程。然而,对于 CQS 实现,这似乎是不可能的,除非我开始修改实现来处理它。我想展示我是如何实现这个的,看看这是否真的很糟糕。我正在使用装饰器,因此命令无法返回任何内容(异步任务除外),因此 ValidationResult 是不可能的。让我知道!
此示例将使用 ASP.NET MVC
Controller :(api)
[Route(ApiConstants.ROOT_API_URL_VERSION_1 + "DigimonWorld2Admin/Digimon/Create")]
public class CreateCommandController : MetalKidApiControllerBase
{
private readonly IMediator _mediator;
public CreateCommandController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task Post([FromBody]CreateCommand command) =>
await _mediator.ExecuteAsync(command);
}
public class CommandHandlerExceptionDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _commandHandler;
private readonly ILogger _logger;
private readonly IUserContext _userContext;
public CommandHandlerExceptionDecorator(ICommandHandler<TCommand> commandHandler, ILogger logger,
IUserContext userContext)
{
Guard.IsNotNull(commandHandler, nameof(commandHandler));
Guard.IsNotNull(logger, nameof(logger));
_commandHandler = commandHandler;
_logger = logger;
_userContext = userContext;
}
public async Task ExecuteAsync(TCommand command, CancellationToken token = default(CancellationToken))
{
try
{
await _commandHandler.ExecuteAsync(command, token).ConfigureAwait(false);
}
catch (BrokenRuleException)
{
throw; // Let caller catch this directly
}
catch (UserFriendlyException ex)
{
await _logger.LogAsync(new LogEntry(LogTypeEnum.Error, _userContext,
"Friendly exception with command: " + typeof(TCommand).FullName, ex, command)).ConfigureAwait(false);
throw; // Let caller catch this directly
}
catch (NoPermissionException ex)
{
await _logger.LogAsync(new LogEntry(LogTypeEnum.Error, _userContext,
"No Permission exception with command: " + typeof(TCommand).FullName, ex, command)).ConfigureAwait(false);
throw new UserFriendlyException(CommonResource.Error_NoPermission); // Rethrow with a specific message
}
catch (ConcurrencyException ex)
{
await _logger.LogAsync(new LogEntry(LogTypeEnum.Error, _userContext,
"Concurrency error with command: " + typeof(TCommand).FullName, ex, command)).ConfigureAwait(false);
throw new UserFriendlyException(CommonResource.Error_Concurrency); // Rethrow with a specific message
}
catch (Exception ex)
{
await _logger.LogAsync(new LogEntry(LogTypeEnum.Error, _userContext,
"Error with command: " + typeof(TCommand).FullName, ex, command)).ConfigureAwait(false);
throw new UserFriendlyException(CommonResource.Error_Generic); // Rethrow with a specific message
}
}
}
public class CommandHandlerValidatorDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _commandHandler;
private readonly IEnumerable<ICommandValidator<TCommand>> _validators;
public CommandHandlerValidatorDecorator(
ICommandHandler<TCommand> commandHandler,
ICollection<ICommandValidator<TCommand>> validators)
{
Guard.IsNotNull(commandHandler, nameof(commandHandler));
Guard.IsNotNull(validators, nameof(validators));
_commandHandler = commandHandler;
_validators = validators;
}
public async Task ExecuteAsync(TCommand command, CancellationToken token = default(CancellationToken))
{
var brokenRules = (await Task.WhenAll(_validators.AsParallel()
.Select(a => a.ValidateCommandAsync(command, token)))
.ConfigureAwait(false)).SelectMany(a => a).ToList();
if (brokenRules.Any())
{
throw new BrokenRuleException(brokenRules);
}
await _commandHandler.ExecuteAsync(command, token).ConfigureAwait(false);
}
}
public class CreateCommandValidator : CommandValidatorBase<CreateCommand>
{
private readonly IDigimonWorld2ContextFactory _contextFactory;
public CreateCommandValidator(IDigimonWorld2ContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
protected override void CreateRules(CancellationToken token = default(CancellationToken))
{
AddRule(() => Validate.If(string.IsNullOrEmpty(Command.Name))
?.CreateRequiredBrokenRule(DigimonResources.Digipedia_CreateCommnad_Name, nameof(Command.Name)));
AddRule(() => Validate.If(Command.DigimonTypeId == 0)
?.CreateRequiredBrokenRule(DigimonResources.Digipedia_CreateCommnad_DigimonTypeId,
nameof(Command.DigimonTypeId)));
AddRule(() => Validate.If(Command.RankId == 0)
?.CreateRequiredBrokenRule(DigimonResources.Digipedia_CreateCommnad_RankId, nameof(Command.RankId)));
AddRule(async () =>
{
using (var context = _contextFactory.Create(false))
{
return Validate.If(
!string.IsNullOrEmpty(Command.Name) &&
await context.Digimons
.AnyAsync(a => a.Name == Command.Name, token)
.ConfigureAwait(false))
?.CreateAlreadyInUseBrokenRule(DigimonResources.Digipedia_CreateCommnad_Name, Command.Name,
nameof(Command.Name));
}
});
}
}
public class CreateCommandValidatorHandler : ICommandHandler<CreateCommand>
{
private const int ExpectedChangesCount = 1;
private readonly IDigimonWorld2ContextFactory _contextFactory;
private readonly IMapper<CreateCommand, DigimonEntity> _mapper;
public CreateCommandValidatorHandler(
IDigimonWorld2ContextFactory contextFactory,
IMapper<CreateCommand, DigimonEntity> mapper)
{
_contextFactory = contextFactory;
_mapper = mapper;
}
public async Task ExecuteAsync(CreateCommand command, CancellationToken token = default(CancellationToken))
{
using (var context = _contextFactory.Create())
{
var entity = _mapper.Map(command);
context.Digimons.Add(entity);
await context.SaveChangesAsync(ExpectedChangesCount, token).ConfigureAwait(false);
}
}
}
internal static class ErrorConfiguration
{
public static void Configure(
IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IConfigurationRoot configuration)
{
loggerFactory.AddConsole(configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>()?.Error;
context.Response.StatusCode = GetErrorStatus(error);
context.Response.ContentType = "application/json";
var message = GetErrorData(error);
await context.Response.WriteAsync(message, Encoding.UTF8);
});
});
}
private static string GetErrorData(Exception ex)
{
if (ex is BrokenRuleException brokenRules)
{
return JsonConvert.SerializeObject(new
{
BrokenRules = brokenRules.BrokenRules
});
}
if (ex is UserFriendlyException userFriendly)
{
return JsonConvert.SerializeObject(new
{
Message = userFriendly.Message
});
}
return JsonConvert.SerializeObject(new
{
Message = MetalKid.Common.CommonResource.Error_Generic
});
}
private static int GetErrorStatus(Exception ex)
{
if (ex is BrokenRuleException || ex is UserFriendlyException)
{
return (int)HttpStatusCode.BadRequest;
}
return (int)HttpStatusCode.InternalServerError;
}
}
public class BrokenRule
{
public string RuleMessage { get; set; }
public string Relation { get; set; }
public BrokenRule() { }
public BrokenRule(string ruleMessage, string relation = "")
{
Guard.IsNotNullOrWhiteSpace(ruleMessage, nameof(ruleMessage));
RuleMessage = ruleMessage;
Relation = relation;
}
}
[Route(ApiConstants.ROOT_API_URL_VERSION_1 + "DigimonWorld2Admin/Digimon/Create")]
public class CreateCommandController : MetalKidApiControllerBase
{
private readonly IMediator _mediator;
private readonly ICreateCommandValidator _validator;
public CreateCommandController(IMediator mediator, ICreateCommandValidator validator)
{
_mediator = mediator;
_validator = validator
}
[HttpPost]
public async Task<IHttpResult> Post([FromBody]CreateCommand command)
{
var validationResult = _validator.Validate(command);
if (validationResult.Errors.Count > 0)
{
return ValidationHelper.Response(validationResult);
}
await _mediator.ExecuteAsync(command);
return Ok();
}
}
[Route(ApiConstants.ROOT_API_URL_VERSION_1 + "DigimonWorld2Admin/Digimon/Create")]
public class CreateCommandController : MetalKidApiControllerBase
{
private readonly IResultMediator _mediator;
public CreateCommandController(IResultMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<IHttpAction> Post([FromBody]CreateCommand command) =>
await _mediator.ExecuteAsync(command);
}
Task ExecuteAsync(TCommand, CancellationToken token);
最佳答案
我的命令使用了非常相似的模式,基于 MediatR来自 Jimmy Bogard(使用管道功能在我的处理程序周围添加多个装饰器),并使用 Fluent Validation对于验证器。
我和你经历了类似的思考过程——我的验证器抛出异常(它们以与你类似的方式被捕获,在 MVC 的顶部),但有很多人会告诉你这不应该做——尤其是我最喜欢的科技预言机 Martin Fowler
一些想法:
关于c# - 验证引发异常的 CQS 系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46157174/
这个问题听起来我可能很蠢,但我真的很困惑。创建 command、query、commandhanlder、queryhandler 和 repositories 并使用依赖注入(inject)来解析基
在我的网络应用程序中,我正在跟踪页面的查看次数。 现在, Controller 中的操作向数据层发出命令以在返回查询结果之前增加模型上的 View 计数。 此操作似乎违反了命令-查询-分离的规则,因为
我正在考虑为我的 ASP.NET MVC 网站应用 CQS,但这是一件非常简单的事情。我不是指 CQRS,因为我想对查询和命令部分使用相同的数据源,因此我不需要事件溯源和其他更复杂的模式。 所以,我的
CQS 原则 (https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) 规定命令应返回 void。 对于异步方法的建议是永远不要
我一直在读到异常应该只用于“异常”的东西,而不是用来控制程序的流程。然而,对于 CQS 实现,这似乎是不可能的,除非我开始修改实现来处理它。我想展示我是如何实现这个的,看看这是否真的很糟糕。我正在使用
维基百科对 command query separation 的定义, 据称 More formally, methods should return a value only if they are
在 DDD 上观看了 Greg Yound 的这个视频 http://www.infoq.com/interviews/greg-young-ddd 我想知道当内存发生变化时,如何使用 DDD 实现命
CQS 架构模式背后的原则是将查询和命令分成不同的路径。理想情况下,您的持久性存储可以读/写分区,但在我的情况下,有一个单一的规范化数据库。 如果您使用的是 ORM(在我的例子中是 NHibernat
我正在学习什么是CQRS模式,并了解到还有CQS模式。 当我尝试搜索时,我发现了很多关于CQRS的图表和信息,但没有找到太多关于CQS的信息。 CQRS 模式中的关键点 在 CQRS 中,有一种要写入
我是一名优秀的程序员,十分优秀!