以 Data 模組化帶動 API 模組化(前篇)
歷史就像一本故事書,述說著我們的過去
大約在兩年前,我被賦予維護及開發公司與日本 KDDI 共同產品 Videopass 的 Content Management System (CMS)。該產品主要是提供類似 KKTV 的服務,而在 CMS 上就可以編輯他的影片資訊、頁面排版等等的功能。
然而該產品在最一開始開發的時候並沒有把 CMS 給考慮進去,同時開發的時程緊湊,許多部分都是先從 Walking Skeleton 疊加上去,導致許多部分必需要 Workaround 或是 Highly Customize ,在維護及開發上需要注意許多盲點,很有可能今天這裡改一塊,另一個地方就爆炸。
此外,由於 CMS 的特性,許多新的 Feature 除了在 CMS 上可以編輯以外,還要考慮產品是否可以顯示,產品端的 API (Layout API) 是否有支援這樣的功能等等,因此往往除了 Maintain CMS 外,還需要同時 Maintain Layout API ,也因此當 CMS 炸了, Layout API 也會一起炸; CMS 上 Workaround , Layout API 也一起 Workaround 。
也因此當時我們每一次 CMS 的 Release 都需要從晚上11點持續到隔天早上5點(註1),在 QA 時除了要驗證 CMS 本身以外,還要同時做 End-to-End 的測試(註2)。
註1:由於我們的用戶在日本,因此需要在日本時區的凌晨12點~早上6點之間完成 Release。
註2:當時我們還沒導入 Neil 大大的 Infrastructure-as-code 及 CloudFormation 的架構,因此還沒辦法做到 Partial deployment,大多的 deploy 都是手動。
由於幾乎每次都必需要 Over night release,因此我們也開始找出方法並嘗試不要 Over night release,同時因應客戶常常變動的需求,讓我們萌生了 Refactor 的念頭。可惜現實往往都是殘酷的,我們並沒有足夠的時間把整個 CMS Refactor,因此我們開始嘗試在小部分新的 Feature 上導入模組化的概念。在 CMS 上新的 Feature,大多是客戶希望能增加一些新的頁面,或不同 Platform 之間會有不同顯示的資料等等,因此我們希望能設計 Template 的概念來讓往後增加新的 Feature 時能減少許多功夫。
當時我們大多是利用 Configuration 或開關的方式去分類管理我們的資料,以便於下一次客戶需要再多一個平台,或多一個頁面的時候可以重複利用這樣的功能。
// 用 config 解決 workaround 的問題
[ 'alias' => [ 'androidstb' => [ 'target' => 'android', 'collection' => ['item_groups'], ], 'webtv' => [ 'target' => 'android', 'collection' => ['item_groups', 'promotions'], ], 'appletv' => [ 'target' => 'ios', 'collection' => ['item_groups'], ], 'sonytv' => [ 'target' => 'android', 'collection' => ['item_groups'], ], ],]
與此同時,我們也偷了一些時間,開始嘗試開發 CMS v2,在 CMS v2 上嘗試打掉重練幾個比較核心的功能,並選用了一個不同的程式語言 (Golang) 來 Proof of Concept (PoC)。此外,我們更在高雄辦了一場活動,召集公司的許多成員一起 Brainstorming (註3)構思瞭解 CMS 該有什麼樣的需求,並以此作為模組化的範本。
註3:Ava大大還將此次的 Brainstorming 寫了一篇文章
百折不屈的 CMS v2
由於線上的產品已經有一定的穩定性,因此如果需要大改許多核心的功能,將會需要進行一次大型的 Release,同時需要與客戶溝通,讓客戶能夠支持我們的決定,但這樣就會造成一些新的需求有所延誤。然而這樣的理想世界,在現實中是很難達成的,客戶也需要為了他們自己業務的成長,而增加許多需求,因此若要延誤需求,且無法確定大改後的新版本是否穩定,客戶是不會允許有這樣的風險產生的,也因此我們的 CMS v2 就此胎死腹中。
不過雖然我們 CMS v2 無疾而終,但我們從中進行了許多大大小小的 PoC,並從中學習到許多,例如在 CMS v2 的開發過程中,我們採用了 GraphQL、使用 WebSocket、用 Golang 開發模組化、Login 走標準 OAuth 流程等等。
錯誤經不起失敗,但是真理卻不怕失敗。 — — 泰戈爾 《飛鳥集》
新的契機OTTBuilder — 「跟馬叔叔一起搖滾學吉他」
在我們還來不及傷心的時候,公司在這個時候想要有新的嘗試並開發新的 OTT 市場,希望能夠避免過去的慘痛經驗,實現能有一個我們自己的服務,以提供 Scalable video streaming platform 為公司的主要服務,而不再只是單純的代工廠,在經過不少的嘗試後 (在 OTTbuilder 之前亦有不少的嘗試),我們試著透過小型 PoC 的方式來找尋不同的市場,同時訓練我們的模組化能力。
在這個時候,套用在 OTTBuilder 的第一個 Case,馬叔叔就這樣登場了(註4)。我們與馬叔叔合作,協助馬叔叔透過我們所建立的平台來增加馬叔叔教學產品曝光度及方便性。
在過去,馬叔叔是透過販售教學 CD 的方式來營利,但我們提供了一個線上服務,讓馬叔叔的粉絲可以直接透過 Facebook 的聊天機器人來購買他的教學影片,並直接在線上觀看。
這項產品進展速度有如神速,從最一開始談產品需求,一直到第一次的正式 Release,僅有短短的三個月時間,我們也在此真正的運用了 Scrum 的精髓,將 Scrum 的方法運用的淋漓盡致。若有機會,往後會再分享我們如何 Run scrum。當然,我們一開始無法避免的,又多多少少走了過去的老路,產品趕鴨子上架,大多是 Walking skeleton。
註4:針對馬叔叔的產品,我們團隊中最強大的 Intern 有在他的文章中描述過這件事。
就在經過幾次的 Release 之後,我們遇到了我們的瓶頸…
馬叔叔希望我們的機器人能再生動一點,多一些不同的互動,根據不同的訊息能夠有生動有趣的回覆,但是…我們的 Walking skeleton 就只能允許我們在 Code 裡面加規則!
由於許多 Chatbot 聊天的流程是直接寫死在 Code 裡面,要改流程或文字就要改 Code,改 Code 就要進行完整的 QA 後再 Release ,而我們最快也只能 2週 Release 一次,因此如果要改流程或文字,就必需要等2週!
在此這樣的前提下,我們做了一個決定,在產品還沒有變的很大之前,馬上進行模組化!邊 PoC,邊模組化!模組化後 PoC,PoC 後再模組化!
不斷 PoC,不斷模組化
由於 Facebook Chatbot 的特性,Facebook 規範了許多顯示及互動用的 Template (註5),因此許多顯示只需要符合 Facebook 的 Template 規範,就能創造許多不同的互動,也因此我們不會有太多特殊的顯示需求!
註5:Facebook 所定義的 Template,允許我們只要傳送給 Facebook 定義好的 JSON Format 資料,就可以在 Messenger 上顯示。Template 上定義好了 顯示用的 Image、Title、按鈕文字及按鈕按下去之後需要 Callback 的 API 路徑等等。
此外,由於我們大多是開發 CMS 出身,因此我們理想中希望能讓 Chatbot 模組化,並讓 CMS 可以控制聊天的流程,並可以更改每一個 Template 上的圖片、文字或互動的流程。
一開始我們一樣先採用 Configuration 的方式來控制我們的 Flow,至少將不少寫死在 Code level 的東西給抽出來,變成不再需要爬 Code 去改 Flow。
然後我們再想辦法讓 Configuration 轉成 可被隨時存取的 Template Data,並讓他成為一個在 CMS 上可以隨時變更的 Flow template,並制訂了許多 Template 的規則。
接著,我們再研究 Facebook Template,將原本顯示的資料進行分類、統整,將相似度過高的資料抽象化成一種資料。
例如 Template 中的 Generic 及 Buttons,他大致上的區別僅是一個有圖片,一個沒有圖片。持續將 Template 的資料抽象化下去,會發現 Generic、List、Buttons、Quick Reply 等等的顯示,僅是不同小元件上的組合 (圖片、文字及按鈕),因此僅需要將我們的各種資料,轉換成這些小元件上的資料,那麼我們就可以模組化我們的產品!
因此,在這裡我們建立了一套規則,只要符合規則,就能動態的調整 flow。首先我們將 Flow 的設定綁定 Template,並將 Template的每個欄位設定好一些對應的變數參數,讓使用者可以隨意調整並填入這些參數,而這些參數的來源就來自於我們 DB 上的某些欄位。也就是說,我們定義了規則,讓Template上的每個欄位,都可以透過動態 Mapping 的方式,來 Mapping DB 中的資料。
此外,我們產品 DB 中的結構有許多不一樣的資料類型,例如 Video、Product (把 Video 包成產品,額外需要的資訊)、Attachment、Q & A、Image、File、Series 等等。既然我們能將流程及顯示模組化,那為什麼這些資料就不能也模組化呢?
例如 Q & A 可以歸類為 Product 的一種,Video 也可以歸類為 Product 的一種,Series 也可以歸類為 Product 的一種,而 Product 僅是不同產品類型的集合體,將不同的產品打包成一個完整的 Product 後再加上描述,而 Video 其實也僅是有 Title、Thumbnail (image)、Media (file) 的集合體,假設我們將這些資料抽象化,打散後再重新組裝,那不就可以達到資料上的模組化?
換句話說,我們最後所做的事情,就只會是將一堆定義好的資料 Transform 成為另外一堆定義好的資料而已,而我們僅需建立這套規則,就可以達到模組化的需求。而開發模組化的 API,就像在開發 ORM,建立規則去取得資料,套用規則去 Transform 資料。
下一篇,我將會分享,我們如何將馬叔叔產品的經驗,運用在更大型的專案上。