關于COLA框架的一些總結和思考
寫在前面: 其實之前一直想匯總一篇關于自己對于面向對象的思考以及實踐的文章,但是苦于自己的“墨跡”,一延再延,最近機緣巧合下仔細了解了一下COLA的內容,這個想法再次被勾起,所以這次一鼓作氣,準備好好梳理一篇。至于標題,因為是被DDD和COLA喚起的,索性就叫這個吧。
思維:面向對象和面向過程
領域驅動設計本質上是講的面向對象,但是談面向對象,始終無法繞開面向過程,所以我們先好好說一下面向過程和面向對象這兩個概念。
什么是面向過程呢,其實就是我們學習編程時最初被植入的邏輯,這也是很多人即便學了面向對象后,寫的代碼卻四不像的原因,因為這個思維是根深蒂固的。我想大多數院校科班生,第一次接觸編程都是C語言,從一個hello word開始,然后便是if else、for循環,其實if else 思維便是面向過程的基本邏輯,就是做事的 “步驟”,比如經典的圖書管理系統的課設,基于面向過程去設計編碼,想的是新增圖書、修改圖書名字、描述;是根據輸入的參數,進行if else的選擇,然后進入對應的流程,在流程里去先做什么,再做什么,關注點在圖書的操作上, 是專注于事情本身,專注于做事的流程;所以面向過程更適合去做一些底層的,基于硬邏輯的內容。當需要做的東西規模過大且頻繁變化時,代碼量和改動成本也會增加。
面向對象相對于面向過程,它更偏向于概念的抽象和建設模型。同樣圖書管理系統,面向對象考慮的重點則變成了圖書(而不是操作圖書數據這件事),一條條的圖書數據,在內存里就是一個個的個體對象,至于圖書的各種操作那是細節的內容,它不會去關心,只要定義了圖書這個對象,那對于圖書的操作那都在對象自身的事情,面向對象專注于事情的主體,是以主體以及它們之間的行為組合去構建程序。當然對象自身內部的行為又是一個個的面向過程組成,這就是在編碼的時候最容易讓人模糊和把握不準的地方。面向對象把程序設計又拔高了一層,把細節忽略,站在更高維度去構建程序。
通過一個簡單的例子來對比一下這兩種思想:數據庫中存在所有學生的數據,比如姓名、學校、專業,下面需要實現一個自我介紹的功能,描述方式為:我是XX,畢業于XXX學校,用面向過程的思維實現是這樣的:
public class test{ public static void main(String[] args){ desc("張三"); } public static void desc(String name){ //查詢數據庫 connection = DbManager.createConnection(root,XXX,3306); //查詢數據 Map<String,Object> a = connection.query("SELECT name,school FROM tb_student WHERE name = #{name}",name); System.out.print("我是"+a.get('name')+",畢業于"+a.get("school"); } }
拿到需求,我們關注的自我介紹這件事,只要完成這件事就好了,所以直接定義一個過程(方法、函數)然后過程里去根據需求把這件事完成;
而使用面向對象的話,面對需求,首先需要確定主體,也就是學生對象,然后學生對象有姓名、學校、專業這些屬性和一個自我描述的能力。
public class Student{ private String name; private String school; private String discipline public Student(Map map){//省略構造函數內容} public void introduce(){ System.out.print("我是"+this.name+",畢業于"+this.school); } }
然后定義另一個對象,數據庫對象,數據庫對象有一個可以查詢學生對象的能力
public class Db{ private String url; private String username; //其他屬性…… public Student searchStudent(String name){ Map a = connection.query("SELECT name,school FROM tb_student WHERE name = #{name}",name); return new Student(a); } }
最后通過使用兩個對象,來完成這件事
public class test{ public static void main(String[] args){ Student object = Db.searchStudent("張三") object.introduce(); } }
通過上面的代碼,可以發現,面向對象的實現似乎需要更多的代碼來完成這件事,沒錯,這是事實,雖然在設計上我們忽略細節,可是編碼上是無法忽略的,甚至使用面向對象成本更高,但是注意我這里說的是針對咱們這個場景需求,當前場景如果戛然而止,確實面向過程方式更精簡,但是如果需求繼續增加,隨著業務增加、需求變大、邊界變寬,面向過程可能就需要追加更多的過程代碼去完成,而面向對象可能需要的是調整對象的組合方式或者對象本身的擴展去完成,所以,面向對象在代碼層面最大的優勢就是 復用和擴展
業務開發面向對象理論:領域驅動
說完面向過程和面向對象,再說一下關于面向對象的集成方法論—領域驅動,它的本質是統一語言、邊界劃分和面向對象分析的方法。簡單點來講就是將OOA、OOD和OOP融匯貫通到系統開發中,充分發揮面向對象的優勢和特點,去降低系統開發過程中的熵增。狹義一點解釋就是如何用 java 在業務開發中寫出“真正面向對象”的系統代碼。
關于COLA框架
COLA 是 Clean Object-Oriented and Layered Architecture的縮寫,代表“整潔面向對象分層架構”。由阿里大佬張建飛所提出的一種基于DDD和代碼整潔理論所誕生的實踐理論框架,詳細內容可閱讀《程序員的底層思維》和相關git代碼去了解
項目地址:GitHub - alibaba/COLA: ?? COLA: Clean Object-oriented & Layered Architecture
前面我們分析了貧血模式在面對復雜業務和較長周期的系統迭代時存在的不足,使用充血恰好能解決貧血模式的不足,但充血的概念模糊和實現困難又成了新問題,這里大佬張健飛通過對DDD的理解和業務系統開發經驗的積累,摸索出了一套實踐方案來解答了如何落地充血模式,并產出了一套可以使用的代碼框架——COLA。
下面把COLA框架的主要架構梳理一下,這里的框架圖采用COLA4.0版本(當前作者認為的最優形態)

COLA整體上由兩部分組成,代碼架構和通用組件,兩者并非強綁定,代碼架構是一套基于maven的代碼模板,通用組件則是可選項,可以理解為Common組件包。
Adapter層(適配層)
負責對前端展示(Web、Wireless、WAP)的路由和適配。對于傳統B/S系統而言,Adapter層就相當于MVC中的Controller。
App層(應用層)
主要負責獲取輸入、組裝上下文、參數校驗、調用領域層做業務處理,以及發送消息通知(如有需要)等。層次是開放的,應用層也可以繞過領域層,直接訪問基礎設施層。
Domain層(領域層)
用于封裝核心業務邏輯,并利用領域服務(Domain Service)和領域對象(Domain Entity)的方法對App層提供業務實體和業務邏輯計算。領域層是應用的核心,不依賴任何其他層次。
Infrastructure層(基礎設施層)
主要負責技術細節問題的處理,比如數據庫的CRUD、搜索引擎、文件系統、分布式服務的RPC等。此外,Infrastructure層還肩負著領域防腐的重任,外部依賴需要通過Gateway的轉義處理,才能被App層和Domain層使用。
Commpont(外掛Cola擴展組件)
非業務相關的功能組件包
對象類型概念統一
使用OO理論及COLA,首先就是要明確各類對象的概念以及所處位置,統一認知并且嚴格執行。

DO(Data Object):數據對象
DO應該跟其名字一樣,僅作為數據庫中表的1:1映射,不參與到業務邏輯操作中,僅僅負責Infrastructure層的數據持久化以及讀取的實體對象
Entity:實體對象
對應業務模型中的業務對象,字段及方法應該與業務語言抱持一致,原則上Entity和Do應該是包含嵌套的關系,且Entity的生命周期僅存在在系統的內存中,不需要序列化以及持久化,業務流程結束,Entity也應該跟隨消亡
DTO(Data Transfer Object): 數據傳輸對象
主要作為和系統外部交互的對象,DTO主要是數據傳輸的載體,期間可以承載一部分領域能力(業務相關的判定職責等);
VO(view Object):展示層對象
性質和DTO相差不多,也是數據傳輸載體,主要是作為對外交付數據的載體,承載部分數據隱藏、數據保護、數據結構化展示的作用。
代碼組織結構及層次間關系
使用作者推薦的maven Archetypes模板后,能發現COLA整體采用maven module的結構來構建代碼,劃分原則是結合技術維度和領域維度綜合劃分:

相應的分層則對應到不同的module中(技術維度劃分),每一層對應一個maven的module
<modules> <module>eden-demo-cola-adapter</module> <module>eden-demo-cola-app</module> <module>eden-demo-cola-domain</module> <module>eden-demo-cola-infrastructure</module> <module>start</module> </modules>

然后就是關于每個module的代碼目錄劃分原則(基于領域維度進行劃分),即每個module中對應的概念都劃分到具體某一功能領域包中:


而每個module(層)之間的依賴關系是這樣的:

Comm本身作為公共內容被所有module依賴,Adapter作為系統唯一外部輸出僅依賴APP,App本身除了依賴領域層外還因為CQRS相關內容依賴Infras;Domain層巧妙地使用依賴倒置讓Infras層反向依賴它,以保證了Domain的獨立和完整。整體是按照技術維度進行劃分的。

其中Domian層中的gateway僅僅定義interface,而在infrast層中進行gateway的Impl類,由此完成了巧妙地依賴倒置,解耦了Domain,讓Domain地module作為一個內聚的個體,聚焦核心業務開發,并且此處的設計,gateway作為與Infras層的防腐層,很靈活的將業務和數據進行隔開。
框架運行邏輯

場景1(包含業務訴求):
由客戶端發起請求,首先Adapter層接收請求,進行相關的校驗后進行數據封裝(Coverter操作:由請求參數封裝為App層所需要的DTO結構),根據預設的邏輯去調用App的內容;DTO數據進入App后, App根據業務訴求,然后調用Domain的ability和module進行業務組裝(Converter操作:將DTO轉換為Entity對象);被調用的Domian在進行業務處理過程中調用Infras層去進行相關持久化和查詢;Infras在被Domain調用時,需要把傳入的Entity轉換為內部對應數據的DO對象。最后逐層返回,相應的在Adapter層將DTO轉為VO

場景2(CQRS訴求):
由外部客戶端發起的不攜帶業務處理的查詢操作,例如:某某數據列表查看、某某內容統計,則由Adapter接收請求后,封裝參數對象后,調用相關App內容,App內部進行DTO和DO的相互coverter操作后,直接調用Infras層進行數據的查詢操作。
寫在最后
從上面的運行流程不難看出,COLA在代碼上有兩個特點
-
Domain層足夠獨立且直至業務核心,通過巧妙的依賴倒置(gateway的接口與實現切割至兩個module中),完成了與基礎數據持久層的耦合,變成了最基礎的被依賴者,內部分的model和ability這兩部分是平等的, model是針對業務場景進行抽象的業務對象Entity、ability是抽象的業務直接的操作,相當于上文充血模式例子中
AccountBO類和CreditPolicy、DebitPolicy的關系;優勢就是可以專注于復雜的業務開發,并且保證業務代碼的整潔性和可重構性。 -
制定了明確地規范,將部分非業務內容(數據轉換、校驗、適配等)均攤至不同的層次里,降低了原本貧血模式中Service持續積累過程中不可避免地臃腫;但是另一方面,為了實現面向對象與解耦,不可避免地追加了許多轉換操作,存在DTO和Conver操作代碼膨脹的風
COLA框架相較于傳統MVC(貧血模式)的三層結構要復雜一些,而復雜出來的內容(convertor、domainservice、domainablity等)的根本目的是在復雜的業務場景下,去踐行面向對象的設計和編碼,充分發揮面向對象的優勢(保證代碼的整潔、可維護等);但是同時也是要考慮一旦領域抽象不夠好時DTO對象和數據轉換代碼會冗余激增的風險。
所以考慮使用COLA之前先謹慎考慮一下自己項目的特點,涉及的業務是否足夠繁瑣復雜、團隊管理以及開發方式能否滿足OO土壤;因為絕大多數業務并沒有很復雜,都是基于數據的CRUD,且團隊整體從項目管理到需求分析、研發編碼 ,整個流程采用面向數據的開發方式去運作,這種情況下強行套用COLA未嘗不是一種災難,沒必要 “為了引入而引入”;畢竟完全使用OO思想開發是有技術成本的,至少對于團隊研發工程師的要求較高(必須精通OOA、OOD、OOP),保證每個功能和需求的設計到落地都是連貫且完整的面向對象設計。
但是COLA中很多設計思路和思想確實值得吸收,結合自身項目的實際情況,可以將COLA的一些優秀思想落地到項目代碼中,下面是針對COLA框架設計時依據的一些思想的提煉:
-
規范的代碼框架:井井有條的包和目錄分類以及統一的命名規范會像代碼地圖一樣形成無形的約束
-
核心業務邏輯和技術細節分離:區分業務部分和非業務部分,并進行分離,讓“上帝歸上帝,凱撒歸凱撒”
-
奧卡姆剃刀:讓事情簡單化,如無必須,勿增實體
-
合理使用領域對象模型設計domain和面向對象設計:讓代碼更加“面向對象”(個人比較喜歡,代碼滿足面向對象的一些設計原則),從而充分發揮面向對象的高擴展,易維護,實現高內聚、低耦合的究極目的。

浙公網安備 33010602011771號