上一篇文章我們談完 Domain Event 的發送部份以後,接下來我這篇文章想要談談 API + DDD,會有這篇文章是因為我在實作時有碰到一些 API 設計的問題,例如 :
我之前一直在花時間看這個部份的東西,因為 DDD 內部實作還算蠻明確的,但是我每一次寫到 API 那一個層級腦袋都會跑出好多問題,而且好像在 DDD 的書都很少討論到 API 設計這一塊,不過這也合理因為 DDD 本來就是以 Domain Layer 為主。
接下來就開始吧。
這個應該是不少人第一個直覺會是以這個方案來進行,會有什麼問題呢 ?
接下來我們一個一個回答。
首先這裡我認為,如果要使用這個方案的話,那至少在 Controller 那一層一定要將 Aggregate 轉成 ViewModel 概念的東西,這樣我們就不會因為 UI 要啥,然後修改了 Aggregate。然後這個 api resource 要以 Aggregate 名稱來叫嗎 ? 我覺得可以。
接下來是如果是沒有 Aggregate 的情況下呢 ? 我自已在實務上碰過不少是 UI ,需要統計資訊的情況下,所以這裡很會有兩種解法 :
正常的情況下,不太可能一有 Boundd Context 就直接切微服務,我自已覺得那個不太好,BC 本質是給你獨立的單位,但不代表要切,因為微服務是有成本的,先排除掉溝通這一塊 ( 因為 BC 就是為了獨立 ),但是你還是要有維護的成本,而且運氣不好某個人又用什麼 c 語言寫,你真的會想打人。
所以正常情況下就是只是在同個 repo,然後根據 BC 切資料夾,然後這時 API Resource Path 會叫啥 ?
/{bounded context}/{resource}
? 但問題就在於你 context 重新命名與重構了怎麼辦 ?
比較好的解法是請前端打兩隻。但很容易發現生另一個 BC 是其它團隊的服務,而且還需要一些權限驗證,那這時通常也只能寫在這個 BC 裡了。
這個模式有個很大的問題就在於,你的 BC 很容易變成支援某個 UI 的東西,這樣就很容易導致前端不是根據 BC 職責來呼叫你,而是有什麼資料就呼叫你。想想這個情境,我們前端通常在找 API 時,很常先根據畫面上看發現,有這個他需要的欄位,然後就去找用的 API,然後如果這個 API 有提供,那就直接拿來用,對吧 ?
所以這個方案很容變成:
你的 Bounded Context 最後會變成不知道他實際提供那些資料,也不知道資料的職責
還有另一個問題就是:
你要 Bounded Context 重構怎麼辦呢 ?
然後上面整個問題的原因,我覺得是:
UI 與 Bounded Context 是沒綁定的
理想上,一個 BC 就是一個團隊的地盤,相對的 UI 希望也是,例如我們在 event storming 討論的 read model 本質上就是 UI 的表現,但是問題就在於:
我們實務上也很難用 Bounded Context 來限制 UI 的顯示啥,因為 UI 是用戶導向
所以這裡我們嘗試了使用 Backend For Frontend 的架構來嘗試解決這件事情,但是我們碰到好多的坑如下:
這題真的難,首先整個最大的問題在於 :
所有的分頁一定是要資料庫中處理
然後先說一下,我這裡分兩個情境下來說:
理想上每個 BC 都是有獨立的資料庫這個比較合理,但很多情況下資料庫的切分是真的難,所以通常會是先在 application 層開始整理地盤,然後慢慢的根據 BC 切出那個它們地盤需要用的表後,才有可能分資料庫。
然後第 1 種情況下,我們最後還是要走回,再某一個 BC 下,下資料表的 JOIN、SORT 分頁,最後再從這個 BC 回傳給 BFF,然後再回給前端。這樣就又變成 BC 回傳的資料不一定是這個 BC 職責的。
第 2 種情況下應該就是建立 data lake,然後再去這個地方來抓資料,但是你想想每一次開發時,你都需要做這些事情,才能完成一個簡單的 table,會不會想罵髒話。
這題目前真的有點無解,我們是走 1,然後我腦袋一直在想這是不是怪怪的。
就是我們通常有個功能就是列表上有個下載,然後他是下載所有資料的,不會有分頁,然後這裡就發現好慢啊,因為它 network latency 很高,因為它中間多了一層。
這裡我們最後變成這樣 :
雖然一樣很久,但就是別卡住用戶操作就都還好。
順到說明一下,為什麼不要在 BC 產 GCS 連結,不是說不行,但很多情況下是因為 BC 不是你管理。
這題我事實上思考很久,主要的糾結點在於:
- 我驗證的遊戲規則與資料都在 BC 內,但矛盾的是權限是用在 UI 上的
- 但是有些 command 類的權限就不算綁 UI,這種寫在 Aggregate 我又覺得很合理 ( 例如用戶是創作者的情況下,才能執行課程修改之類的 )
然後我們這裡是用 RBAC 所以這裡我整理一下情境:
但最後就變成 BFF 有綁 RBAC 的權限,然後在 Aggregate 裡面我們會在進行更詳細業務權限判斷 ( 例如這個操作要管理者並且有寫個評價的才能操作 )。
這個參考就好,我不覺得這是最佳解。
就是我們有時後不是會實作影片、圖片、或啥的上傳功能嗎 ? 影片還好,因為實務上我們通常不是直接傳影片到 application 再到 video 服務去,通常是前端會和後端要到上傳位置以後,就由前端上傳到那個位置,目前這個應該是所有影片服務商的正規流程。
然後接下來就是圖片、檔案的上傳,很多情況下我們要轉換,也就是說 client 先到 bff,再傳到 bc,雖然量小還好,但是我們的流量消耗要花 2 倍,積少成多,最後就發現這麼雲端網路費用變高了……
這裡我自已覺得比較好的解法也是和 video 用相同的方法,就是 client 是直接存到 storage。
因為 BFF 的本質上就是個為了支援 UI 的 Query 集中器,所以每當我們寫 command 相關的 api 時,都會發現我們只是將請求、包含內容直接丟到後面的 BC 中,聽起來還好,但煩的點是:
這題我有在考慮,就直接將 bypass 給 bc 就好,剩下就交給他處理,但我還沒仔細想想有沒有什麼問題,就單純先說說幹話。
以這種情況下,各 BC 事實上都會可以去修改這個 BFF ( 有錢的公司例外,因為說不定每個都是一個團隊 )
對吧 ? 例如建立訂單的情境就可能如下:
那是不是我們就可以在 BFF 呼叫這幾個 BC 的 API,那這個問題就在於 :
Domain Event 還需要嗎 ?
整體而言,理想上我是會希望走 :
第一個方案 + UI 是有對應到 Bounded Context
我以前在 kkbox 一起聽時,我自已覺得是有符合這個方案在走,開發起來真的很舒服,不會碰到 BFF 的坑,也很少需要到去抓其它服務資料,有需要也只有一點點 ~ 理想啊 ~
但是如果現在要叫訂個規則和 UI/UX 說我也不確定要如何定呢 ? 總結後我會開始覺得走第一條路,然後就讓 BC 有 UI 的東西,好像會比較好點,不過這個是我的想法,我現在也沒太好的解法 ~ 就把我碰過的問題列出來討論看看 ~