2014-01-21 41 views
2

我有一个简单的服务,通过用户ID返回UserDto:GetUserById。 当用户不存在时,响应DTO将返回一个异常。单一ExceptionDto用于所有错误。DTOs和空结果

因此,当ExceptionDto返回时,客户端并不真正知道服务器端发生了什么。但是,我需要我的客户端将“NotFound”错误与其他所有错误区分开来。另外,我想保持简单的设计。

到目前为止,我正在考虑用FindUsersById替换GetUserById方法。 Find方法将返回一组用户(collection dto)。如果找不到用户,集合将为空。并且返回集合永远不会有多个元素(不允许重复)。

您是否同意或有其他方法来处理?每评论


UPDATE:

  • 服务决不会返回null或抛出异常。
  • 服务总是返回从基类(DtoBase)
  • 每个DTO目的涉及一些实体类型派生DTO对象。每个实体都分配了ID(在我的示例中,我使用长ID为,但Response类可以设为通用)。

DtoBase类:

[DataContract] 
public abstract class DtoBase 
    : IDtoResponseEnvelop 
{ 
    [DataMember] 
    private readonly Response _responseInstance = new Response(); 

    protected DtoBase() 
    {} 

    protected DtoBase(long entityId) 
    { 
     _responseInstance = new Response(entityId); 
    } 

    #region IDtoResponseEnvelop Members 

    public Response Response 
    { 
     get { return _responseInstance; } 
    } 

    #endregion 
} 

每个DTO目的涉及一些实体。如果有一些结果,应该调用具有entityId的构造函数。

Response类是不言自明:

[DataContract] 
public class Response 
{   
    #region Constructors 

    public Response():this(0){} 

    public Response(long entityId) 
    { 
     _entityIdInstance = entityId; 
    } 

    #endregion   

    #region Private Serializable Members 

    [DataMember] 
    private BusinessExceptionDto _businessExceptionInstance; 

    [DataMember] 
    private readonly IList<BusinessWarning> _businessWarningList = new List<BusinessWarning>(); 

    [DataMember] 
    private readonly long _entityIdInstance; 

    #endregion 

    #region Public Methods 

    public void AddBusinessException(BusinessException exception) 
    { 
     _businessExceptionInstance = new BusinessExceptionDto(exception.ExceptionType, exception.Message, exception.StackTrace); 
    } 

    public void AddBusinessWarnings(IEnumerable<BusinessWarning> warnings) 
    { 
     warnings.ToList().ForEach(w => _businessWarningList.Add(w)); 
    } 

    #endregion 

    #region Public Getters 

    public bool HasWarning 
    { 
     get { return _businessWarningList.Count > 0; } 
    } 

    public IEnumerable<BusinessWarning> BusinessWarnings 
    { 
     get { return new ReadOnlyCollection<BusinessWarning>(_businessWarningList); } 
    } 

    public long EntityId 
    { 
     get { return _entityIdInstance; } 
    } 

    public bool HasValue 
    { 
     get { return EntityId != default(long); } 
    } 

    public bool HasException 
    { 
     get { return _businessExceptionInstance != null; } 
    } 

    public BusinessExceptionDto BusinessException 
    { 
     get { return _businessExceptionInstance; } 
    } 

    #endregion 
} 

基本上,Response类聚集体的操作的响应信息,例如:如果有任何值,异常和警告。

+0

“如果没有找到用户,集合将为空,并且返回集合永远不会有多个元素” - 欢迎来到[也许monad!](http://en.wikipedia.org/wiki/Monad_(functional_programming )#The_Maybe_monad) – MattDavey

+0

当你说'service'时,你是指DDD定义中的'service'还是'web service'中的'service'? –

+0

在DDD中。使用DTO的服务 – Tenek

回答

1

如果找不到结果或者某些数据是由某些验证规则引起的(例如激活状态),则不得抛出异常。没有适当的文件,消费者不知道哪个exception赶上,如果他们发现错误的异常(特别是一般的Exception),他们会危及他们的系统更多。

虽然有一个例外,如果服务和消费者之间做出了某种约定,那么预先将异常从服务中抛出或处理。

如果你有改变DTO结构和消费方式的奢望,我建议你有一个能够确定结果的generic basic structure。如(在C#):

public class GenericQueryResult<T>{ 
    public string OperationMessage{ get; set; } 
    public bool HasValue{ get; set; } 
    public T Value{ get; set; } // or Result is fine 
} 

这种方式,消费者可以理解,而不从服务的结果将返回一个对象,它永远不会null(你不返回null GenericQueryResult对象)的任何文件。但是,所需的对象可以为空,因为它具有HasValue属性以决定它是否为空,并且结果的原因是已知的(由OperationMessage)。

如果您打算只返回一个集合,则不要返回集合。它会让消费者困惑,他们会认为该方法可以返回多个结果(因此重复数据可能是正确的情况)。

也不要返回空对象(空对象模式)。再次,它会混淆消费者决定是否找到结果。

如果您没有更改退货价值的奢侈品,只需返回null并让消费者处理它。只要确保在代码中记录它。

虽然你用Collections或数组处理通用结果的方式可能会有所不同。

请参阅Eric Lippert's文章vexing exception

编辑:

根据您的设计,我不清楚为什么你需要Response是不同的实体DtoBase和使用Composition。我认为Response继承DtoBase是自然的,因为DtoBase实际上只是一个响应(除非它也有一个Request)。

其次,目前您可以使用BusinessWarnings作为向用户提供关于未找到原因的信息。我自己更愿意将其更改为OperationMessage,其状态为Success,ErrorWarning(也可能是Logging),而不仅仅是警告。稍后为了更容易访问,您可以将Messages公开为​​,ErrorMessagesWarningMessages。这可以被视为客户端日志记录,具有更多的用途,而不仅仅是BusinessWarnings

此外,我认为如果您将代码发布到代码审阅堆栈交换中,您会得到更好的响应。

+0

我的服务不会抛出异常,但总是返回DTO。对不起,我在第一篇文章中并不清楚。 我的基DTO类中唯一缺少的是HasValue属性。我更新了我的帖子,可否请检查一下,看看我们是否在同一页面上。谢谢! – Tenek

+0

感谢您的更新。我喜欢将单独的响应对象作为响应相关信息的容器。例如,如果DTO具有HasValue属性,则不会发生冲突。 对于空结果我有HasValue属性。警告(在我的情况下)只是提示性消息。例如,“ID为1的用户已被删除”或“未找到ID为1的用户”。 我从来没有使用代码到代码审查,我会检查出来! – Tenek

0

如果您的业务意图是尝试检索单个用户,那么我会坚持使用GetUserById()方法。但是,找不到单个项目的标准行为是返回null

如果您试图获取多个项目,则标准将返回一个空集合(如果没有找到)(尝试获取多个项目时从不返回null)。

这样,任何人直接调用您的服务谁收到null响应,将知道用户没有找到。

对于特殊情况,您最好保留例外情况。只要让他们泡到顶端。在整个应用程序中全局处理任何异常情况,以阻止他们完全清除它,然后向用户显示“发生了什么不良事件”消息。

使用类似ELMAH(如果您使用的是.NET),所以您的用户遇到的任何异常也会记录下来,供您在以后定期进行调查。然后想法是找出哪些异常可以通过一些先发制人的检查来避免,或者修复一个错误,哪些错误是不可恢复的,并且最好是单独处理(即最好使用“发生错误的事情”消息来处理)。

+0

这是最常用的方法,但是我一直以这种方式使用'null'出现问题。这似乎是重载'空'具有一些额外的含义。它假定程序员将以某种特定的方式解释'null'响应。像Jim Barrows所建议的monad使得这个更加明确。 – MattDavey

1

在Functional Java库(http://www.functionaljava.org/)中,您可以使用&和Option类。所以,你可以这样做:

Either<Error, Result> findById(...) 

如果你的查询可以返回什么:

Either<Error, Option<Result>> findById(...) 

Java的模板语法使它有点丑陋,但它确实表达你的意图非常好,我认为。

注意:几种功能语言都支持Option /或者,Haskell也可能会这样称呼它......在您的编程语言中查找等价物。

+0

+1这是要走的路。 Fendys的回答基本上是说同样的事情的一种冗长的方式。值得一提的是,这个monad可能被认为是一个零或一个元素的集合,就像问题中描述的OP一样。我希望这个答案上升到顶端:) – MattDavey