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驗證批註支援的宣告式樣式的完美用例,而語義驗證在純程式碼中更易讀。
相關文章
- NPM酷庫042:ajv,JSON 模式驗證NPMJSON模式
- Spring Boot之輸入Bean驗證@Valid應用場景總結Spring BootBean
- asp.core 同時相容JWT身份驗證和Cookies 身份驗證兩種模式JWTCookie模式
- [BUG反饋]mac環境下驗證碼不顯示Mac
- [BUG反饋]GIT最新版中文驗證碼出錯Git
- 如何利用策略模式優化表單驗證模式優化
- 安全可靠的簡訊驗證碼API,毫秒級反應API
- qq郵箱收不到epic驗證郵件怎麼辦 epic郵箱驗證沒反應怎麼辦
- NPM酷庫043:joi,語義化模式驗證NPM模式
- JS 基礎篇(策略模式-表單驗證案例)JS模式
- 詳解Spring Security的HttpBasic登入驗證模式SpringHTTP模式
- 驗證碼原理及驗證
- spring注入bean的幾種策略模式SpringBean模式
- 「資料分析」2種常見的反爬蟲策略,資訊驗證和動態反爬蟲爬蟲
- 「Spring認證」Spring Bean 定義教學SpringBean
- 倉儲模式到底是不是反模式?模式
- 5. Bean Validation宣告式驗證四大級別:欄位、屬性、容器元素、類Bean
- JavaScript驗證碼生成和驗證效果JavaScript
- [BUG反饋]LINUX下圖示、圖片及驗證碼顯示不正確Linux
- 反DDD模式之“複用”模式
- SSL證書是如何驗證的?驗證方式推薦
- 【Medium 萬贊好文】ViewModel 和 LiveData:模式 + 反模式ViewLiveData模式
- 驗證碼---js重新整理驗證碼JS
- easy-captcha實現驗證碼驗證APT
- thinkphp驗證器獲取$data資料,自定義驗證,多條件唯一性驗證unique驗證PHP
- 身份證驗證工具類
- 需求驗證
- livewire 驗證
- 拖拽 驗證
- Java-Bean Validation後端校驗總結JavaBean後端
- 手機號碼驗證方法(正則驗證)
- Knative是FaaS的反模式嗎?模式
- 《SQL 反模式》 學習筆記SQL模式筆記
- Laravel 驗證類 實現 路由場景驗證 和 控制器場景驗證Laravel路由
- 裝飾模式-使用裝飾器來寫表單驗證外掛模式
- 分析|無感驗證:應用適老化與業務反欺詐的“守門員”
- PHP 驗證身份證號碼PHP
- C++身份證號驗證C++