閒談軟體設計:Query Object

更新於 發佈於 閱讀時間約 6 分鐘

前言

之前在開發旅遊 app 的內容管理平台時,初期 Repository 用得好好的,但到後期,為了輸出報表或是提供特定列表時,就需要一直在 repository 的介面上加上特定的 query method,像是這樣 (這不是當時的程式,只是示意):
一下是取得某份文件所有語系副本,一下又是取得某文件的特定語係副本,又或者是取得某份文件參考到的其他文件,就這樣,DocumentRepository 的 method 越來越多。
當時根據資料 scale 和特性,用不同的資料庫儲存,像是比較類似 metadata 的資料,例如帳號、權限管理,使用傳統的 RDBMS 儲存,資料量會比較龐大,沒什麼結構的資料則是用 NoSQL 的儲存,這時 repository 的抽象層確實提供一個好處,business logic 並不需要知道實際資料放在何處,操作的方式在換資料庫後也完全不用變。

查詢條件物件化

說是這麼說啦,但 repository 內部的實作就有點頭痛了,同樣的查詢條件,在換資料庫後得重寫,於是我決定替 Repository 加入一個 QueryDesciptor 物件描述查詢條件,說真的,那時還沒有看過《Patterns of Enterprise Application Architecture》的 Query Object (這本書一直沒全部看完),當初的動機很單純,只是想包裝 Hibernate 的 Criteria API,可以用類似的物件導向語法寫 query 條件,然後由 repository 轉換成底層對應的 query 方式,如此 business logic layer 不需要根據不同的資料庫寫不同的 query。
因此上面例子中的 DocumentRepository 就不再需要多加三個 method,而是在 GenericRepository 中加入對 QueryDescriptor 的支援,為什麼 method 要叫 filter,而不是 find 或是其他的,主要是最近各大語言 (例如Swift) 的 collection 介面都把類似的行為用 filter 來完成。
這麼做之後,原本寫在MongoDocumentRepository 的 query 省下來了,太好了,少寫很多程式,ㄜ~ QueryDescriptor 沒那麼神,只是變成 query 搬到 DocumentManager 的實作中 (例子中的 originId、language 和 referedId 是 Document 物件的 property 名稱,不是 database 的 column 名稱):
也就是說,假設要換資料庫,只要注入 (inject) 不同的 repository 實作,邏輯是不需要更改的,聽起來很棒,但當初實作 QueryDescriptor 對 Hibernate Criteria 及對 MongoDB 搜尋條件的轉換時吃了不少苦頭,足足花上了一個禮拜的時間,而且還是最簡單的版本。
若不考慮 DSL 的寫法,可能會快一點,但為了讓用起來很像在寫 query language,得花上不少時間包裝,where 後面能接 is、in、like、greatThan、 lessThan、 greatThenOrEqual、lessThanOrEqual 等常見條件,加上 all 和 any 設定條件是全部滿足 (AND) 或是任一滿足 (OR),在 aggregation 的部分只有 min、max 和 count,最後再加上 sort、from 和 size 處理分頁的問題,projection 當時沒用到,就沒有加進去。
Figure 1 - Query object and the visitor
內部其實是一個 Query 的 AST 樹狀結構 (上述很多 method 只是建立一個物件然後呼叫 add 加到目前的節點), 當時轉換時是寫個 Visitor 走遍這個 AST 然後根據當下的節點產生對應的物件,Hibernate 應該是較簡單的,像是 is 其實是用 Restrictions.eq(name, value) 產生一個 SimpleExpression 物件,在處理 MongoDB 時就比較討厭一點,不過最花時間的,是測試,畢竟轉換出錯,撈出來的資料就會是錯的,測試案例寫了不少。
至於花這麼多時間真的值得嗎?畢竟有原生的 API 可以做, 特別是如果有用 Spring Data JPA 搭配 annotation,只要繼承 Spring Data 的 Repository 然後使用 Spring 提供的 naming convention 設計 method,Spring Data 就會自動在 runtime 提供實作 (這其實真的蠻神的),但我自己覺得值得
稍微複雜一點的 query 其實代表著某些商業邏輯,為了避免相依 (這時候不在意相依的人就開心了,對吧~在意這麼多幹嘛,直接在 business logic layer 用第三方 API 寫 query 就好啦),必須把這一段程式 push 到 repository 的實作層,會變成這些商業邏輯被隱藏起來了,如果有個好的描述語言,像剛剛 DefaultDocumentManager 的例子,我倒覺得很好讀,也可以清楚知道背後的商業邏輯是什麼,是很好的一件事。
只是沒想到最近在整理一些 Repository 的心得時,再次拿起《Patterns of Enterprise Application Architecture》來看時,才發現原來這是一個稱作 Query Object 的 pattern,而 Hibernate Criteria 或是 MongoDB 的 Filters 都是各家廠商在他們自己產品中提供的 Query Object,我做的只是透過抽象避免 vendor lock-in 而已。

結語

最後,以 PoEAA 書中對 Query Object 的描述來總結
An object that represents a database query.
本來這一篇是要放入 閒談軟體設計:Repository 的內容,但覺得有點長,於是獨立出來了!
為什麼會看到廣告
avatar-img
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
簡單說,Repository 提供像是 array 或是 dictionary 的容器,對程式來說,就好像記憶體中有所有的物件,不用去想物件其實是從資料庫來的。作法是在 data mapping layer 之上再提供一個 collection 的介面來存取物件,將資料庫的細節從商業邏輯層抽離。
不是在軟體建置之初就決定所有一切的行為,而是執行時根據配置 (configuration) 或執行時的環境 (例如:特定目錄有什麼 plug-in) 決定有哪些行為,話是這麼說,但要實作一個可以載入 plug-in 的軟體 (以下稱作 host application) 倒是有不少事情要考慮。
基本上就是這樣,例外處理是可以在軟體架構設計時就考慮進去,或者說,在軟體架構設計時就該考慮進去,制定方針讓團隊有一個原則可以遵循,透過設計讓例外的處理較容易與一致,最終讓軟體的品質可以更好。
設計時的考量主要有:(1) App 是 Internet App,在考量 UI 體驗和網路頻寬的消耗,多數資料需以某種形式儲存部分資料在行動裝置上;(2) 因此會需要同步伺服器端和行動裝置端之間資料狀態;(3) 但行動裝置網路的穩定性不如一般網路可靠,要有足夠的自動化測試驗證正常的流程與異常的流程。
程式開發有趣的地方,同樣的目標,不同的團隊會因不同的因素做出不同的設計抉擇。而這往往也是為什麼一個資深的工程師在開發速度上不一定比較快的原因之一,一個越是資深的工程師,思考的因素會更多,不過,不是考慮得越多就結果就一定越好,有時還會變成 over design 較糟的結果。
唸研究所開始當助教,偶而會有學弟妹問:怎樣寫好程式?老實說,這是個大哉問,連我學開發軟體這麼久,我也只能回答他們:多培養自己釐清問題、拆解問題、解決問題與抽象化的能力。但他們通常只會一臉狐疑看著我,感覺我說的話好抽象。
簡單說,Repository 提供像是 array 或是 dictionary 的容器,對程式來說,就好像記憶體中有所有的物件,不用去想物件其實是從資料庫來的。作法是在 data mapping layer 之上再提供一個 collection 的介面來存取物件,將資料庫的細節從商業邏輯層抽離。
不是在軟體建置之初就決定所有一切的行為,而是執行時根據配置 (configuration) 或執行時的環境 (例如:特定目錄有什麼 plug-in) 決定有哪些行為,話是這麼說,但要實作一個可以載入 plug-in 的軟體 (以下稱作 host application) 倒是有不少事情要考慮。
基本上就是這樣,例外處理是可以在軟體架構設計時就考慮進去,或者說,在軟體架構設計時就該考慮進去,制定方針讓團隊有一個原則可以遵循,透過設計讓例外的處理較容易與一致,最終讓軟體的品質可以更好。
設計時的考量主要有:(1) App 是 Internet App,在考量 UI 體驗和網路頻寬的消耗,多數資料需以某種形式儲存部分資料在行動裝置上;(2) 因此會需要同步伺服器端和行動裝置端之間資料狀態;(3) 但行動裝置網路的穩定性不如一般網路可靠,要有足夠的自動化測試驗證正常的流程與異常的流程。
程式開發有趣的地方,同樣的目標,不同的團隊會因不同的因素做出不同的設計抉擇。而這往往也是為什麼一個資深的工程師在開發速度上不一定比較快的原因之一,一個越是資深的工程師,思考的因素會更多,不過,不是考慮得越多就結果就一定越好,有時還會變成 over design 較糟的結果。
唸研究所開始當助教,偶而會有學弟妹問:怎樣寫好程式?老實說,這是個大哉問,連我學開發軟體這麼久,我也只能回答他們:多培養自己釐清問題、拆解問題、解決問題與抽象化的能力。但他們通常只會一臉狐疑看著我,感覺我說的話好抽象。
你可能也想看
Google News 追蹤
Thumbnail
/ 大家現在出門買東西還會帶錢包嗎 鴨鴨發現自己好像快一個禮拜沒帶錢包出門 還是可以天天買滿買好回家(? 因此為了記錄手機消費跟各種紅利優惠 鴨鴨都會特別注意銀行的App好不好用! 像是介面設計就是會很在意的地方 很多銀行通常會為了要滿足不同客群 會推出很多App讓使用者下載 每次
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
這篇文章主要是介紹了SQL查詢效能調校的方法,針對索引最佳化做了整理和分享,並提供了一些注意事項和建議。
Thumbnail
這篇文章介紹了面試時以及開始工作後可能會遇到的問題,包括物件導向OOP、SOLID 設計原則、測試方式,以及 Cookie、Session 與 Cache 的相似處與不同處。提供了豐富的相關資訊。
※ Object(物件) & Constructor Function(建構式函式) Object(物件)是什麼? 物件是一種「可以將資料、程式碼包含在其中」的資料結構。 Object(物件)的兩種創造方式: 匿名物件:直接使用"{}"。沒有特別的名字,直接從Object中繼承過來的一個物件
Thumbnail
物件導向(Object-Oriented Programming,OOP) 可以用來提高程式碼的可讀性、可維護性和可擴展性,同時還能夠促進程式的重用和組織。
※ 什麼是資料庫正規化?為什麼需要正規化? 什麼是資料庫正規化? 資料庫正規化是一種設計關聯式資料庫的方法,目的是建立良好結構的關聯表,主要目的有二: 去除重複性:建立沒有重複的關聯表。因為重複資料不只浪費資料庫的儲存空間,而且會產生資料維護上的問題。 去除不一致的相依性:資料相依是指關聯表
※ ORM 是什麼?ORM 的優缺點是什麼? ORM 是什麼? ORM 專用於關聯式資料庫 (relational database)一種叫「物件映射 (object mapping)」 的技術,主要是用程式語言裡的「物件」來包裝資料庫的 SQL (structured query langua
Thumbnail
在用 QUERY 查詢資料時,你曾遇過在 WHERE 寫很多個 OR 的狀況嗎?有個更簡單好用的寫法推薦給你,來瞧瞧!
Thumbnail
這邊統整了所有過去發表過關於 QUERY 函式的教學分享,希望可以方便你按照順序閱讀和練習。 QUERY 可以用來查詢、篩選、聚集、排序資料,還可以做張簡易的資料透視表,是我在 Google 試算表上做數據分析、製作報告、製作儀表板時最常用的函式之一,既方便又好用,誠心推薦!
Thumbnail
※ 基本操作:SQL 語法,SELECT, WHERE, CREATE, UPDATE, DELETE。 SELECT:從資料庫中或資料表中指定要選擇的欄位中取得資料,稱之為查詢 (query)。 ※ 語法:要由兩部分構成,第一部分是要 "拿什麼" 資料 (若有多項用逗號隔開);第二部分則為
Thumbnail
/ 大家現在出門買東西還會帶錢包嗎 鴨鴨發現自己好像快一個禮拜沒帶錢包出門 還是可以天天買滿買好回家(? 因此為了記錄手機消費跟各種紅利優惠 鴨鴨都會特別注意銀行的App好不好用! 像是介面設計就是會很在意的地方 很多銀行通常會為了要滿足不同客群 會推出很多App讓使用者下載 每次
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
這篇文章主要是介紹了SQL查詢效能調校的方法,針對索引最佳化做了整理和分享,並提供了一些注意事項和建議。
Thumbnail
這篇文章介紹了面試時以及開始工作後可能會遇到的問題,包括物件導向OOP、SOLID 設計原則、測試方式,以及 Cookie、Session 與 Cache 的相似處與不同處。提供了豐富的相關資訊。
※ Object(物件) & Constructor Function(建構式函式) Object(物件)是什麼? 物件是一種「可以將資料、程式碼包含在其中」的資料結構。 Object(物件)的兩種創造方式: 匿名物件:直接使用"{}"。沒有特別的名字,直接從Object中繼承過來的一個物件
Thumbnail
物件導向(Object-Oriented Programming,OOP) 可以用來提高程式碼的可讀性、可維護性和可擴展性,同時還能夠促進程式的重用和組織。
※ 什麼是資料庫正規化?為什麼需要正規化? 什麼是資料庫正規化? 資料庫正規化是一種設計關聯式資料庫的方法,目的是建立良好結構的關聯表,主要目的有二: 去除重複性:建立沒有重複的關聯表。因為重複資料不只浪費資料庫的儲存空間,而且會產生資料維護上的問題。 去除不一致的相依性:資料相依是指關聯表
※ ORM 是什麼?ORM 的優缺點是什麼? ORM 是什麼? ORM 專用於關聯式資料庫 (relational database)一種叫「物件映射 (object mapping)」 的技術,主要是用程式語言裡的「物件」來包裝資料庫的 SQL (structured query langua
Thumbnail
在用 QUERY 查詢資料時,你曾遇過在 WHERE 寫很多個 OR 的狀況嗎?有個更簡單好用的寫法推薦給你,來瞧瞧!
Thumbnail
這邊統整了所有過去發表過關於 QUERY 函式的教學分享,希望可以方便你按照順序閱讀和練習。 QUERY 可以用來查詢、篩選、聚集、排序資料,還可以做張簡易的資料透視表,是我在 Google 試算表上做數據分析、製作報告、製作儀表板時最常用的函式之一,既方便又好用,誠心推薦!
Thumbnail
※ 基本操作:SQL 語法,SELECT, WHERE, CREATE, UPDATE, DELETE。 SELECT:從資料庫中或資料表中指定要選擇的欄位中取得資料,稱之為查詢 (query)。 ※ 語法:要由兩部分構成,第一部分是要 "拿什麼" 資料 (若有多項用逗號隔開);第二部分則為