我們來為 Redis 寫一個簡單而優雅的 ORM。這篇文章的靈感來自於 Django ORM。
這篇文章假定你對 Redis 以及 Python 中的 redis 庫 redis-py 有了基本的了解。
實體假設我們正在開發一個輪詢應用程式,這個應用包括 Question 和 Choice。每一個問題都有多個選項。
我們希望在我們的應用程式中具有以下能力:
存儲問題列表並檢索
根據 id 檢索問題
存儲選項
關聯問題與選項列表
將問題與選項取消關聯
檢索一個問題的所有選項
跟蹤一個選項的投票數
我們希望將 Redis 作為我們的資料庫使用。
模型我們的模型主要就是Question 和 Choice。由於這兩個模型與其他模型都有一些相似的功能,因此我們可以創建一個基類 Model,Question 與 Choice 都繼承自這個基類 Model。
基礎模型Model 應該是這樣的:
類 Question 如下:
我們來為 Model 的不同方法添加實現。
latest_instance_id_key:
list_key:
add_to_list:
add_to_list 使用了 Redis 中的列表將所有實例的 key 存入 redis。我麼這裡使用 redis-py 中的 lpush 方法,它會映射到 redis 中的 LPUSH 操作。
稍後我們會添加生成 self.id 的代碼。
確保 connection 定義在 models.py 模塊內是可訪問的。
latest_instance_id:
increment_latest_instance_id:
cache_key
save:
注意我們這裡在 save() 方法中用的是 self.repr() 方法。它可以調用子類中定義的 repr() 方法,返回一個我們要保存的屬性的字典。這個稍後解釋。
你需要關注的是 save() 中是怎樣調用 increment_latest_instance_id() 和 add_to_list() 的。
save() 方法還用到了 redis-py 中的 hmset 方法,其對應 redis 中的 HMSET 命令。通過這種方式保存實例中我們需要的屬性。
子類模型現在我們來實現類 Question。
由於子類的 save 方法使用到了 repr() 方法,所以其每一個子類都要實現 repr() 方法。repr() 應該返回的是我們希望在 redis 中持久化的字典形式的數據。比如我們希望一個問題的 id 以及 question_text 保存在 redis 中,那麼 repr() 返回的字典中應該包含這些屬性。
當我們試著使用 save() 保存第一個實例的時候,其內部會調用 latest_instance_id_key() 方法,期望能從 redis 取出已經存在的鍵 question-latest-id。
我們來創建一個文件 migration 並向 redis 中保存一個鍵。
運行這個文件:
通過 ipython 運行我們的代碼。
首先確保你的 redis 伺服器 redis-server 已經啟動了。
現在啟動一個 redis 客戶端 redis-cli,然後校驗一下 question-1 是否已經插入到 redis 中了。
這裡同樣還校驗了 questions 的存在。再來看看 id 有沒有保存到 questions 中。
現在再來校驗一下 id 和 question_text 是否也被保存在 questions 中。
我們再來用 ipython 創建一個 question 並將其保存在 redis 中。
現在看看我們期望的鍵值是否都保存到 redis 中了。
我們添加一個 Question 方法來根據給出的 id 提取一個問題。
注意這裡我們傳入了 id 作為 cache_key。相應地我們就需要修改 cache_key 的代碼:
現在來使用之前添加的 questions 的 ids 來提取它們。
不過需要確保 save 功能可用。
非常棒!
提取所有問題我們再添加一個方法來提取所有問題。
現在使用這個方法提取問題。
get_question 和 get_questions 擁有相似的功能並且可以使用我們稍後添加的模型。所以,現在將它們移到基類中去。將 get_question 重命名為 get,將 get_questions 重命名為 list。
現在就類用一下 get() 和 list() 方法,並校驗一下其功能是否符合預期。
現在來添加一個 Choice 模型。
在保存 choice 模型之前,我們需要添加一個鍵 choice-latest-id。還記得我們之前的 migrations.py 文件嗎?
在 ipython 中執行。
現在我們通過 Choice 來演示 ORM 操作。
這應該歸功於我們的基類。通過 Choice 我們可以自由地使用 save(),get(),list() 等方法。
英文原文:https://www.agiliq.com/blog/2019/11/writing-an-orm-for-redis/