2014-01-21 42 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