2015-08-15 23 views
2

比方說,我有一個超級簡單的用戶註冊檢查,用戶的電子郵件在所有用戶中必須是唯一的。如何從持久層分離唯一性驗證?

我已經在這些功能中表達了這個要求。

(defn validate-user [user] 
    (and (:email user) (is-unique? (:email user)))) 

(defn is-unique? [email] 
    (not (db-api/user-exists {:email email}))) 

但我想從數據庫中解耦我的驗證,我想使它純粹的功能。我大概也注入了數據庫API作爲參數傳遞給validate-user,像

(defn validate-user [db-api user] 
    (and (:email user) (is-unique? db-api (:email user)))) 

(defn is-unique? [db-api email] 
    (not ((:user-exists db-api) {:email email}))) 

,但我不知道這是地道的。

另外,它感覺像validate-user的消費者不應該關心數據庫API。感覺就像這種依賴破壞了將業務邏輯層與持久層分離的整個概念。所以我正在尋找解釋如何正確地做到這一點的心態,或者爲什麼它不能完成。

+0

你打算以後插入用戶?如果是的話,那麼你就必須使用這種方法的競爭條件。 (除非你保留在內存中,並鎖定所有電子郵件)。 – ClojureMostly

+0

您是Ruby開發人員,所以我想我應該提醒您[Rails唯一性驗證指南的這一部分](http://guides.rubyonrails.org/active_record_validations.html#uniqueness)。本節的一部分仍然適用於Clojure。 –

回答

2

爲了避免競爭條件,數據庫應該處理這個約束。您可能正在尋找相當於SQL世界中的INSERT IF NOT EXISTS

實際上,您可以具有功能create-user和功能update-usercreate-user函數可以使用IF NOT EXISTS檢查。

您無法將其與數據庫分離:維護數據約束的數據庫責任(關係,在關係世界中)。由於競爭條件,沒有其他人能夠做到這一點。

讓我就此展開一個例子:

假設兩個用戶(用戶A和用戶B),在同樣的時間希望與未選擇在新的電子郵件創建一個帳戶:「[email protected] 」。那麼你的系統查詢數據庫:

你然後進行:

根據數據庫的語義和你的請求,你可能會得到:

  • 兩個帳戶與郵件「[email protected]」(前。在數據庫上沒有約束,沒有唯一索引)

  • 爲「用戶B」的帳戶被創建,不考慮對用戶A(例如:請求創建帳戶的用戶A失敗,因爲已經有一個MAL存在)

  • 創建了一個「userB」帳戶,那麼「userA」的數據將覆蓋「userB」的數據(例如,更新語句的語義是數據庫中的用戶 - 可能是此時的錯誤)

正因爲如此,你應該嘗試創建一個帳戶,並讓數據庫告訴你,如果它失敗了。你就不能檢查自己,不會重新創建一個數據庫,自己的屬性(我個人不敢嘗試)。