







摘要:在企業內部網站的建設過程中,網站后端最初采用傳統的表模式的開發方式。這種方式極易導致站點的核心業務邏輯和業務規則分布在架構的各個層和對象中,這使得系統業務邏輯的復用性不高。為了解決這個問題,作者在后期的開發過程中引入了領域驅動設計的開發方式,把系統的業務邏輯獨立建模、充分地復用,并基于這些模型打造易于擴展的開發框架,提高了整個團隊開發業務邏輯的效率,最終網站如期上線,穩定運行至今。
關鍵詞:表模式;貧血模型;領域驅動設計;領域模型
中圖分類號:TP311? ? ? 文獻標識碼:A
文章編號:1009-3044(2022)10-0044-04
數字化是每個IT企業系統建設必不可少的一個環節,而企業內部網站往往也是數字化的重要標志和組成部分。企業內部站點建設項目就是在這種場景下誕生的,主要就是為順利開展研發部門的日常工作,提供所需的管理各項流程制度的功能。
在項目的開始階段,作者在研發方式上選擇了常見的表模式來開發網站后端,在架構上選用了傳統的多層架構來組織代碼。這種技術選型和搭配常用于開發業務邏輯比較簡單的小型項目,整個開發的過程都聚焦在具體的數據庫設計和面向流程的開發上,對于團隊成員來說簡單易于上手。
不過,當網站的功能逐漸增多以后,系統的復雜度迅速攀升,采用這種搭配使得系統的業務邏輯得不到聚焦,最終它們零散地分布在架構的各個層和對象中,業務邏輯的復用性很差。
為了解決這個問題,作者引入了領域驅動設計的開發方式。這種開發方式聚焦于多變的業務邏輯,把它們作為系統開發的核心模型管理起來,這樣就使得系統的業務邏輯得到了充分的復用,整個團隊開發的效率就得到了顯著的提高。
1 軟件開發的三種模式
在軟件開發中,根據研發過程的不同,一般把開發流程分為事務腳本、表模式和領域驅動設計三種模式。
1.1 事務腳本
1) 模式介紹
時至今日,雖然主流編程語言都是面向對象語言,但是很多團隊都是采用面向過程的開發模式,代碼中使用的對象并沒有實際的意義,純粹只是代碼腳本的載體。
事務腳本(Transaction Script) 就是這樣一種簡單的開發模式,它完全采用的是面向過程的做法,直接將用戶在表現層上所做的操作翻譯成代碼腳本(比如SQL語句、批處理語句等) 執行的模式[1]。
2) 實現方式
通常,實現事務腳本模式不會進行對象設計,也不會對涉及的組件進行分層,所有從表現層到底層的操作全部放到一起。
典型的例子就比如展示圖書列表的頁面,開發的時候通常就是在界面上放置一個列表控件,然后該控件直接綁定從數據庫中獲取圖書的SQL語句進行數據填充。增加和修改圖書的時候也是類似,直接提供SQL語句操作數據庫。
當然,為了使整個程序的結構更加清晰,在使用事務腳本模式時,很多團隊也會進行簡單的分層,這就是基本的多層架構的由來,也就是把應用分為如圖1所示的三層。
在分層架構下,應用把整個業務流程分割并組織在不同的層級中,與用戶直接交互的組件放在表現層中,用戶在表現層的操作會被翻譯成系統的輸入數據模型并進入業務邏輯層,業務邏輯層再調用底層的數據訪問層的腳本操作數據庫、文件系統等數據源,完成業務邏輯。
在這個過程中,系統設計的重點依然是面向過程的,各個層和對象只是過程的載體,具體叫什么其實并沒有什么太大的區別。
3) 應用場景
事務腳本是一種常用的開發模式,針對那些交互邏輯簡單,業務模式幾乎不會改變和發展的簡單場景,比如常見的“增刪改查型”應用,整個開發過程的效率會非常高。
1.2 表模式
1) 模式介紹
在研發過程中,數據庫作為最重要的數據載體,在任何一個項目的開發過程中幾乎是必然會出現的。在事務腳本中,與數據庫交互基本都是通過在代碼中直接使用SQL語句來完成的。
在常見的編程語言中,SQL語句都是作為字符串存在的,這就帶來一個問題,由于字符串的內容不參與編譯,所以如果SQL語句存在語法問題,那就只能到了應用運行時產生錯誤才會被發現,編譯時發現不了,這有時會帶來潛在的發布風險。
為了優化操作數據庫的體驗,提高代碼編寫的效率,在數據訪問層,經過工程師們不斷地嘗試和努力,就出現了一種全新的數據交互方式,這就是通過編程語言中的實體對象來操作數據庫中的數據。
根據實體影響數據庫中數據的具體實現不同,以最為核心的表操作為例,如果一個實體對象能執行數據庫表中多條數據的相關操作,那么這種模式就稱為“表模式(Table Module) ”,如果一個實體對象只執行表中一條數據的相關操作,那么這種模式就稱為“活動記錄(Active Record) ”。這兩種模式本質上并沒有太大的區別。
2) 實現方式
以表模式為例,從具體實踐上來說,它們是在事務腳本的基礎上,把腳本操作變成了對象的操作。這個過程一般是通過特定的框架來完成的,這種框架一般被稱為“對象關系映射(Object Relational Mapping,以下簡稱ORM) ”。
ORM框架把關系數據庫中的結構映射成語言中的實體對象,當調用實體的方法的時候,ORM框架就會將對象操作翻譯成SQL語句,然后作用于數據庫,如圖2所示。
從具體實踐上來說,該模式在事務腳本的基礎上,把腳本操作變成了對象的操作。由于這些對象只是數據庫中表的映射,只具有保存數據的字段,而沒有任何實際的業務操作,所以,這些實體對象一般也被稱為“貧血模型”。
3) 應用場景
表模式優化了數據庫訪問的體驗,但是并不涉及任何業務邏輯的處理,所以表模式和事務腳本一樣,都非常適合那些業務邏輯簡單的場景,對于較為復雜的應用場景則不太合適。
1.3 領域驅動設計模式
1) 模式介紹
事務腳本和表模式兩種開發模式,主要采用的是面向過程的編程方法,而且在應用開發的過程中,需求分析和系統設計大都是分離的,這樣就把應用開發前期的工作割裂開來,這也就導致了需求分析的結果與系統設計的代碼不能完全匹配,以至于軟件上線后,客戶才發現許多功能不是自己想要的。
不同于這兩種模式,領域驅動設計(Domain Driven Design,以下簡稱DDD) 開發模式是純粹的面向對象的設計過程,它以業務領域為核心,分析領域中的問題,通過設計和建立對應的“領域模型(對象) ”來有效的解決領域中的核心問題[2]。
在上述的過程中,通過“領域分析”到“領域建模”,DDD統一了需求分析和設計編碼兩個階段,這使得軟件能夠靈活快速的跟隨需求的變化而變化。
在編程實踐上,DDD豐富了實體類的行為,把業務邏輯也封裝到了實體類中,實體類成為承載系統核心業務邏輯和規則的載體,實體類是整個應用設計的核心。
要復用業務邏輯,就是要復用這些實體類,為了更好地做到這一點,在DDD中,就把實體類相關的模型都聚攏到一起,放到領域模型層中。
2) 實現方式
DDD是一種設計思路,為了實現聚焦業務邏輯,構建領域模型的目的,具體實施時一般分為戰略設計和戰術設計兩個階段。
戰略設計階段主要完成的工作是識別應用的領域,將領域細化得到子域,為每個子域設計限界上下文,并在這個過程中,通過與業務專家的充分討論,得到描述業務的統一語言。
戰術設計階段完成的工作是根據限界上下文和統一語言,設計領域層中的領域模型對象,具體包括實體、值對象、領域事件、倉儲、聚合和領域服務等(具體概念和細節可以參見文獻[3]) 。
DDD的具體實現也存在多種形式,本次網站的實踐采用的就是經典的DDD的分層架構,整體架構圖如圖3所示。
表現層與以往的架構相同,主要是與用戶交互的界面元素。
應用服務層是薄薄的一層,主要是把用戶的輸入轉換成系統需要的數據模型和把系統生成的數據模型轉化成表現層需要的對象模型,需要注意的是應用服務層主要完成轉換和交接工作,調用基礎設施完成一些諸如持久化的工作,千萬不能實現任何的業務邏輯。
領域層是整個系統的核心層,它包含所有的領域模型。這些領域模型承載和實現了所有的業務邏輯和業務規則,所有的其他層都依賴于領域層,但是領域層不會依賴于任何其他層。
基礎設施層完成了的一些輔助的功能,比如一些第三方類庫、具體的持久化實現如ORM等。
當按照DDD走完代碼設計的過程以后,領域模型就封裝了所有的業務邏輯,這樣復用領域模型就可以復用整個業務邏輯,同時也能保證這部分復雜多變的邏輯被單獨地隔離起來,不會對系統其他部分存在影響。
3) 應用場景
DDD開發模式特別適用于那些業務場景比較復雜、業務邏輯變化比較頻繁、系統復雜度比較高的場景,典型的就比如中臺系統的搭建[4]、微服務的開發[5]。
2 開發模式的應用
2.1 初期采用表模式開發模式
在最初的開發過程中,作者帶領團隊采用表模式開發系統,也就是每個業務功能的開發都遵循如下的流程:
1) 根據業務的需求分析得到數據表的字段,把數據庫表建好。
2) 通過ORM將數據庫中的表映射成代碼中的實體對象。
3) 在業務邏輯層根據功能的需要補充業務邏輯和規則,操作實體對象進行數據的讀取和持久化。
這種開發方式簡單易行,但是隨著網站的功能逐漸增多,邏輯越發地復雜,如下問題逐漸顯現出來:
1) 通過ORM得到的實體對象都是貧血模型,它們只有數據庫映射的屬性和字段,并不具有具體的業務行為。
2) 因為沒有明確的約束,核心業務邏輯和業務規則非常容易從業務邏輯層擴散到其他層和對象中。有的跑到了幫助類中、有的跑到了存儲過程里,這導致了業務邏輯的復用性不高。
為了解決上述問題,作者再次深入研究了上述的軟件開發模式并決定引入DDD來開發和管理項目。
2.2 后期采用DDD開發模式來聚焦業務邏輯
使用DDD開發模式來開發和管理項目,作者帶領團隊實踐了如下的過程。
1) DDD戰略設計
①將整個應用程序域進行劃分,剝離各個子域
在該項目中,經過分析,作者共拆分出賬號管理、任務管理、積分管理、圖書管理等子域。
②對每個子域進行限界上下文的設計
在每個限界上下文中,保證任何一個對象模型的語義是確定的、無歧義的。
比如在任務管理子域,定義一個User對象,作為任務創建人和執行人存在,它就只具有與之相關的屬性和行為。而在賬號管理子域中,又定義了另一個User對象,它卻具有常見的用戶名、密碼、郵箱等屬性和對應的行為。
雖然兩個User對象具有相同的名字,但是它們是完整的用戶對象在不同上下文中的投影,是兩個不同的對象。
在項目實施的過程中,作者選擇將子域和限界上下文一一對應,獨立部署在每個AppService中。
2) DDD戰術設計
DDD戰術設計的核心就是把核心業務邏輯集中放到一起,組成領域層。其他層設計為依賴于該層。領域層中包含所有重要的領域模型,包括聚合根、實體、值對象、領域事件、倉儲、領域服務等。
①設計聚合根、實體和領域服務
在該項目的領域層中,最重要的對象就是聚合根和實體。它們完成了主要的業務邏輯。
使用聚合根還是實體,在實踐中一般都可以,但是如果完成一個業務操作,就一個主要的實體就可以了,不需要其他相關實體的配合,那么暴露這個實體給其他層就可以了。
比如Book這個實體,不僅包含了所有圖書的所有信息,還包括增加封面、修改作者、增加點評等各種業務行為。
但是如果完成一個業務操作,需要一個主要的實體,配合上其他一些相關的實體才能完成,那么就將主要的實體暴露成聚合根給其他層使用,把相關的其他實體全部隱藏起來,這些相關的實體的操作統統通過聚合根來暴露。
比如Task這個實體,不僅包含了所有Task自身的信息和操作,還包括其包含的子對象TaskItem實體的信息和各種操作,所以Task實體就暴露為聚合根,向外提供所有Task和TaskItem的相關操作,其他層無法直接操作TaskItem對象以及其方法。如圖6所示。
除此以外,還有一種情況就是,完成一個業務邏輯需要多個主要的實體配合,這個時候,就可以設計一個領域服務來實現這個業務邏輯,在這個領域服務中,它會使用其他相關的聚合根或實體來完成功能。
不過在該項目中,一般使用聚合的概念、暴露聚合根就可以實現需求了,需要使用領域服務的情況比較少,所以沒有用到。
②設計領域事件
該項目中,非常常見的一個需求就是一個實體改變了,需要通知其他的一些實體就行一些對應的變化。這種需求就需要通過事件模式來實現。
在項目中,作者實現了一個EventBus作為通用的領域事件模型,任何實體都可以掛接這個對象,發布消息,或關注感興趣的消息并做出響應,這個部分與通用的事件模型并沒有太大的不同,示意圖如圖7所示。
③設計倉儲
使用倉儲模式可以很好地隔離數據庫的操作和領域對象。
在領域層,作者定義了各個倉儲接口,倉儲與聚合根或實體一一對應,包含對應的增刪改查等操作,它們是數據庫操作的接口。
需要注意的是,在領域層中,只是定義了接口,并沒有具體的持久化的實現。具體的持久化工作放在了基礎設施層中。
3) 其他層的一些細節
①基礎設施層
這里重點說明一下的是數據庫持久化的選型。
針對領域層提供的倉儲接口,作者實現時選擇了使用Entity Framework Core(以下簡稱EF Core) 作為基礎框架。使用此類ORM框架大大簡化了數據庫的操作,使用對象而不是SQL語句的方式可以避免很多語法上的錯誤,有效地提升了代碼的健壯性。
而且使用了EF Core的Code First的方式后,項目只要使用EF Core的包命令就可以非常簡單地根據實體對象的變更來創建和維護數據庫。
②應用服務層
在該項目中,應用服務層比較簡單,它實現了一個公用的AppService,封裝了公用的分頁的邏輯。其他各項服務比如BookAppService都繼承自該基類,實現了自己的邏輯。
在各個服務中,為了調用領域層的業務邏輯,首先需要把前端傳過來的輸入數據傳輸對象(Data Transfer Object,以下簡稱DTO) 轉換成領域層的數據模型,然后調用領域層的領域模型和基礎設施層的對象完成功能,最后再將結果轉換成輸出DTO返回給前端。
③后端接口層和前端表現層
接口層使用Restful Api去提供服務,前端表現層使用Vue 3.0實現展示,不管是否使用DDD,這些部分都是一樣的,不去多解釋。
經過上述的分析、設計與重構,最終得到如下新的架構,如圖8所示。
3 結束語
采用了DDD來開發和管理項目以后,項目的業務邏輯沉淀到了領域層的領域對象中,領域對象變成了包含業務邏輯的充血模型,這樣業務邏輯得到了復用,程序的擴展性得到了保證。
此外,由于存在清晰明確的分層和職責,團隊日常的開發效率得到了顯著的提升。目前該站點已經上線并穩定地運行至今。
參考文獻:
[1] Fowler M.企業應用架構模式[M].北京:人民郵電出版社,2009.
[2] Evans E.領域驅動設計[M].北京:人民郵電出版社,2010.
[3] Vaughn V.實現領域驅動設計[M].滕云,譯.北京:電子工業出版社,2014.
[4] 歐創新,鄧頔.中臺架構與實現:基于DDD和微服務[M].北京:機械工業出版社,2020.
[5] Richardson C.微服務架構設計模式[M].喻勇,譯.北京:機械工業出版社,2019.
【通聯編輯:謝媛媛】
收稿日期:2022-01-25
作者簡介:董向陽(1982—) ,男,江蘇灌云縣人,中級工程師(上海) ,碩士,研究方向為計算機應用技術。