我有一个简单的服务,可以通过用户 ID 返回 UserDto:GetUserById。 当用户不存在时,响应DTO返回异常。单个 ExceptionDto 用于所有错误。
因此,当返回 ExceptionDto 时,客户端并不真正知道服务器端到底发生了什么。但是,我需要我的客户将“NotFound”错误与所有其他错误区分开。另外,我想保持我的设计简单。
到目前为止,我正在考虑用 FindUsersById 替换 GetUserById 方法。其中 Find 方法将返回用户集合(集合 dto)。如果未找到用户,集合将为空。并且返回集合永远不会有多个元素(不允许重复)。
您同意吗?或者还有其他方法来处理这个问题吗?
每条评论更新:
- 服务永远不会返回 null 或抛出异常。
- 服务始终返回从基类 (DtoBase) 派生的 DTO 对象
- 每个 DTO 对象都与某种实体类型相关。每个实体都有分配的 ID(在我的示例中,我使用 long 作为 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 的构造函数。
响应类是不言自明的:
[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 类聚合操作响应信息,例如:如果有任何值、异常和警告。
最佳答案
如果未找到结果,或者由于某些验证规则(例如事件状态)导致数据出现问题,则不得抛出异常。如果没有适当的文档,消费者不知道要捕获哪个异常
,如果他们捕获错误的异常(尤其是一般异常
),他们的系统就会面临更大的危险。
但是,如果服务和消费者事先达成了某种协议(protocol),则服务将抛出异常或处理程序,但也有异常(exception)。
如果您有能力更改 DTO 结构及其使用方式,我建议您使用一个可以确定结果的通用基本结构
。如(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
对象)。然而,所需的对象可以为 null,因为它有一个 HasValue
属性来决定它是否为 null,并且结果的原因是已知的(通过 OperationMessage
)。
如果您只想返回一个集合,请不要返回集合。这会让消费者感到困惑,他们会认为该方法可以返回多个结果(因此重复的数据可能是正确的情况)。
也不返回空对象(空对象模式)。这又会让消费者困惑于是否找到结果。
如果您没有能力更改返回值,只需返回 null
并让使用者处理它。只要确保在代码中记录它即可。
使用集合
或数组处理通用结果的方式可能会有所不同。
参见Eric Lippert's关于令人烦恼的异常
的文章。
编辑:
根据您的设计,我不确定为什么您需要 Response
与 DtoBase
成为不同的实体并使用 Composition
。我认为 Response
继承 DtoBase
是很自然的,因为 DtoBase
实际上只是一个响应(除非它还有一个 Request
)。
其次,目前您可以使用BusinessWarnings
作为向用户提供有关未找到原因的信息。我自己更喜欢将其更改为 OperationMessage
,其状态为 Success
、Error
和 Warning
(也许 记录
)而不仅仅是警告。稍后,为了更轻松地访问,您可以将 Messages
公开为 SuccessMessages
、ErrorMessages
和 WarningMessages
。这可以被视为客户端日志记录,并且具有更多用途,而不仅仅是 BusinessWarnings
。
此外,我认为如果您将代码发布到代码审查堆栈交换,您会得到更好的响应。
关于design-patterns - DTO 和空结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21270608/