DDD验证没有抛出异常

时间:2017-06-19 17:25:54

标签: c# validation domain-driven-design

我正在尝试首次尝试使用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关注影响域,但我不想再复制所有验证两次,以便我可以确保命令不会失败,因为这会导致可维护性问题进一步下线(我发现自己的情况并试图解决!)

1 个答案:

答案 0 :(得分:2)

问题可能有点广泛,围绕建筑设计,这是你应该决定的事情,但无论如何我都会尝试和帮助 - 我不能帮助自己。

首先:这篇文章很可能暗示你对你的设计过于挑剔: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
    }

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