旋轉拍賣 Carousell Transactional Service之產品架構演進

MinHsien Chang (LightBlue)
Carousell Insider
Published in
16 min readFeb 24, 2020

--

筆者2019年初加入旋轉拍賣 Transactional GC (前身為 Convenience Team),主要負責平台的支付、物流相關使用者體驗。旋轉拍賣(下稱 Carousell) 2017年在台灣市場介接 7–ELEVEN 交貨便,用戶可在門市寄件及取貨付款,2018年在新加坡發表 CarouPay,整合信用卡支付及行動支付 DBS PayLah!(*1),以及追蹤 SingPost(新加坡郵局) 物流狀態的一站式服務。2019年在幅員廣大的馬來西亞發表了新一代的買賣家訂單整合服務 ,同時將新加坡的 CarouPay 品牌重塑為 Carousell Protection,替買賣家雙方提供可信賴且多元的交易方式。

本文會介紹 Transactional Service的系統架構以及在不同階段產品演進的過程,希望幫助讀者了解從單一到多國市場常會遇到的業務問題,以及工程團隊相對應的開發、營運流程改善,歡迎大家多多交流。

2019Q4 Carousell Market Scope

Transactional GC Team 主要業務

Carousell 是個源自新加坡的C2C電商平台,目前在六個市場營運。由於介面親和,我們受到城市型國家如新加坡及香港用戶的喜愛,但由於缺乏搭配的支付及物流服務,交易方式一直侷限於面交及私下轉帳郵寄的方式,難以在地域較大的市場擴大版圖。另外私下郵寄常會遇到買家付款後沒收到貨或是品質不合預期的狀況,長久以來影響用戶對平台的信心,一套能夠適應各市場特殊交易習慣的系統極其重要,也是我們要解決的主要問題。

Transactional Service 演進

Carousell 的通用後端架構可以參考 hothero 的文章,包含 Load Balancing、K8S cluster、HA以及 Data Storage等等。

Carousell 的服務都是基於同一套 框架 — Orion 來開發 Microservice,工程師只需要專注在 Protocol Buffers 的定義(包含 API request/response)以及 service function 的商業邏輯即可上線,其餘的 configuration、Service Observability、甚至是 HTTP/GRPC client/server scaffold 都可在彈指之間構建完成。

用一個單純的API範例來說明用戶如何從 APP 呼叫相關的服務,假設用戶在下單時輸入了一個優惠代碼,後端服務需驗證此用戶是否符合使用條件:

  1. APP 發送優惠代碼驗證 API Request (e.g. POST /order/validateCoupon)
  2. Request 進到 Routing Proxy,紀錄 Access Log 並根據 Nginx Config 決定此 Request要給那個服務的 HA(Nginx + Port Forwarding => Internal Load Balancer)
  3. 進入每個 Microservice 各自的業務邏輯,呼叫各服務後由 API Gateway 組裝好 Response,APP 上就會顯示用戶輸入的優惠代碼是否有效。

其中 Transactional Service 涉及金流及整合大量第三方服務,業務邏輯相對複雜且需要更精確的 Rate Limitation及 Throttling保障整體服務安全,除了與公司其他服務共用 API Gateway 以外還增加了一層 Fulfillment Gateway 負責定義及統整與 Client 溝通的 Request/Response,以下章節依照時序描述我們的產品迭代過程。

2017,台灣超商取貨付款

Carousell 在台灣選擇與7–11合作,賣家可以在鄰近的7–11店家寄貨,買家也可以在指定的店舖付款取貨。跟第三方的深度整合除了技術上的難度外更常遇到的還是溝通問題,如何在對方維修時服務不致中斷,如何處理雙方服務的交握,Restful API 如何處理 failover, throttling, 以及 rate limitation 都是難點,雖然公司當時已有一層 Django 負責對客戶端的認證及導流,我們利用 Fulfillment Gateway 設計來解耦跨團隊開發的依賴,Fulfillment Gateway 利用 GRPC 跟後方的 Payment、Order、Logistics 等服務溝通,一個完整定義全公司服務的 Protocol Buffers 倉庫就顯得十分重要,一方面可以當成前後端呼叫 API 格式的 single source of truth,另一方面可整合如 protoc-gen-swagger 即時產生文件供前端工程師參考。

整體服務的核心是訂單系統 (Order Management),包含訂單流程、介接物流及支付等服務、APP訊息推送及寄發 email 等等業務邏輯。物流服務 (Logistic Gateway)則負責處理物流狀態,內有一個抽象的 Finite State Machine 用以整合各市場的物流第三方發送的事件。另外有一個完全對接 7-ELEVEN後端出帳系統的服務(7–11 Integration),負責拉取賣家寄件、買家取件、運費明細甚至貨品遺失賠償等資訊,雖然大多數的事件都可以 Webhook 傳遞到 Carousell,但經由 Internet 傳遞偶爾會發生 timeout 或被用戶端裝的 VPN擋掉等問題,這些定期拉取的資料能當成 Single Source of Truth ,可參考下面的買交易流程圖:

The flow of 7-ELEVEN integration

2019年我們在原有系統設計下小幅重構台灣的業務邏輯並推出兩項新功能:

  1. 優惠代碼機制:相對以往的全站免運活動,提供台灣 Marketing 團隊更靈活的行銷工具,可針對特定買賣家、滿額商品、商品類別、限時促銷商品等設置優惠代碼,精準提供用戶購物優惠。
  2. 新版賣家寄貨流程:以往賣家在寄貨時必須先給店員60元的7-ELEVEN交貨便手續費,當買家未取貨時就會造成賣家損失,經過跟7-ELEVEN交涉後雙方分別改善接送單系統,並於2019十月順利介接系統上線,涉及到複雜的endpoint切換、舊訂單資料 migration、對帳系統更新、rollback練習等等 (感覺可以再寫一篇血淚文)。

2018,新加坡 CarouPay

在台灣的初步成功後,下個目標就是提供有保障的支付體驗給新加坡用戶,我們必須擴充原本架構以支援兩個不同市場需求,此時會遇到許多與開發台灣版時截然不同的問題:

  • 便利商店未提供金流物流服務,需要整合當地其他第三方服務。
  • 新加坡幣需要顯示到小數點後2位,在台灣時只需要顯示整數。
  • 需整合多種情境,包含信用卡及行動支付。
  • 正式跨入第三方託管領域 Escrow,系統需符合法規稽核要求。

在這個版本我們同時跟數家支付及物流追蹤的第三方廠商合作,背後的市場評估、用戶研究、產品需求及業務流程可參考 Damiano 的文章: All about Convenience。

2019,馬來西亞及新加坡 Carousell Protection

這是個快速成長的一年,團隊也吸收了新加坡的經驗開始拓展其他國家的市場,更希望可從城市型(多數交易仰賴面交),跨入國家型市場(依靠物流寄件收貨),而Carousell 在鄰近的馬來西亞已經有堅實的用戶基礎,成為優先導入 Transactional Service的市場。

我們在服務的基礎架構下做了幾項重要的改變:

  • Payment Async Flow
  • State Machine Reconstruction

在電商平台上下一個訂單並付款需要經過很多複雜的步驟,若要等所有步驟都做完會造成用戶在APP上面感受到明顯卡頓,若第三方服務此時出錯,更會造成訂單狀態不一致導致交易失敗,Payment Async Flow 就是要解決此類問題,當用戶按下訂單鈕後,後端接收到 Request 馬上讓前端顯示空白訂單,用戶此時可以繼續瀏覽其他商品或到聊天室跟賣家溝通細節,等所有支付細節都已完成 (如信用卡或行動支付扣款成功),後端會推送訊息將訂單的所有細節完備,買家此時會收到推播訊息顯示訂單已成立,完成這個重大變更後我們的訂單失敗率大幅降低。

我們使用狀態機 (State Machine) 來管理各市場的訂單、支付及物流狀態,初期每個市場都有自己的狀態機,大多數的狀態也難以共用,時間一久就發展成下圖這樣讓人頭暈的狀態:

Complex FSM of user experiences in different markets

為了避免每增添一個新市場都必須 Clone 一份可能大多數狀態都用不到的狀態機,我們決定將核心狀態收斂,狀態變更也都改為單向,大幅度簡化狀態機實作的複雜度,也提高程式的可讀性。

開發馬來西亞版本時,團隊也利用翻新的架構在新加坡推出了嶄新的服務,例如訂單完成時將原本需要2~4天的轉帳時間縮短為2分鐘的 Instant Balance,以及大幅放寬用戶進入門檻的 Tiered ID Verification(*3),都是推動新加坡業務的重要里程碑。

談完了後端的核心架構變更,開始來說說用戶直接接觸的APP及網頁。

Unified Flow for Transactional Experience

Carousell 的客戶端包含 iOS, Android 以及 Web三大平台,每個平台都有不同的架構及語言 (Swift, Kotlin, React 等等),在拓展市場時等於要維護多維度的實作,必定導致開發速度變慢、 bug 數量激增、修A壞B等嚴重問題,於是我們團隊決定在開發馬來西亞版本時重新釐清已在線上的新加坡業務流程,並納入新的 feature requests 。最終統整兩個市場的 UI 呈現、業務流程乃至於客戶端的實作。這個過程涉及產品規劃、設計以及工程端實作,是我在職涯中第一次接觸大規模的產品重構,經歷許多跨部門、市場的意見整合,最終提供用戶一致且精煉的使用體驗。

Re-designed deal detail page (left: legacy version, right: the design of the current live app)

產品迭代速度是近年來業界看重的工程團隊評估標準,除了嚴格的開發規範、乾淨的系統架構、充分的測試以外,自動化開發部署流程也十分重要,下面的章節會開始著重 SRE 領域。

開發及部署流程

服務多國用戶必須基於穩定的基礎架構及快速反應的開發流程, Carousell 目前以 Google Cloud 作為主力雲端服務商,我們在 GKE 上面也有多個 cluster,分別負責資料分析、生產及開發環境之microservice。從工程師開發到上生產環境可拆分為下列步驟:

Carousell 的軟體開發部署流程
  1. 工程師將代碼推到 GitHub,PR (pull request)通過 automation code checker,並得到兩位 reviewer 同意 merge。
  2. 上 Jenkins 使用 Pipeline 建立 service docker image
  3. 將 docker image 推到公司私有的 container registry,GKE 內所有的服務都應該從這裡拉取避免服務遭到竄改。
  4. 部署到 staging 環境,基本上 Load balancer, Internal HA, Data Storage 等都跟生產環境類似,但跟生產環境實體隔離。
  5. 利用 API 測試再度驗證 Integration Tests, Performance Tests 等環節都順利過關,我們的QA在此時也會在 APP 端做相對應的手動測試(Acceptance Test),確保業務邏輯跟最初產品設計一致 。
  6. 測試通過後將服務布到 Canary Node,同時讓一定比例的真實用戶流量打到這個節點,工程師同步會觀察流量是否正常,是否出現新的 error 等等,確定無誤後將其 Promote 取代其他現有的 Pod ,上線完成。
  7. 若上線後發現嚴重錯誤,只需要在 Jenkins 執行回滾 (rollback),服務就會在數分鐘內恢復為任意正常作業的版本,整個回滾流程不需任何人力介入,大大減少手忙腳亂造成的風險。

在 Carousell 這樣方便的開發流程已經是工程師日常,每週都有超過200個 Backend Deployment,快速迭代的結果讓團隊無論是開發新功能以及修 bug 都能夠無所畏懼(咦?)地快速上線,支撐日益複雜的跨國市場需求變更。

服務監控及維運

服務監控的主流概念為 Service Observability ,也就是服務是否容易被觀測及度量,包含計算資源用量、網路 I/O等等, Carousell 使用下列的 OSS 來監控服務及定位問題:

  • Log: ELK + Sentry
  • Metrics: ELK, Grafana, Prometheus, and Hystrix
  • Trace: Zipkin with OpenTracing

除了 infrastructure 層級的監控,如 CPU/memory以及 Network I/O以外,跨國服務更應該著重每個市場的使用者體驗,於是我們導入了 Business Metrics 的概念,利用 Prometheus client 將業務數據拋出來,然後在 Grafana 繪出即時圖表,幫助維運及開發團隊掌握任何交易異常的狀況。

Transactional GC Team’s Business Metrics

我們在此定義同步指標,例如各市場即時的成交數量及AOV (Average Order Value) 、Order 成立時間等,利用 P90, P99 等數據抓出極端值,進而分析系統瓶頸,逐步改善服務品質。其他還有各市場特色指標,例如台灣會觀察 7–11 用戶取貨狀態,新加坡會統計面交及郵寄商品的即時數據,監控公司帳戶內餘額是否足夠支付貨款也是核心指標之一,造成跳票可是會大大損害用戶及合作廠商的信任關係。

由於 Transactional GC Team 負責支付及物流體驗,需要接非常多外部系統,所以我們也在 Dashboard 加上許多監控第三方服務品質的指標,並且設定 Prometheus AlertManager ,遇到問題馬上由 Slackbot 通知所有工程師並跟對方窗口聯絡,在溝(ㄔㄠˇ)通(ㄐㄧㄚˋ)時有即時數據協助釐清責任歸屬,才能在最短時間定位並解決根本問題。

結語

電商對於業務需求的標準較高,支付服務則更要求服務穩定度及可用性,Transacrional GC Team 經過三年多的產品迭代,已經可以同時滿足城市型及國家型市場,跟合作夥伴更是互相支援成長。放眼未來我們會打入其他的東協國家,繼續跟更複雜的用戶情境及更巨大的流量作戰,到時再跟各位分享最新架構!

如果這篇文章有幫助到你,請按左邊的 Claps 拍手鼓勵,可以直接按住滑鼠觸發連續攻擊 :)

Carousell 台灣團隊現況

台灣辦公室目前有四個工程團隊:

  • Transactional GC
  • Buyer Experience
  • Search and Recommendation
  • Data Engineering。

其中 Transactional GC,以及 Buyer Experience 屬於 Delivery team,團隊內包含 Backend、Web、iOS、Android engineers自成一個 full-function team,我們負責不同市場的用戶體驗,與在新加坡的 PM/PD以及各市場的 Business Leads 都有許多的溝通機會,甚至有機會直接飛去國外跟用戶面對面接觸,歡迎喜愛開發用戶導向服務並實踐技術結合商務的朋友加入。

Search and Recommendation以及 Data Engineering的成員皆為 Backend engineer,部門專注於利用大數據分析及人工智慧給予用戶精準的搜尋體驗及個人化的推薦服務,技術上十分有挑戰性,能跟 Data Scientists 一同探索各種 modeling 可能性。

文末不免俗到了招募的工商時間,歡迎有相關經驗的朋友來聊聊,目前工程類職缺如下:

TW

SG

備註

*1 由新加坡DBS銀行開發的行動支付系統,可於實體商店或虛擬通路付款,類似街口支付。

*2 CarouPay 為實名制,一開始用戶得輸入很多個人資料並傳送身分證照片,大幅降低用戶註冊的動力,目前已經改善為用戶僅需填寫基本的姓名、生日、住址等資料即可開始使用服務。

--

--