Bean驗證反模式 - reflectoring.io
Bean驗證是在Java生態系統中實施驗證邏輯的事實上的標準,它是一個很好的工具。
但是,在最近的專案中,我對Bean驗證進行了更深入的思考,並確定了一些我認為是反模式的實踐。
反模式免責宣告
就像每一次關於模式和反模式的討論一樣,都涉及一些觀點和個人經驗。在一種情況下使用反模式很可能是在另一種情況下的最佳實踐(反之亦然),因此,請不要將下面的討論視為宗教規則,而應將其作為對該主題進行思考和進行建設性討論的觸發點。
反模式1:僅在持久層中進行驗證
使用Spring,在持久層中設定Bean驗證非常容易。假設我們有一個帶有一些bean驗證批註的實體以及一個關聯的Spring Data儲存庫:
@Entity public class Person { @Id @GeneratedValue private Long id; @NotEmpty private String name; @NotNull @Min(0) private Integer age; // getters and setters omitted } |
public interface PersonRepository extends CrudRepository<Person, Long> { // default CRUD methods provided by CrudRepository } |
只要我們在類路徑classpath上有一個像Hibernate Validator這樣的bean驗證實現庫包,每次呼叫儲存庫方法save()時,都會觸發一次驗證。如果傳入物件根據bean驗證註釋判斷是無效的,將丟擲ConstraintViolationException
持久層是驗證的正確地方嗎?
我認為至少它不是唯一可以驗證的地方。
在常見的Web應用程式中,持久層是最底層。我們通常在上面有一個業務層和一個Web層。資料通過業務層流入Web層,最後到達持久層。
如果僅在持久層中進行驗證,那麼我們將承擔Web和業務層使用無效資料的風險!
無效的資料可能會導致業務層中的嚴重錯誤(如果我們希望業務層中的資料有效)或導致超級防禦性的程式設計,並且需要在整個業務層中進行手動驗證檢查(一旦我們瞭解到其中的資料業務層不能被信任)。
總之,對業務層的輸入應該已經有效。這樣,在持久層中的驗證就可以充當附加的安全網,但不是唯一的驗證位置。
反模式2:驗證太多
除了驗證得過少之外,我們會驗證得太多。這不是特定於Bean驗證的問題,而是通常具有驗證功能都會具有的問題。
在通過Web層進入系統之前,使用Bean驗證對資料進行驗證。Web控制器將傳入的資料轉換為可以傳遞給業務服務的物件。業務服務不信任Web層,因此它使用Bean驗證再次驗證該物件。
在執行實際的業務邏輯之前,業務服務將以程式設計方式檢查我們能想到的每個約束,以便絕對不會出錯。最後,持久層在將資料儲存到資料庫之前再次對其進行驗證。
這好像是一種不錯的防禦性驗證方法,但它帶來的問題多於我的經驗。
首先,如果我們在很多地方使用Bean驗證,那麼到處都會有Bean驗證註釋。如有疑問,我們將向物件新增Bean驗證批註,即使它畢竟可能不會得到驗證。最後,我們花時間在新增和修改可能根本不執行的驗證規則上。
其次,到處進行驗證會導致意圖明確但最終導致錯誤的驗證規則。
想象一下,我們正在驗證一個人的名字和姓氏,以使其至少包含三個字元。這不是必需的,但是無論如何我們都新增了此驗證,因為在我們的環境中,不驗證是不禮貌的。有一天,我們會收到一個錯誤報告,稱一個名為“ Ed Sheeran”的人未能在我們的系統中註冊,並且剛剛在推特上引發了一場狗屎風暴。
第三,到處驗證會減慢開放速度。
如果我們在整個程式碼庫中散佈了驗證規則,其中一些在Bean驗證批註中,而另一些在純程式碼中,則其中的某些可能會妨礙我們正在構建的新功能。但是我們不能僅僅刪除那些驗證,畢竟,有人將它們放在那裡一定有道理。我們放慢了腳步,因為我們必須仔細考慮每個驗證,然後才能應用更改。
最後,由於驗證規則遍及整個程式碼,如果遇到意外的驗證錯誤,我們將不知道在哪裡尋找解決方案。
簡而言之,我們應該有一個明確而集中的驗證策略,而不是在任何地方驗證所有內容。
反模式3:使用驗證組進行用例驗證
Bean驗證JSR提供了稱為驗證組的功能。此功能使我們可以將驗證註釋與某些組相關聯,以便我們可以選擇要驗證的組:
public class Person { @Null(groups = ValidateForCreate.class) @NotNull(groups = ValidateForUpdate.class) private Long id; @NotEmpty private String name; @NotNull @Min(value = 18, groups = ValidateForAdult.class) @Min(value = 0, groups = ValidateForChild.class) private int age; // getters and setters omitted } |
當一個Person建立時,Id可以為空,但是修改時,不能為空。
首先,我們故意違反“單一責任原則”。
其次,它很難閱讀。
我提議不使用驗證組。
特定於用例的語義使用程式碼驗證,並且模型程式碼不依賴於用例。業務規則使用程式碼實現,成為“豐富”充血領域模型的一部分,並且可以通過查詢方法進行訪問。
有意識地驗證
Bean驗證是一個觸手可及的好工具,但好的工具會帶來很大的責任感(聽起來有些陳詞濫調,但是如果您問我的話,這很重要)。
我們應該有一個清晰的驗證策略,告訴我們在哪裡進行驗證以及何時使用哪種工具進行驗證,而不是對所有內容都使用Bean驗證並在各處進行驗證。
我們應該將句法驗證與語義驗證分開。語法驗證是Bean驗證批註支援的宣告式樣式的完美用例,而語義驗證在純程式碼中更易讀。
相關文章
- WCFNetTcpBindingTransport安全模式(8)ClientCredentialType證書驗證模式—-PeerOrChainTrust驗證模式TCP模式clientAIRust
- WCFNetTcpBindingTransport安全模式(7)ClientCredentialType證書驗證模式—-ChainTrust驗證模式TCP模式clientAIRust
- 策略模式下表單驗證模式
- [BUG反饋]mac環境下驗證碼不顯示Mac
- [BUG反饋]GIT最新版中文驗證碼出錯Git
- asp.core 同時相容JWT身份驗證和Cookies 身份驗證兩種模式JWTCookie模式
- Vim反模式模式
- 如何利用策略模式優化表單驗證模式優化
- NPM酷庫042:ajv,JSON 模式驗證NPMJSON模式
- Android Activity 啟動模式的功能驗證Android模式
- 安全可靠的簡訊驗證碼API,毫秒級反應API
- qq郵箱收不到epic驗證郵件怎麼辦 epic郵箱驗證沒反應怎麼辦
- Spring Boot之輸入Bean驗證@Valid應用場景總結Spring BootBean
- python 反模式Python模式
- OpenSessionInView是反模式SessionView模式
- 「資料分析」2種常見的反爬蟲策略,資訊驗證和動態反爬蟲爬蟲
- JS 基礎篇(策略模式-表單驗證案例)JS模式
- NPM酷庫043:joi,語義化模式驗證NPM模式
- 驗證碼原理及驗證
- 詳解Spring Security的HttpBasic登入驗證模式SpringHTTP模式
- 聊聊jQuery的反模式jQuery模式
- MVC驗證02-自定義驗證規則、郵件驗證MVC
- JavaScript驗證碼生成和驗證效果JavaScript
- javascript 驗證身份證JavaScript
- MVC驗證04-自定義驗證規則、日期範圍驗證MVC
- SQL Server 2008的選擇身份驗證模式SQLServer模式
- SSL證書是如何驗證的?驗證方式推薦
- 倉儲模式到底是不是反模式?模式
- 驗證碼---js重新整理驗證碼JS
- MVC驗證08-jQuery非同步驗證MVCjQuery非同步
- easy-captcha實現驗證碼驗證APT
- 泛型方法的反模式泛型模式
- 7種微服務反模式微服務模式
- 異常處理反模式模式
- 反DDD模式之“複用”模式
- thinkphp驗證器獲取$data資料,自定義驗證,多條件唯一性驗證unique驗證PHP
- 身份證驗證工具類
- 需求驗證