0

我一次又一次地回想起對需要訪問某些上下文(例如NH中的ISession,例如IRepository)的POCO對象執行驗證的最佳方式。Validation without ServiceLocator

我仍然能看到的唯一選擇是使用服務定位,所以我的驗證看起來像:

public User : ICanValidate { 
    public User() {} // We need this constructor (so no context known) 

    public virtual string Username { get; set; } 

    public IEnumerable<ValidationError> Validate() { 
     if (ServiceLocator.GetService<IUserRepository>().FindUserByUsername(Username) != null) 
      yield return new ValidationError("Username", "User already exists.") 
    } 
} 

我已經使用反轉控制和依賴注入和真的不喜歡由於一些事實ServiceLocator:

  • 難以維持隱式依賴。
  • 難以測試代碼。
  • 潛在的線程問題。
  • 僅顯式依賴於ServiceLocator。
  • 代碼變得更難理解。
  • 需要在測試期間註冊ServiceLocator接口。

但是另一方面,對於普通的POCO對象,我沒有看到任何其他的方式來執行上面沒有ServiceLocator並僅使用IoC/DI的驗證。

目前,我在服務層中執行此類驗證。因此,無論何時一個演員試圖更改用戶名(當然可能有些不同),該服務會執行此驗證。一個明顯的缺點是每個使用用戶的服務都必須執行此檢查(即使它是一次調用)。

所以問題是:是否有任何方式使用DI/IoC的上述情況

謝謝,
德米特里。

回答

1

存儲庫通常處於比它們獲取/存儲的域對象更高的抽象級別。如果您發現域對象取決於存儲庫,那麼這是上游設計問題的指示。

你真正擁有的是循環依賴。 IUserRepository取決於UserUser取決於IUserRepository。這技術上的作品,如果兩個對象在同一個程序集中,它將編譯,但它會作爲一個通用的設計遇到麻煩。可能有各種想要處理User的對象,但不知道它來自的IUserRepository

我給你的建議不是將其作爲User的「驗證」屬性。驗證應該由存儲庫本身執行,或者 - 更好的是 - 只要存儲庫在試圖保存時已經存在,就會引發一個異常。

這個建議還有第二個原因。這是因爲併發性。即使您驗證用戶名和發現,它並沒有已經存在,當您嘗試保存該用戶可能不是真正的在1秒鐘之後。所以你需要處理異常情況(試圖插入已經存在的用戶名)反正。鑑於此,您可能會推遲到最後一刻,因爲您無法事先做出保證。

域對象應該沒有依賴;如果他們自我驗證,那麼驗證應該僅取決於關於正在驗證的實際對象的,而不取決於數據庫中的其他數據。重複的用戶名約束實際上是數據約束,而不是域約束。

摘要:User類之外移動此特定驗證。它不屬於那裏;這就是爲什麼你發現自己使用這種特殊的反模式。

+0

我完全同意這一點。目前,我在服務層執行'Unique'驗證,但是我必須複製/繼承與用戶一起工作的每個服務中的邏輯。 – 2010-05-11 00:02:06

+0

@Dmitriy:這很好,可以理解;然後,我將這個邏輯放在客戶端/用戶交互的*服務*中,而不是特定的項目。如果您需要保持服務整潔,請委派專門的驗證人。 – Aaronaught 2010-05-11 00:29:20

+0

我所有的'服務'目前都是特定用例/故事的實現(所以它們可以被看作是用戶交互的)。 我會做的是可能添加另一個依賴項'IUserValidation'並將其注入到Use-Case服務中。我認爲這將是現在最好的方式。 – 2010-05-11 01:55:27

1

只是爲了增加Aaronaught的話。這個設計有一個更大的問題,因爲域模型驗證應該只驗證模型固有的屬性 - 而不是在更大的系統範圍內。這種固有特性的一些實例將是用戶名的長度,可接受的字符的要求,這兩個第一和最後一個名稱被歸檔等

您正在執行的驗證是全系統的驗證和在服務/資源庫所屬。這是如果使用域驅動設計設計這個系統的樣子:

public class User : ICanValidate { 
    public User() {} 

    public virtual string Username { get; set; } 

    public IEnumerable<ValidationError> Validate() { 
     if (!string.IsNullOrEmpty(this.UserName)) 
      yield return new ValidationError("Username must not be empty"); 
    } 
} 

public class UserRepository : IUserRepository { 
} 

public static class UserService { 
    readonly IUserRepository Repository; 

    static UserService() { 
    this.Repository = ServiceLocator.GetService<IUserRepository>(); 
    } 

    public static IEnumerable<ValidationError> Validate(User user) { 
     if (Repository.FindUserByUsername(user.Username) != null) 
      yield return new ValidationError("Username", "User already exists.") 
    } 
} 
+0

伊戈爾,謝謝。我只是添加了一個用戶驗證服務,以便我可以重用它。我會考慮它稍後命名'UserService':) – 2010-05-11 04:21:00