design-patterns - DTO 和空结果

标签 design-patterns domain-driven-design dto

我有一个简单的服务,可以通过用户 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关于令人烦恼的异常的文章。

编辑:

根据您的设计,我不确定为什么您需要 ResponseDtoBase 成为不同的实体并使用 Composition。我认为 Response 继承 DtoBase 是很自然的,因为 DtoBase 实际上只是一个响应(除非它还有一个 Request)。

其次,目前您可以使用BusinessWarnings作为向用户提供有关未找到原因的信息。我自己更喜欢将其更改为 OperationMessage ,其状态为 SuccessErrorWarning (也许 记录)而不仅仅是警告。稍后,为了更轻松地访问,您可以将 Messages 公开为 SuccessMessagesErrorMessagesWarningMessages。这可以被视为客户端日志记录,并且具有更多用途,而不仅仅是 BusinessWarnings

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

关于design-patterns - DTO 和空结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21270608/

相关文章:

c# - 域实体中的外键属性

java - DDD - 复合聚合序列化 - 设计问题

asp.net-mvc - 带有knockout.js : using domain classes, DTO或每种形式的自定义模型的MVVM中的最佳实践是什么

java - MapStruct 忽略对象内列表内的属性

design-patterns - 应用干净的代码和 SOLID 原则花了我很多时间 : normal?

java - 使用多个属性查找匹配对象

objective-c - 单例实例与类方法

design-patterns - channel 适配器和消息网关模式有什么区别?

java - 模糊逻辑域模型

java - JAX-RS 是否需要数据传输对象 (DTO)?