閒談軟體設計:Query Object

更新於 2023/07/15閱讀時間約 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
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
塔西佗陷阱(Tacitus Trap),一個得名於古羅馬歷史學家塔西佗的政治學理論,意指倘若公權力失去其公信力,無論如何發言或是處事,社會均將給予其負面評價。 當然信著恆信、不信者恆不信,這就是真實的人生。
Thumbnail
關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作
Thumbnail
那天我問隊友,怎樣才算是一部小說呢?按字數計算嗎? 他說:「故事內起承轉合都有,就算」 所以我大膽地按著他的標準,將自己寫過的故事,粗略整理出一個明細。
纏中說禪,本名李彪,專欄筆名木子,其人是中國股市比較早期的操盤手,所以他比較熟悉a股的市場情況。他以“纏中說禪”為筆名,從2002年開始寫博客,直到2008年癌症病重停更,期間寫下了不少文章。而博客文章中最為著名的就是他的“教你炒股票”系列文章,他在這個系列裡講到的炒股理論和方法被粉絲稱為“纏論
Thumbnail
婚姻是人生大事,對溥儀尤其如此,因為如果皇帝大婚,就代表溥儀可以脫離眾多便宜老媽的束縛而得以親政。 但詭異的是,這個可以讓他脫離便宜老媽掌控的婚姻,卻還是要由便宜老媽進行主導並且居中角力......
Thumbnail
上次我提到:溥儀就是個死小孩。其實這不能全怪溥儀,而要怪詭異的宮廷教育及生活制度......
Thumbnail
近期電影「末代皇帝」重新修復上映。 為了推坑這部經典之作,本人決定以溥儀本身的自傳《我的前半生》為主要基底,和大家談一些電影中礙於篇幅或是藝術改編,而不容易察覺或是沒有呈現的真實歷史。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
最近身旁有幾位正在懷孕、或剛生產完的朋友,讓我想起自己在懷孕期間印象最深刻的三件「怪事」,其中又以第三件事最誇張。
Thumbnail
不知道大家在買房之前是不是都會參考親朋好友的意見,或是上網看一些買房注意事項,有時候考慮了這塊就忘了那塊,考慮的那塊又忘了這塊.......
Thumbnail
塔西佗陷阱(Tacitus Trap),一個得名於古羅馬歷史學家塔西佗的政治學理論,意指倘若公權力失去其公信力,無論如何發言或是處事,社會均將給予其負面評價。 當然信著恆信、不信者恆不信,這就是真實的人生。
Thumbnail
關於片名   台灣片名《花漾女子》,原文片名《Promising Young Woman》,台灣譯名將時間定格在悲劇發生前,而原文片名則進一步帶我們看見另一個可能性結果
Thumbnail
前幾年因為身體的關係,當了幾年的律師逃兵,當時開了之前的事務所以後,一時間也沒有特別想要做甚麼事情,所以就邊讀一點書、早晚運動一下,剛好聽到當年同梯朋友進去金融業工作,因此也抱著嘗(ㄊㄠˊ)試(ㄅㄧˋ)的心態,找了份銀行法令遵循的工作
Thumbnail
那天我問隊友,怎樣才算是一部小說呢?按字數計算嗎? 他說:「故事內起承轉合都有,就算」 所以我大膽地按著他的標準,將自己寫過的故事,粗略整理出一個明細。
纏中說禪,本名李彪,專欄筆名木子,其人是中國股市比較早期的操盤手,所以他比較熟悉a股的市場情況。他以“纏中說禪”為筆名,從2002年開始寫博客,直到2008年癌症病重停更,期間寫下了不少文章。而博客文章中最為著名的就是他的“教你炒股票”系列文章,他在這個系列裡講到的炒股理論和方法被粉絲稱為“纏論
Thumbnail
婚姻是人生大事,對溥儀尤其如此,因為如果皇帝大婚,就代表溥儀可以脫離眾多便宜老媽的束縛而得以親政。 但詭異的是,這個可以讓他脫離便宜老媽掌控的婚姻,卻還是要由便宜老媽進行主導並且居中角力......
Thumbnail
上次我提到:溥儀就是個死小孩。其實這不能全怪溥儀,而要怪詭異的宮廷教育及生活制度......
Thumbnail
近期電影「末代皇帝」重新修復上映。 為了推坑這部經典之作,本人決定以溥儀本身的自傳《我的前半生》為主要基底,和大家談一些電影中礙於篇幅或是藝術改編,而不容易察覺或是沒有呈現的真實歷史。