【问题标题】:DDD validation without throwing exceptions不抛出异常的 DDD 验证
【发布时间】:2017-06-19 17:25:54
【问题描述】:

我正在尝试第一次涉足 DDD,我问了一个关于批量导入的问题 here,但我正在兜圈子,试图为我的域模型应用验证。

基本上,我想在不引发异常的情况下运行所有​​验证,以便我可以通过 Command 对象内的 CommandResult 对象列表拒绝带有所有验证错误的命令。虽然有些只是可配置的强制性字段检查,因此将在聚合之外处理,但也有业务规则,所以我不想重复验证逻辑,也不想通过移动所有内容陷入贫血模型在聚合之外保持实体的永远有效的口头禅。

我有点不知所措,所以我认为最好在开始进一步搅浑水之前询问专家我是否正确地处理事情!

尝试和演示:

看下面,我们有相当简单的 UserProfile 聚合,构造函数获取配置文件存在所需的最少信息。

 public class UserProfile : AggregateRoot
    {
        public Guid Id {get; private set; }
        public Name Name {get private set;}
        public CardDetail PaymentInformation {get; private set;}



      public UserProfile(Guid id, Name name, CardDetail paymentInformation)
        {
            Name = name;
            PaymentInformation = paymentInformation;
        }

    }

public class CardDetail : ValueObject
{
    public string Number {get; private set;}
    public string CVC {get; private set; }
    public DateTime? IssueDate {get; private set;}
    public DateTime ExpiryDate {get;private set;}

    public CardDetail(string number, string cvc, DateTime? issueDate, DateTime expiryDate)
    {
        if(!IsValidCardNumber(number))
        {
            /*Do something to say details invalid, but not throw exception, possibly?*/
        }
        Number = number;
        CVC = cvc;
        IssueDate = issueDate



        ExpiryDate = expiryDate;

    }

    private bool IsValidCardNumber(string number)
    {
        return Regex.IsMatch(/*regex for card number*/);
    }
}

然后我有一个接受命令对象的方法,它将构造一个 UserProfile 并保存到数据库,但我想在保存之前进行验证

public void CreateProfile(CreateProfileCommand command)
{
    var paymentInformation = new CardDetail(command.CardNumber, command.CardCVC, command.CardIssueDate, command.CardExpiryDate)

    var errors = /* list of errors added to from card detail validation, possibly? */

    var profile = new UserProfile(/* pass args, add to errors? */

    if(errors.Any())
    {
        command.Results.Add(errors.Select(x => new CommandResult { Severity = Severity.Error, Message = x.Message });
        return;
    }

    /* no errors, so continue to save */

}

现在,我可以处理异常并将它们添加到命令结果中,但这似乎很昂贵,并且肯定违反了允许异常控制流的规则?但另一方面,我想保持实体和值对象的有效性,所以我发现自己有点墨守成规!

此外,在上面的示例中,可以从创建屏幕手动导入或完成配置文件,但用户应该收到所有错误消息,而不是按出现的顺序收到每条错误消息。在我正在处理的应用程序中,应用的规则有点复杂,但想法是一样的。我知道我不应该让 UI 问题影响域本身,但我不想再重复所有验证两次,这样我就可以确保命令不会失败,因为这会导致可维护性问题更进一步(我发现自己正试图解决的情况!)

【问题讨论】:

  • 不幸的是,我不清楚你想要什么。看起来您正在寻找有关如何构建解决方案的指导,这让我认为这是一个过于宽泛的问题。如果我错了,请纠正我。
  • 抱歉,好像有点含糊。我将用一个模仿我所面临情况的示例来更新问题,希望对您有所帮助!

标签: c# validation domain-driven-design


【解决方案1】:

这个问题可能有点宽泛,而且围绕着建筑设计,这是你应该决定的,但无论如何我都会尽力提供帮助 - 我无法帮助自己。

首先:这是一篇很棒的文章,可能已经暗示您对自己的设计过于挑剔了:http://jeffreypalermo.com/blog/the-fallacy-of-the-always-valid-entity/

您需要决定系统处理验证的方式。

也就是说,您是否想要一个域绝对不会导致一致性失败的系统?然后,您可能需要额外的类来清理您拥有的任何命令并在您接受或拒绝对域(卫生层)的更改之前验证它们。或者,如那篇文章中那样,它可能表明处理特定情况需要完全不同类型的对象。 (类似于不符合当前规则的遗留数据)

当出现严重错误时,域抛出异常是否可以接受?然后丢弃当前聚合(甚至当前上下文)中的所有更改并通知用户。

如果您正在寻找一个和平的中间解决方案,也许可以考虑这样的事情:

public OperationResult UpdateAccount(IBankAccountValidator validator, IAccountUpdateCommand newAccountDetails)
    {
        var result = validator.Validate(newAccountDetails);
        if(result.HasErrors)
        {
            result.AddMessage("Could not update bank account", Severity.Error);
            return result;
        }

        //apply further logic here

        //return success
    }

现在您可以将所有验证逻辑放在一个单独的类中,但您必须通过双重调度传递它并调用,并且您将在每个调用中添加如上所示的结果处理。 您必须真正决定哪种风格对您/团队来说是可以接受的,以及从长远来看哪些是可维护的。

【讨论】:

  • 感谢您的回复 :) 我一直在看以下文章:enterprisecraftsmanship.com/2016/09/13/validation-and-ddd 这似乎很合适。这样,如果事先没有进行正确的检查,它将在域模型中引发异常(以保持始终有效的原则),并且我也可以将检查作为可以使用的工厂类的一部分进行接受/拒绝命令的模型验证,如果接受,则构建有效模型。这听起来像是一种不违反 DDD 基本原则的方法吗?
  • 这听起来像是另一种选择 :) 我个人喜欢我可以“信任”的域的想法,所以我更喜欢在设置域状态(填充对象)之前验证和清理。决定通常围绕在哪里调用(服务类/域对象等)。正如我所说,设计最终必须与您“正确”。
  • @StevenBrookes 在同一站点上查看这篇较早的文章,了解有关处理错误enterprisecraftsmanship.com/2015/03/20/… 的更多想法。看看铁路导向编程的想法。
  • 我确实喜欢验证器模式的外观,但我很难理解其中与 DDD 相关的部分内容。 DDD 规定聚合负责强制执行不变量。如果验证被加急到验证器,它如何满足该原则?
  • 根据我上面的例子,实体仍然负责调用验证器。因此,实体没有明确包含验证逻辑,但仍然通过在将更改应用于其内部状态之前调用该验证器来执行检查。作为一些额外的命令,我将命令添加为接口,因此域对象的状态不能从外部修改(封装仍然存在),它可以决定什么是有效的以及允许应用哪些更改)-对不起回复晚了,但我被困在了会议上。没有互联网。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
  • 1970-01-01
  • 2014-07-27
  • 2016-05-05
  • 1970-01-01
  • 1970-01-01
  • 2013-06-29
相关资源
最近更新 更多