2017-06-19 110 views
1

我试图第一次进入DDD,我问了一个关于批量导入here的问题,但我正在尝试为我的域模型应用验证。DDD验证没有抛出异常

本质上我想通过所有验证而不抛出异常,以便我可以通过Command对象内的CommandResult对象列表拒绝所有验证错误。虽然有些只是可配置的强制性现场检查,所以会在聚合之外进行处理,但也有业务规则,所以我不想复制验证逻辑,也不想陷入贫血模式以维持总是有效的实体口头禅。

我有些失落,所以认为这是最好的我问专家,如果这是我正确的事情开始之前,我开始进一步混淆水域!

要尝试并展示:

就拿下面,我们有相当简单的用户配置骨料,构造函数接受一个配置文件存在必需的信息。

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*/); 
    } 
} 

然后我有一个接受命令的对象,这将构建一个用户配置并保存到数据库的方法,但我想节省

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

+0

不幸的是我不清楚你想要什么。看起来你正在寻找一些关于如何构建解决方案的指导,这让我认为这是一个*太广泛的问题*。如我错了请纠正我。 – Alisson

+0

对不起,它似乎有点含糊。我会用一个模拟我面对的情况来更新这个问题,而这个情况应该会有所帮助! –

回答

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 
    } 

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

+0

谢谢你的回复:)我一直在看下面的文章:http://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/这似乎是一个可能的合适。这样,如果事先没有进行正确的检查(以保持始终有效的原则),它将在域模型中引发异常,并且我也可以将检查作为工厂类的一部分进行检查接受/拒绝命令的模型验证,如果接受,则构建一个有效的模型。这听起来像是一种不违反DDD基本原则的方法吗? –

+0

这听起来像又一个选项:)我个人喜欢我可以“信任”域的想法,所以我更愿意在设置域状态(填充对象)之前进行验证和清理。这个决定通常围绕着被调用的地方(服务类/域对象等)。正如我所说的那样,设计最终必须与您“坐在一起”。 –

+1

@StevenBrookes在同一个网站上有一个关于处理错误的更多想法的旧文章,请看http://enterprisecraftsmanship.com/2015/03/20/functional-c-handling-failures-input-errors。看看铁路定位规划的想法。 –