我有一个存储库数据访问模式,如下所示:
IRepository<T>
{
.. query members ..
void Add(T item);
void Remove(T item);
void SaveChanges();
}
想象一下我有一个用户存储库的场景,用户拥有一个唯一的用户名,如果我创建一个存在用户名的新用户(想象我有一个不检查的哑ui层),当我将它添加到存储库,一切都很好..当我点击SaveChanges时,我的存储库尝试将项目保存到数据库,我的数据库幸运地执行这些规则并且由于唯一的密钥违规而抛弃了中止的异常。 / p>
在我看来,通常这个验证是在存储库的ABOVE层完成的,调用它的层知道它们应该确保这个规则,并且会预先检查和执行(希望在某种事务范围内避免比赛,但似乎并不总是存在中等无知。
我的存储库不应该强制执行这些规则吗?如果我的媒体是愚蠢的,例如没有任何完整性检查的平面数据库会发生什么?
如果存储库正在验证这些类型的东西,他们如何通过调用者可以准确识别出错的方式通知呼叫者有关违规的情况,异常似乎是处理这种情况的一种不好的方式,因为它们相对昂贵并且是很难专注于特定的违规行为..
我一直在使用'Can'模式,例如.. CanAdd with Add,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!
}
在这种情况下,在添加用户时进行验证是没有意义的,因为“下游”操作无论如何都会让我们烦恼,添加验证规则需要检查实际的持久性存储以及排队等待的所有项目。
这仍然无法阻止之前的更改跟踪问题..所以现在我是否开始验证保存更改调用?看起来很可能会发生大量违规事件,这些违规事件可能会发生在几乎不相关的行为中。
也许我要求一个不切实际,完美的安全网?
提前致谢, 斯蒂芬。
答案 0 :(得分:2)
理想的规则是每个图层都应该是一个黑盒子,它们都不应该依赖于另一层的验证。这背后的原因是DB不知道UI,反之亦然。因此,当数据库抛出异常时,UI必须具有DB知识(坏事)才能将其转换为UI层可以理解的内容,因此它最终可以将其转换为用户可以理解的内容。啊。
不幸的是,在每一层上进行验证也很困难。我的解决方案:将验证放在一个地方(可能是业务层),并使其他层真正愚蠢。他们不会在其他地方检查任何事情。
或者以抽象的方式将您的验证写入模型,然后从中生成所有验证。例如:
String name;
Description nameDesc = new Description("name",
new MaxLength(20), new NotNull());
通过这种方式,您可以编写检查描述内容的代码(生成代码甚至在运行时)并在每个层中进行验证,而且成本很低,因为一个更改修复了所有层。
[编辑]为了验证,您只有这些情况:
所以你应该能够逃脱这些具有object,field,old和amp; new值以及特殊信息(例如被击中的限制)的异常类。所以我想知道你的许多异常类来自何处。
对于你的另一个问题,这是......呃... two phase commit protocol“解决”了。我说“已解决”了,因为在协议发生故障的情况下,根据我的经验,给用户“重试”会更好吗?对话或其他一些方法来解决问题而不是花费大量时间进入TPC。