Coding Story

在黑暗中寫故事 👻

0%

Android Clean Architecture - 仍然好用的架構

前言故事

小唯加入當前的公司後,主要負責一款 Android App 的開發以及維護,那是一款相當有歷史年代的產品,介面是老式的風格、使用了許多 deprecated 的技術、連內部商務邏輯也都幾乎已經不可考了,要不是 Google 要求每年的更新 Android Target SDK 版本,相信有許多部份應該是會更老舊。小唯進公司沒多久後,機緣巧合內部設計師與企劃有對 App 改版的需求,想將產品改造成一個介面更現代化、資訊架構更有條理、以及為將來要推出的新服務事先鋪路,所以小唯也趁此機會,針對 App 的軟體架構進行了一次徹底的打掉重練。

改版之前

原始版本的 App,是一款集所有需求於一身的龐然大物,內含少說上百種以上的大小功能,但本身可以說是幾乎沒有程式架構可言。試想當年這款產品是怎麼開始的(以下假設這款產品是一個通訊錄 App):

2010年,產品企劃:「嗨~ 公司打算將目前線上那款通訊錄服務,上架到當前正在流行的手機平台(Android & iOS),我們打算先做聯絡人的基本功能…」

一年後,產品企劃:「之前上架的那款 App,反應不錯,我們打算將它與 Facebook 做整合…」

半年後,產品企劃:「半年前 Facebook 的整合頗受好評,所以我們將另一個通訊錄服務做整合…」



接著,更多的功能不斷上架,即時線上狀態、聯絡人相簿、Instagram 整合,需求一個接一個的來

最終,這款通訊錄 App 就變成這樣:



功能需求一個接一個,當初的開發人員就是一個個疊加上去,上層的功能開發時,直接或間接的使用到下層的功能、服務、或介面等,造成強烈的耦合,如下圖:



圖片中,聯絡人的線上狀態依賴之前 Facebook 整合的程式,Facebook 又依賴下層聯絡人的基本功能,每個功能都是如此的環環相扣,每一次修改,都需要確定對不同功能的影響範圍。到最後,功能越做越多;相依性就越串越大;後面接手的人也越來越痛苦。如果某天需要拔除某個功能,會拉出一串串的相依性,讓整個過程幾乎是難以預估工時以及風險程度。也因如此,程式碼中有需多看似沒用到的程式碼,誰也不敢移除,因為它的確有被引用,但一堆引用點又無法確認到底哪些是有用,哪些是無用的。歷史悠久的程式碼中途已經不知道經過幾手了,許多的當初的合理行為,現在已經變成歷史包袱,到此地步,整個 App 重新設計已經是勢在必行了!

理想架構

自從改版規劃確定後,小唯就在四處尋找合適的架構,最後是選用 Frnando Cejas(Android10)Android Clean Architecture(以下稱 Android10 架構)。因為是幾年前選的架構,所以小唯是用 Java 版的,作者也有移植 Kotlin 版本。

Android10 架構有幾個重點功能,對於改造架構上有很大的影響:

  1. 明確的相依性設計

    首先,這架構上的相依性設計上非常的簡單:

    • Entities 是商務邏輯上的最基礎元件,以通訊錄這個 App 來說,Entities 定義了聯絡人的格式,通常是使用 POJO 格式,可由 Clean Architecture 中的 Domain 看得出來,這是個純 Java library,連對 Android 都沒有相依,在 App 中所有行為都是針對 Entities 來做操作。

    • 接著 Use cases 定義了 Entities 可以有什麼樣式的操作,例如取得聯絡人列表、聯絡人詳細資訊、新增、刪除等操作。

    • Presenter 則是實作了 App 中的操作邏輯,並把 UI 與 uses cases 串接起來,它本身不限制對應的 UI 要如何呈現,只要符合 presenter 定義的規格。

    • 最後,UI 實作了畫面該如何呈現給使用者,它必須符合 presenter 定義的規格,但不限定方式,所以不管是 Activity、Fragment、Dialog、甚至是 console (也許 debug 用)都能使用。


  2. 工作分明的架構

    在 Android10 的 Clean Architecture 規劃了三個模組,分別是:

    • Domain 包含了所有商務邏輯上的基礎元件與互動行為的定義,也就是 Entities、資料層的介面接口,以及提供給 Presentation 模組的 Use cases。常見的誤解是,這個模組並不限制資料層面該如何實作,也不管 Uses cases 會被誰使用,如何使用。

    • Data 實際處理資料的來源,資料該從哪裡來、該如何處理、是否該快取等等。只要最後回傳的格式符合 Domain 的定義,資料則是可以從各式來源而來,像是雲端、資料庫、檔案、快取、甚至是模擬數據。

    • Presentation 負責將 Uses cases 的功能,轉為介面提供給使用者。


  3. 易替換的 presentation 模組

    Android10 的 Clean Architecture 在 Presentation 中使用的是 MVP:

    s MVP

    原作者所提,Presentation 層所使用的架構不拘,可以是 MVP,也可是 MVC 或 MVVM,唯一一致的地方只有是都透過 Domain 層的 Use cases 來與資料做互動。


  4. reactive 的資料流向

    Clean Architecture 使用 RxJava 來聯繫所有資料的流動,所有來自 data 層的資料都是使用 Observer 包裝過。RxJava 提供了相當多便捷的操作,像是之前提過的,利用 RxJava 來把多個 API 組成一個結果,或是監測資料是否有更新,並做即時的畫面處理。


更新結果

利用 Clean Architecture 重整後,原本通訊錄的 App 重構新的樣式:


原本堆疊的功能,經過重新設計後,Domain 與 Data 的部分被取出來,剩下的則以垂直的方式排在 Domain 層上,原本相互引用的相依性,變成只向下相依,功能之間的依賴,也變成需透過 Domain 間接提供。如此一來,各功能間的介面也得以被抽象出來。新的架構讓原本的 App 變得:

  1. 相依性大幅降低,各功能介面被抽象後,更容易開發、維護與測試。

  2. Presentation、Domain 跟 Data 切分後,分工方式也更多元,從原本按功能分工的方式,之後也能以不同階層分工,有點類似前端與後端的分工。

  3. 與企劃規格更容易相互應對。Domain 層內的 Entities 與 Use cases 對應了規格上的元件與行為操作。

新架構花了小唯團隊將近一年的時間,才把所有功能陸續移植,中途還不斷收到需求變更,但靠著 Clean Architecture 架構上的彈性,移植上減少了很多工時的浪費。例如過程中,有一變更是將所原本直接透過 Retrofit 使用線上 API 的存取服務,改由透過 SDK 來存取資料,在這個例子中,只需要修改 Data 層中如何存取資料的方式,Domain 與 Presentation 兩層完全不需要變動。這正是 Clean Architecture 的優勢所在,雖然剛開始資訊架構較複雜,但是等專案成長到一定階段,所有辛苦都是遲早能回收的~

相關連結: Demo 專案

------------- 本文结束 程式架構就是最重要地~ -------------