例如,假设我有PostsService
创建帖子:
public class PostsService : IPostsService
{
public bool Create(Post post)
{
if(!this.Validate(post))
{
return false;
}
try
{
this.repository.Add(post);
this.repository.Save();
}
catch(Exception e)
{
return false;
}
}
}
问题在于,如果在存储库操作期间抛出异常,则会被吞下。 Create()
返回false
,而消费者知道的所有内容都是Post
未添加,但不知道为什么。
相反,我想到的是ServiceResult
类:
public class ServiceResult
{
public bool Success { get; private set; }
public Exception Exception { get; private set; }
}
这是一个好的设计吗?或者我甚至需要向消费者报告这些错误?是否足以说“添加帖子时发生错误”。然后在服务中记录异常?
赞赏任何其他建议。
答案 0 :(得分:4)
我认为这取决于为什么的范围。在我看来,一些失败原因不应该与消费者有关,这些应该记录在服务中。另一方面,有些情况下应该告知消费者这些原因,因此除了记录它们之外,还应该以某种方式指出错误的原因。
例如,假设用户注册服务的一个非常常见的用例。您很可能拥有一个标识用户的唯一属性(用户句柄,电子邮件等)。这种唯一性必须由服务强制执行(也可能在数据库级别)。现在,如果消费者试图注册用户并且由于违反了约束而失败,则应该通知消费者这个原因(因此它可以尝试其他用户处理)。在大多数情况下,失败的验证都属于同一场景。
另一方面,如果存在某种内部数据库问题,则不应告知消费者细节(因为这是该服务的专有责任;并且消费者无论如何都不能做任何事情。)
考虑到这一点,我会说在很多情况下返回布尔值是不够的。因此,拥有某种ServiceResult
类型并不是一个坏主意。我不确定我是否会包含Exception
。但也许您可以创建某种特定于您的服务的ServiceExpection。有时错误代码也适用于此。
答案 1 :(得分:2)
是的,ServiceResult
类是一个好主意,但我不会包含异常,有时你想报告错误,但你不想/需要创建/抛出/捕获异常相反,我会包含一组错误消息或一个枚举类型,表明出现了什么问题,例如MembershipCreateStatus。
另外,不要抓住Exception,它可能会让臭虫很难找到。
答案 2 :(得分:1)
(免责声明:根据我的经验,我在环境之间暴露/消费服务方面做了很多工作,其中像WCF这样的内置功能并不总是有用,而且并不总能控制客户端消费服务。)
我的服务设计越来越多,我已经转向了请求/响应系统。 (实际上,我在这方面大量使用了agatha-rrsl库。)在那个设计中,我有一个BaseRequest
对象和一个BaseResponse
对象,所有请求和响应都是类型继承。
继续你的想法,ServiceResult
课程将作为这样的BaseResponse
的开头。它不一定需要实际的例外,但我倾向于包括的一些事情是:
bool
,即使是成功返回实际结果的请求。IList<>
个自定义Message
个对象,它们本身包含某种消息类型(信息,警告,错误等)以及消息文本(可能还有一个额外的用户 - 用于UI显示需求的友好消息文本,如果相关的堆栈跟踪等等。更进一步,我还想在BaseRequest
中加入一些内容,例如原始用户,主持人等。
这个想法是服务本身从不抛出原始异常。任何使用该服务的人都应该始终期望获得有效的响应,即使该响应表明某种类型的失败。它应该期望每次都能返回一些基本信息并知道如何处理这些信息。
答案 3 :(得分:1)
请记住,面向服务的体系结构的一个主要优点是解耦系统组件。调用层必须知道的服务越多,层的耦合就越紧密。遵循这个逻辑,最好让调用层尽可能少地了解错误,所有需要知道的是服务调用失败。
调用层真的是否需要知道服务失败的原因?它能真正做些什么呢?是否只是为了向用户显示更好的信息?在这种情况下,用户真的关心细节吗?很容易让用户想要了解更多信息,因为您和开发人员一样。但是开发人员应该从日志而不是最终用户消息中获取有关错误消息的信息。
答案 4 :(得分:1)
捕获所有异常是一个非常糟糕的主意。你只能抓住你可以合理处理的东西。你无法处理所有异常,所以你为什么要抓住它?为了防止堆栈跟踪?
如果你不希望你的用户看到一个可怕的堆栈跟踪,我会得到它,但你确实希望它死掉并死得很厉害,以便某些东西不会被破坏。
假设服务引发OutOfMemoryException,您刚刚抓住它,并且不知道您的应用程序是否像筛子一样泄漏内存或者是否正在不正确地分配大型对象。
这是一件坏事。
如果您不希望您的用户看到错误,那么您应该更改您的捕获:
catch (Exception e)
{
//send it to yourself, log it. Just don't swallow it.
LogErrorAndEmailDevTeam(e);
throw new SaveFailedException("We're sorry. \n" +
"It's not your fault, but something bad happened.\n\n" +
"We've been notified and will fix it as soon as possible.");
}
但更好的方法是让应用程序死于可怕的死亡,而不是捕获异常,以便在出现问题时快速了解。
您不希望您的应用程序继续处于损坏状态,但这就是catch (Exception)
所做的。你真的相信你可以处理抛出的任何东西吗?
答案 5 :(得分:0)
如果你正在使用WCF,我建议不要使用ServiceResult
或类似的东西来处理SOA层中的异常。
您应该为您的方法定义FaultContract
。这将允许使用代码来了解可抛出的异常类型,并使用传统的try/catch
块相应地处理它们。
这StackOverflow question has a full implementation of Fault Contracts。
你仍然可以让你的前端简单地说“发生错误”,但如果将来需要,使用错误合同将允许在代码的UI层上更好地处理错误。