就我現在的認知,我覺得 Query 情境比 Command 的情境難處理多,在 Command 的情況下我們用 Domain-Driven Design 或是只單純用 Domain Model 模式我覺得都可以將複雜度降低不少,但在 Query 情況下,目前我到現在還沒看出比較好的模式可以來解決它的複雜度,就算是 GraphQL 我自已也覺得只是解了部份,然下來我就來談談我覺得難題在那。
首先下面這些是我在實務上,我自已比較覺得給我帶來複雜、難以理解、難以維護的情況 :
然後接下來我一步一步的說,首先我們最開始使用 GraphQL 可以解決的東西。
首先我們用這 GraphQL 這東西,應該有不少了有用了,然後用了它我們的確解決了上述的幾個點 :
GraphQL 它有辦法做到以下這些事情,所以它的確解決了我們第 4、5 點的難題。
但是我們現在有個需求就是:
不同的角色,只能拿到用戶可以管的資料,並且要排序、分頁與多條件查詢
所以為了這個需求,我們在 GraphQL 到 QueryService 中間有進行了,根據角色然後取得到它可以控制的資源,然後在組合 Query 丟到 Service 取得資料。
但這裡有個東西要來說說,那就是分頁。
我們已經有差了一個平台用的 GraphQL 了,那接下來的問題就在分頁,然後我們應該都知道要能用分頁的條件在於 :
資料在同一個資料庫
所以這裡有分兩種情境,假設我們現在已經有 3 個 Bounded Context,那我們現在的架構有兩個選項 :
第一種架構的情況下,表面上沒毛病,但實際上還是可能會有問題發生,因為它 GraphQL 所提供的 Scheam 就一定是該服務的,但第二種就有可能會出問題,因為它可能會去抓多個 BC 來整合資料。
那第二種怎麼辦 ? 就只能判斷是用 Sub Resolver 的欄位是沒辦法分頁吧。
然後發現不同平台有不同的欄位意思,所以將 GraphQL 那裡分成,每一個平台都有一個 GraphQL Schema。
但是內部使用的怎辦呢 ? 答案就是我們將平台可取得的邏輯放在 GraphQL 那一層,然後 QueryService 那一裡我們就是做比較接近通用型的。
也就是假設有個需求為 :
在創作者後台上,創作者只能取得到他自已管理的課程
那這樣我們就是:
所以這個地方我們解決了這個點 :
我覺得重點在於
- 要分的清楚那些是共用
- 然後注解要寫清楚 + 有辦法反追回這個欄位是如何產生的。
然後關於第 1 點,我覺得可以想成 QueryService 那的設計比較算是不考慮情境的情況下設計,但準確的說那個欄位是所有情境都適用 ( 共用 ),而 GraphQL 那就是比較可以根據平台考慮情境。
然後第 2 點,我覺得只要能做到以下 2 點,那我覺得應該不算難事 :
以我們家為例,很多很難追的欄位就在於,我們在很多地方都會 populate 欄位,又或是加個欄位、移除個欄位,然後還有修改欄位的,並且方法往下追可能有 4 ~ 5 層,那就真的很靠北。
然後根據以上的規則,我們應該就已經解決了 :
那什麼樣的 Query 架構是好的呢 ? 下面是我列出的特點 :
例如前台要的『 可顯示課程 』 就會不一樣
就如果要獨立、並且越解耦,那事實上最簡單的方式就是,所有的 Query 都重新寫一次,並且一個回傳的 scheam 就是獨立的,對吧 ?
但是以成本、時間考慮,我們不太可能這樣做對吧 ? 這樣做只會拉高我們的開發時間,與後來要維護的時間,所以我們這裡一定會需要一部份是可做到 DRY。
這個是和上面的 TradeOff,例如我很常在 code 中到看到 course.isPublic,但是那個 public 根本就是一中各表,每個地方的 public 的定義都不一樣,不同平台都不一樣,然後接下來這個又在內部用,但內部又沒 public 的概念。
主要分兩個:
但是這裡我覺得的比較難的是 :
我們很多使用情境都是需要考慮 UI + 權限 + 分頁,例如我們就有很多是限制那些畫面那個角色可以看,或是看多少。
如果每一次都像 domain model ( aggregate ) 一樣全部抓出來,但是我們可能只需要 1 個欄位,那真的很好費資源。
不能因為 query 需要的東西,而修變了 domain model。
大部份的情況下,Query 情況下高性能是不能避免,你想想你每一次看一個東西都要等一段時間,會有多火。但也不是說 Command 情境下不用,只是它比較重點在於一致性、好維護、好理解。
我會建議就去 Query 那取得 ReadModel 資料。如果只是在新增情況下,那種性能消耗不會影響太大,大部份我們需要處理性能的情況是在 Query。
某些方面我覺得可以,但比較建議還是至少分兩個 repository,例如 course-write 和 coures-read 之類的,然後 write 是專門用來更新 domain model,而另一個就是支援 query 用,然後這 2 個 repository 都可以在 command 與 query 使用。
我也看過有人推薦 repository 是只能給 domain model 那用,但是我們在使用時,通常很常需要先去查一下資料才能在進行運算,然後這時很常會有那種實際上可以在 command 與 query 情境下都可以共用的,我就會傾向寫在 course-read repository。
但這樣 coure-read repository 是不是就可能同時回傳 domain model 與 read model ?
根據 Clean Architecture 的定義:
離 I/O 越近的越低階
它的本意是不要讓 I/O 的操作與實作,影響到高階的業務邏輯那,所以正常來說,我們 GraphQL 那的應該算是低階。然後別忘了高階模組不依賴低階模組,雙方都要依賴 Interface。
所以用我們這裡的範例來看 QueryService 就算高階。
老實說這篇寫的不太好,因為很多東西都只是概念上的東西,但在實際在腦袋產出程式碼,又覺得實作上很麻煩,這個主題我可能還要在花時間想一下……