【问题标题】:Should my repository enforce validity of data, if so, where and how?我的存储库是否应该强制执行数据的有效性,如果是,在哪里以及如何执行?
【发布时间】:2009-04-01 00:48:47
【问题描述】:

我有一个像这样的存储库数据访问模式:

IRepository<T>
{
  .. query members ..
  void Add(T item);
  void Remove(T item);
  void SaveChanges();
}

想象一下我有一个用户存储库的场景,用户有一个唯一的用户名,如果我创建一个用户名存在的新用户(想象我有一个不检查的哑 ui 层),当我将它添加到存储库中,一切都很好.. 当我点击 SaveChanges 时,我的存储库尝试将项目保存到数据库中,我的数据库幸运地执行了这些规则,并且由于唯一键违规而将我抛出异常中止。

在我看来,通常这种验证是在存储库之上的层完成的,调用它的层知道他们应该确保这个规则,并且会预先检查和执行(希望在某种事务范围内避免种族,但在存在中等无知的情况下似乎并不总是可能的)。

我的存储库不应该执行这些规则吗?如果我的媒介是愚蠢的,例如没有任何完整性检查的平面数据库,会发生什么?

如果存储库正在验证这类事情,他们将如何以调用者可以准确识别问题所在的方式通知调用者违规行为,异常似乎是一种糟糕的处理方式,因为它们相对昂贵且很难专门针对特定的违规行为..

我一直在玩“Can”模式,例如.. CanAdd 和 Add,如果 can 返回冲突,add 将调用 CanAdd 并抛出无效操作异常。. CanAdd 还返回关于什么的冲突列表出错了..这样我可以开始通过堆栈堆叠这些例程..例如,上面的服务层也将有一个“Can”方法,该方法将返回存储库报告+它想要检查的任何其他违规行为(例如更复杂的业务规则,例如哪些用户可以调用特定的操作)。

数据验证是如此基础,但我觉得对于如何可靠地处理更高级的验证要求没有真正的指导。


编辑,另外在这种情况下,您如何处理存储库中实体的验证并通过更改跟踪进行更新。例如:

using (var repo = ...)
{
  var user = repo.GetUser('user_b');
  user.Username = 'user_a';

  repo.SaveChanges(); // boom!
}

正如您所想象的,这将导致异常.. 深入兔子洞,想象一下当我添加用户时我已经有了一个验证系统,然后我做了这样的事情:

using (var repo = ...)
{
  var newUser = new User('user_c');
  repo.Add(newUser); // so far so good.

  var otherUser = repo.GetUser('user_b');
  otherUser.Username = 'user_c';

  repo.SaveChanges(); // boom!
}

在这种情况下,在添加用户时进行验证是没有意义的,因为“下游”操作无论如何都会把我们搞砸,添加验证规则需要检查实际的持久性存储以及排队等待持久化的任何项目。

这仍然不能阻止之前的更改跟踪问题.. 那么现在我要开始验证保存更改调用了吗?看起来显然不相关的行为可能会发生大量违规行为。

也许我要求的是一个不切实际的完美安全网?

提前致谢, 斯蒂芬。

【问题讨论】:

    标签: validation


    【解决方案1】:

    理想的规则是你的每一层都应该是一个黑盒子,它们都不应该依赖于另一个层的验证。这背后的原因是数据库不知道用户界面,反之亦然。所以当 DB 抛出异常时,UI 必须具备 DB 知识(坏事)才能将其转换为 UI 层可以理解的东西,因此它最终可以将其转换为 用户 可以理解的东西。呃。

    不幸的是,对每一层进行验证也很困难。我的解决方案:要么将验证放在一个地方(可能是业务层),然后让其他层变得非常愚蠢。他们不会在其他地方检查任何东西。

    或者以抽象的方式将您的验证写入模型,然后从中生成所有验证。例如:

    String name;
    Description nameDesc = new Description("name",
            new MaxLength(20), new NotNull());
    

    这样,您可以编写检查描述内容的代码(生成代码甚至在运行时),并以很少的成本在每一层中进行验证,因为一个更改修复了所有层。

    [编辑] 为了验证,你只有这些情况:

    • 重复键
    • 超过一定限度
    • 低于某些限制
    • 空(未指定)
    • 格式错误(日期字段等)

    因此,您应该能够摆脱这些异常类,这些异常类具有对象、字段、旧值和新值以及特殊信息(例如被击中的限制)。所以我想知道你的许多异常类是从哪里来的。

    对于您的其他问题,这是……呃……two phase commit protocol“解决”了。我说“已解决”,因为在某些情况下协议会发生故障,根据我的经验,给用户“重试?”要好得多。对话或其他方式来解决问题,而不是在 TPC 上投入大量时间。

    【讨论】:

    • 嗨 Aaron,我目前的情况有点相似,我为每种违规类型生成不同的对象,所有这些违规都将返回给原始调用者(ui 层),其中 ui 层可以身份违规并为违规提供本地化错误消息.. 继续
    • ..问题是每个违规都是它自己的类,这背后的原因是我可能会产生一个“无效的用户名”异常,该异常具有可以格式化为的最小和最大长度属性本地化的错误消息..我似乎有一大群这些违规类.. cont
    • 此外,“可以”模式并不能保证该方法的成功,但我想这是可以理解的。从技术上讲,问“我可以这样做”和说……之间存在竞争条件。好吧,在可能发生违规的地方(但我可以接受这个小窗口)。
    • Aaron,您的帮助最大,非常感谢您分享您的知识和经验,帮助我合理化一个干净的解决方案。
    猜你喜欢
    • 2012-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多