2013-03-15 125 views
5

我有一個關於ddd驗證方法的問題。我已經閱讀了頗有爭議的觀點。有人說這應該活在實體之外,其他人則說這應該放在實體中。我試圖找到一種我可以遵循的方法。ddd中的驗證方法

作爲一個例子,假設我有用戶實體的電子郵件和密碼。用戶有一個方法註冊(電子郵件,密碼)。電子郵件和密碼驗證應該放在哪裏?我個人認爲它應該在Register()方法中。但是這種方法可能會導致用戶類與驗證內容混淆。一種方法可能是在單獨的策略對象中提取電子郵件和密碼規則,並仍然使用Register()方法調用它們。

您對DDD驗證方法有何看法?

回答

3

實際上,用戶的有效性依賴於上下文。用戶可以是有效的(姓名和電子郵件有效),但註冊操作可能無法執行。爲什麼?因爲可能已經存在具有相同名稱的用戶。因此,在某些情況下看起來有效的用戶在註冊上下文中可能無效。

因此,我將驗證的一部分移至實體或值對象(例如郵件對象)以避免明顯無效的實體狀態(例如名稱爲null的用戶)。但是在這種情況下應該存在依賴於上下文的驗證。所以,如果我註冊用戶:

Mail mail = new Mail(blahblahblah); // validates if blah is valid email 
User user = new User(name, mail); // validates if name is valid and mail not null 
// check if there already exist user with such name or email 
repository.Add(user); 

而且我認爲方法Register應該是某一領域的服務(如MembershipService)的方法,因爲它絕對應該使用一些庫。

+0

Register()只會使用密碼,電子郵件,註冊日期,註冊狀態等數據初始化用戶。它不會調用存儲庫。這是一些安全應用程序服務的責任。但這不是這裏的主要問題。我剛剛提供了一個實體操作的例子,並試圖分析驗證應放置在哪裏。 – Markus 2013-03-15 16:00:11

+0

@Markus我認爲這樣的方法名稱令人困惑:) – 2013-03-15 16:02:47

5

首先,我認爲驗證是一個很滑的主題,部分原因在於它需要考慮的各種contexts。最終,將會有各種應用程序層執行驗證規則。至少,在域對象中應該有標準的守衛。這些只是定期的條件和參數檢查,應該是任何設計良好的對象的一部分,並且符合您對Reigster方法的意見。正如lazyberezovsky所述,這是爲了防止對象進入無效狀態。儘管如此,我仍然與永遠有效的學校一起。我認爲如果有必要堅持實體處於無效狀態,應該爲此創建一個新實體。

但是,單獨使用這種方法的一個問題是,通常需要將這些驗證規則導出到其他圖層,例如導出到表示層。而且,在表示層中,規則需要有不同的格式。他們需要一次呈現,並可能翻譯成另一種語言,例如JavaScript,以便立即進行客戶端反饋。試圖從類提出的例外中提取驗證規則可能是困難的或不切實際的。或者,驗證規則可以在表示層重新創建。這很簡單,儘管可能違反DRY,但它允許規則依賴於上下文。特定的工作流程可能需要與實體本身執行的不同驗證規則。

上述方法的另一個問題是可能存在驗證規則,這些規則存在於實體範圍之外,並且這些規則必須與其他規則一起合併。例如,對於用戶註冊,另一個規則是確保電子郵件地址是唯一的。託管適用用例的應用程序服務通常會執行此規則。但是,它也必須能夠將此規則導出到其他圖層,例如演示文稿。總的來說,我嘗試將更多的約束檢查放入實體本身,因爲我認爲實體應始終有效。有時可以設計規則框架,以便它可以用於引發異常並可以導出到外層。其他時候,簡單地跨層複製規則會更容易。

2

作爲一個例子,假設我有用戶實體的電子郵件和密碼。用戶有一個方法註冊(電子郵件,密碼)。電子郵件和密碼驗證應該放在哪裏?

如果您遵循DDD,電子郵件地址和用戶名是您域中的關鍵概念 - 所以它們應該被表示爲實體。在這種情況下,電子郵件地址的驗證位於EmailAddress實體中,用戶名的驗證應放置在Username實體中。

僞代碼示例:驗證和業務規則,其中後者通常進入一個服務或實體方法:

所有我將兩件事情之間分開的
class EmailAddress 
{ 
    Constructor(string email) 
    { 
     if (email.DoesNotMatchRegex("\[email protected]\w+.\w{2,3}")) 
      throw error "email address is not valid" 
    } 
} 

class Username 
{ 
    Constructor(string username) 
    { 
     if (username.length < 6) 
      throw error "username must be at least 6 characters in length" 
    } 
} 

class User 
{ 
    Register(Username username, EmailAddress email) 
    { 
     if (username == null) 
      throw error "user must have a username" 

     if (email == null) 
      throw new error "user must provide email address" 

     // At this point, we know for sure that the username and email address are valid... 
    } 
} 
+1

我可以證實這種方法工作得很好。我只想補充一點,[例外是無所不在的語言的術語](http://epic.tesio.it/2013/03/04/exceptions-are-terms-ot-the-ubiquitous-language.html)。 – 2013-03-16 15:49:06

+0

它們是不是對象的用戶名和EmailAddress? – 2015-04-10 19:23:42

+0

@Barthelomeus是的,他們會成爲價值對象的優秀候選人。在實體/值對象和類/結構體之間沒有*必然的映射 - 類仍然可以是值對象,但沿着這些線做出區分通常是有幫助的。 – MattDavey 2015-09-03 08:53:00

0

首先。對於驗證,例如有效的電子郵件地址或有效密碼,我通常將它們粘貼到擁有它們的實體上,以便我可以將它們移動到實體中,如果我將我的應用程序移植到不同的平臺上。現在對於你的註冊方法的例子,我看不出爲什麼如果對象處於無效狀態,這個方法首先被調用,如果它將被調用,我會使它像另一種方法,如SignUp,可能會做的一步像這樣:if (isValid()) register(username, password)