1. 程式人生 > 其它 >細說業務邏輯(前篇)

細說業務邏輯(前篇)

前言

記得幾個月前,在一次北京部落格園俱樂部的活動上,最後一個環節是話題自由討論。就是提幾個話題,然後大家各自加入感興趣的話題小組,進行自由討論。當時金色海洋同學提出了一個話題——“什麼是業務邏輯”。當時我和大家討論ASP.NET MVC的相關話題去了,就沒能加入“業務邏輯”組的討論,比較遺憾。

其實,一段時間內,我腦子裡對“業務邏輯”的概念也是非常模糊的。但在不斷地閱讀、思考和實踐過程中,這個概念及其相關的內容才在我腦子裡漸漸清晰。我想,很多朋友也許也對這個概念不是很瞭解,所以願意結合既有資料和自己的思考,總結一篇關於業務邏輯的概述性文章,一則與朋友們分享探討,二則也是為自己對業務邏輯的學習做一個總結和提升。因為我還不敢說對業務邏輯內涵及外延理解的非常充分,所以文中如有不當之處,還請各位不用客氣,儘管批評就好!

內容提要

===================前篇=====================

前言

內容提要

1、我把業務邏輯丟了!——找回丟失的業務邏輯

2、細說業務邏輯

2.1、業務邏輯到底是什麼

2.2、業務邏輯的組成結構

2.2.1、領域實體(Domain Entity)

2.2.2、業務規則(Business Rules)

2.2.3、完整性約束(Validation)

2.2.4、業務流程及工作流(Business Processes and Workflows)

2.3、業務邏輯層職責相關爭議

2.3.1、爭議一:資料的格式化

2.3.2、爭議二:資料合法性及完整性驗證

2.3.3、爭議三:CRUD

2.3.4、爭議四:儲存過程

===================後篇=====================

3、業務邏輯的架構模式及實現

3.1、Transcaton Script

3.1.1、概述

3.1.2、分析

3.2、Table Module

3.2.1、概述

3.2.2、分析

3.3、Active Record

3.3.1、概述

3.3.2、分析

3.4、Domain Model

3.4.1、概述

3.4.2、分析

3.5、各種架構模式的比較及選擇

4、結束語

參考文獻

1、我把業務邏輯丟了!——找回丟失的業務邏輯

相信朋友們基本都是軟體開發人員。不論身處什麼職位,我們的工作都有一個共同的目標——製作軟體產品。而所謂的軟體產品,一定是在某個領域內去實現某些業務。如此看來,“業務邏輯”本應和“軟體產品”是緊緊綁在一起的,沒有業務邏輯,何來軟體產品?

但是,我發現一個奇怪的現象,一說業務邏輯,很多人就無法形成清晰地印象。例如,經典的三層架構:表示層、業務邏輯層和資料訪問層,一提到表示層或資料訪問層,大家腦子裡馬上能產生出清晰的概念,但一提到業務邏輯層,就有點模糊了,或者完全不知道其是什麼,或者有個模糊的輪廓,但對其具體的職責、結構不是很清楚。真是奇了怪了!我們天天和業務邏輯打交道,搞不清業務邏輯是什麼。

對於這個奇怪的現象,我思前想後,結合自身的教訓(我也曾很長時間搞不清業務邏輯),終於弄清楚了其原因——這和我們接觸這個概念的途徑和認知結構有莫大關係。

不知道有多少人和我一樣,首次接觸“業務邏輯”這個概念是從分層架構中的“業務邏輯層”概念開始的,我相信不在少數。事情壞就壞在這裡!為了讓朋友們直觀看清“業務邏輯”的概念是怎麼被我們丟掉的,請大家看一個圖,這個圖展示了很多人對“業務邏輯”的認知過程。

圖1-1、狹義的認知分解過程

如圖1-1所示,我們先接觸了分層架構,然後對每個層產生了初步的認識。其中,由於表示層和資料訪問層的程式碼職責清晰明確,基本能正確認識。但是,由於我們接觸的分層架構的Demo大多業務極其簡單,又基本是CRUD操作集中型的業務。所以,我們腦子中就產生了疑問:這個所謂的業務邏輯層是幹什麼的?怎麼就簡單封裝了一下資料訪問層的操作?這有存在的必要嗎?由於有了這種“先入為主”的誤導,使得很多朋友腦中將“業務邏輯”和“業務邏輯層”兩個概念混淆了,始終想不明白這東西到底是什麼,做什麼用的。再加上很多朋友所看的、所做的系統都是CRUD操作集中型的,就形成了“業務邏輯貌似就是對資料訪問操作的簡單封裝”這一片面概念。

到底這一概念有沒有錯呢?其實沒錯,因為在簡單的、CRUD操作集中型軟體中,業務邏輯基本就是對資料訪問簡單的封裝。但是,無錯不代表全面,這是一種狹義的業務邏輯理解,而且是狹義中的狹義。為什麼這麼說呢?因為我們不但是在“業務邏輯層”這麼一個狹義範圍內去理解業務邏輯,而且還是CRUD集中型操作這種“非常瘦”的業務邏輯層範圍內去理解,所以,可謂是在狹義的基礎上的狹義。

當我們把這麼一個“狹義中的狹義業務邏輯”與“業務邏輯”等同起來時,誤會、迷茫、困惑、不屑就出現了。這就如同,給你一隻溫順的哈巴狗,還是病怏怏的、無精打采的小哈巴狗,而你把這隻“病怏怏的小哈巴狗”與“狗”的概念等同起來了。那麼你一定就會為有人養狗看家和警察養狗當警犬抓壞人而困惑:這東西這麼弱小,我一腳就踩死了,怎麼弄用來看家和抓壞人呢?進而可能會產生“狗狗無用論”,“狗狗廢品”等觀念。當然,在現實中,很少有人只見過小哈巴狗而沒見過狼狗等其它狗類,所以,故事中的誤會對“狗”一般是不存在的。但在現實中,確實有很多人只見過業務邏輯中的“小哈巴狗”,卻沒有見過業務邏輯中的“狼狗”、“藏獒”,所以,這種誤會在對“業務邏輯”的理解上廣泛存在。

那麼,廣義的情況究竟是怎麼樣的?請看下圖。

圖1-2、廣義的認知分解過程

(注意!凡是不特別說明,下文中所有“資料”一詞都指需要持久化的資料,而不包括記憶體中的臨時資料。請各位留心。)

如圖1-2所示,廣義的認知分解應該是這樣的:軟體產品都是在某個領域內實現某些特定業務,所以,軟體產品天生應該分解為介面互動部分和業務邏輯部分,其中業務邏輯部分是軟體產品的核心,它客觀存在於軟體產品內部,但是無法對使用者產生直觀刺激,因此業務邏輯不能與使用者直接互動。而介面互動部分是業務邏輯與使用者進行交流的介面,使用者通過介面互動部分,與業務進行交流,從而使得軟體產品發揮其作用。

而在具體實現系統時,介面互動部分演化成表示層,業務邏輯部分演化成業務邏輯層。所以,可以認為,資料訪問層不是軟體產品自然演化的直接產物,之所以出現數據訪問層,是因為某些產品的業務屬於“資料操作集中型”業務,為了實現隔離、複用等目的,架構師從業務邏輯中分離出了頻繁使用的資料訪問業務,形成了單獨的資料訪問層。從廣義來說,可以認為資料訪問隸屬於業務邏輯,因為,資料訪問操作實際上也是業務邏輯的一部分。

總結一下幾個要點:(這幾個要中的業務邏輯均指廣義業務邏輯)

1)軟體產品自然的可分為介面互動部分和業務邏輯部分。

2)從空間結構上看,業務邏輯和資料訪問不是並列關係,而是隸屬關係——資料訪問隸屬於業務邏輯。雖然在具體系統實現層面,資料訪問層和業務邏輯層是並列存在,但從概念本質層面上分析,兩者是隸屬關係。

3)從時間結構上看,應該是先有業務邏輯的概念,才有資料訪問的概念。業務邏輯衍生自軟體本身,資料訪問衍生自業務邏輯。

4)因為業務邏輯是軟體產品自然的一部分,所以擁有業務邏輯是軟體產品的必要條件(讀者可以試著舉出一個不包含業務邏輯的軟體)。但是一個軟體可以沒有資料訪問,如“計算器”、“不帶存檔的小遊戲”等。

利用以上論述要點和認知分解,朋友們可以試試在腦中重新構築狹義和廣義“業務邏輯”的概念。看能不能把我們丟掉的業務邏輯概念找回來。關於業務邏輯更多的細節,將在下文中討論。

2、細說業務邏輯

2.1、業務邏輯到底是什麼

在第一大節裡說了那麼多,相信各位基本已經形成“業務邏輯”的概念了。如果我在這裡再囉嗦什麼,我不嫌累各位也要嫌煩了。所以,這裡我僅給出兩個定義。

廣義上的義務邏輯——軟體本身固有的一種品性,自然存在於軟體產品內部,是軟體具有的在某個業務領域內的邏輯,是軟體的核心和靈魂。軟體產品除介面和互動外的一切都可看作是廣義業務邏輯。

狹義上的業務邏輯——等同於分層架構中“業務邏輯層”的職責,是軟體中處理與業務相關任務的部分,一般狹義上的業務邏輯不包含資料持久化,而只關注領域內的相關業務。

對於以上兩種定義,希望朋友們不要割裂開來看,而 要辯證統一的去看,這樣,才能構建一個完整而辯證統一的“業務邏輯”概念。在下文中,將不再明確區分狹義和廣義,“業務邏輯”一詞將代表兩者的辯證統一體。

2.2、業務邏輯的組成結構

業務邏輯作為一個高層次概念,其內在結構也是非常豐富的,下面我們深入其裡,去探尋一下業務邏輯都是由哪些更底層的部分構成的。

2.2.1、領域實體(Domain Entity)

通俗的說,領域實體就是這個領域內有哪些東西。例如,銀行業領域內有賬戶、支票、前臺營業員等實體;B2C電子商務領域有商品、訂單、交易等實體;魔獸世界遊戲的領域內有角色、種族、道具、魔法等實體;高等代數領域有矩陣、行列式等實體。

領域實體是某個領域內各種物件的抽象,可以用名詞表示(可以是具體名詞或抽象名詞,甚至動名詞,只要其具有名詞性),構成了整個業務邏輯的骨骼和靜態模型。一般每個領域實體有自己的一些屬性和行為。順便說一句,領域實體的存在時OOA&D的基礎。

在具體的軟體系統中,領域實體往往會根據架構的不同有不同的對映存在形式。

其中一種叫做Business Object(BO),即業務物件,某些文獻稱其為“充血實體類”,這種物件完整抽象了領域內的某個實體,封裝了此實體相關屬性和行為。在面向物件的設計和架構中,這種實體類很常見。

另一種叫做Data Transfer Object(DTO),某些文獻稱其為“貧血實體類”,其特點是僅有屬性,不存在行為。這種實體類主要負責整體性傳遞資料。另外,與BO不同的是,DTO可以不抽象領域實體的全部屬性,而只根據需要抽象一部分。例如,某個“User”實體存在很多屬性,但如果某個方法僅需要其聯絡方式,可以設計一個DTO,僅有id,email,address,phone等就夠了。在面向過程的設計和架構中,這種實體設計比較常見。

2.2.2、業務規則(Business Rules)

業務規則就是某個領域內運作的規則,構成了整個業務邏輯的靈魂和動態模型。業務規則作用於領域實體,領域實體遵從業務規則進行運作。

如:在銀行領域內,“轉賬時從A賬戶扣除相應款項,在B賬戶新增相應款項,並從A賬戶扣除相應手續費,並通過某些途徑通知A和B賬戶的戶主”就是一條規則。需要注意的是,業務規則比較抽象,並不是需求,需求需要具體且無二義性,而業務規則只是抽象的一種描述,例如,通知戶主的途徑是什麼?電子郵件?電話?簡訊?並沒有具體描述,但在規則中有“通知”這一項,因此不能將業務規則等同於需求。

2.2.3、完整性約束(Validation)

領域實體和業務規則構建了業務邏輯的主體,但在這主體之上,還存在著一個限制,這就是完整性約束。

完整性約束是對業務領域中的資料、規則的強制性規定與約束。這種約束是系統正常運轉的保證。

如“賬戶密碼不能為空”,“身份證號必須符合具體格式規定”,“轉賬流程必須具有原子性,A賬戶扣錢、B賬戶存錢、A賬戶扣除手續費、通知戶主四項操作必須要麼都做,要麼都不做”,都是完整性約束。

2.2.4、業務流程及工作流(Business Processes and Workflows)

有了上述三項,業務邏輯還不能正常工作,因為還沒有“啟動器”和“過程託管器”。設想我們有了各種實體類,它們有各自的屬性和行為,也有定義好的業務規則和完整性約束。現在實體類僅僅具有實現業務規則的能力,但它們如何啟動並互動協調完成業務規則呢?因此我們需要有東西去觸發和協調實體。

業務流程或工作流是啟動及託管協調領域實體完成既定規則的過程。例如,“線上訂購”是一個業務流程,它包括“使用者登入-選擇商品-結算-下訂單-付款-確認收貨”這一系列流程。各個實體如會員、訂單、商品等已經包含了完成線上訂購必要的行為,但仍需一個流程,才能真正完成業務。

具體到程式中,業務流程也許通過一個方法來實現,這個方法負責啟動並協調各個實體類,完成一個流程。

2.3、業務邏輯層職責及相關爭議

2.3.1、資料的格式化

關於資料的格式化應該放在業務層進行還是表示層進行一直存在爭議。我個人的意見是這樣的:

業務層送給表示層的資料應該具備以下要求。1)返回的資料應該完成了所有必要的業務處理和業務計算。例如,若返回訂單資訊讓表示層展示,會有個必要的資料——訂單總額。這個資料需要首先用各個訂單項的單價乘以數量,然後加和。那麼,這個資料應該在業務層完成計算直接返回,總之不應讓表示層進行任何業務處理和計算操作。2)一次性返回所有需要的資料,避免表示層再一個Action裡呼叫多次業務。打個比方,例如訂單中有個“客戶姓名”,這個資料不儲存在訂單表中,而是通過外來鍵關聯的,那麼,業務層應該將“客戶姓名”一併取出返回給表示層。總之,避免表示層在一個Action裡多次呼叫業務層。3)不攜帶任何格式資訊,僅僅是結構良好的純淨資料,如DTO形式。因為,資料如何展示,是表示層的職責,如何在業務層中返回了過多格式資訊,就會造成表示層的修改困難。例如,我曾聽說過所裡承接的一個實際專案,開始是使用B/S,當時他們的業務層返回的資料全都附帶了html程式碼。後來,客戶嫌B/S響應不夠迅速(可能是客戶公司的網路條件不好),要求改成C/S,當時全傻眼了,貌似幾乎修改了整個業務層。那個專案相當龐大,7個子系統,投入200人開發了1年多,想想修改的難度吧。

2.3.2、資料合法性及完整性驗證

一般做系統,都避免不了資料驗證。上文曾經提到,完整性約束是業務邏輯的一部分。如此看來,資料驗證一般應該放在業務層。但是,實際情況並不盡然。個人認為資料驗證的方式,目前沒有統一標準,可以根據需要放在表示層或業務層。但是,我個人不提倡在“表示層的服務端”放置過多完整性驗證。因為,表示層的職責應該僅僅是接收資料並傳遞給業務層,不應對資料是否合法負責。過多的資料驗證,不但令表示層程式碼臃腫,而且使得表示層職責變得不明確。

可以在“表示層的服務端”放置一些簡單的驗證,如空值驗證,兩次輸入密碼是否一致等,但業務關係緊密的驗證,最好放在業務層。甚至有些驗證只能在業務層驗證,如“當前使用者名稱不能與已有使用者名稱重複”,這種驗證需要訪問持久化資料,需要由業務層完成。

這裡之所以強調“表示層的服務端”,是因為一般在B/S系統中,都會在JavaScript里加入一些基本的資料驗證,如空值檢查,格式正則匹配等。這主要是為了減輕伺服器負擔,將大多數顯然包含不合法資料的請求拒絕掉,而不發給服務端驗證。當然,因為可能會出現JS被遮蔽或黑客惡意攻擊行為,所以,所有驗證不論JS中是否驗證過,服務端(可能是表示層的服務端部分或業務層)一定要再進行驗證。

2.3.3、CRUD

CRUD,即常說的增刪改查操作。關於CRUD是否是業務層的職責,一直也是爭議不斷。因為目前並沒有權威的定義,所以這裡我斗膽說一下我對這個問題的看法。還請大家批判性閱讀。

一說到“增刪改查”,大家一定會覺得這理所當然是資料訪問層的職責。我認為這個理解是對的,但是隻對了一半!之所以這麼說,是因為“增刪改查”有兩個層次含義。

第一個層次,是資料訪問層次上的。在這個層次上,“增刪改查”只是單純的資料庫操作,“增刪改查”可以理解為“插入一條記錄,刪除一條記錄,更新一條記錄的資訊,獲取一條或多條記錄”四個操作,其意義和著眼點完全是資料訪問層面上的,不帶有任何業務成分和業務知覺。這個層面上的CRUD應該屬於資料訪問層的職責。

第二個層次,是業務邏輯層次上的。在這個層次上,“增刪改查”是業務領域內實體的變化以及一系列相關反應,“增刪改查”可以理解為“領域內新增一個業務實體,領域內去掉一個業務實體,領域內一個業務實體更新了資訊,得到領域內一個或多個業務實體的資訊”。

兩者最大的不同,是業務層面上的增刪改查往往不是單純的增加減少,還包括實體變化後相關的業務流程。下面舉個例子:

“新增一個新的訂單”——這是一條典型的“增”操作。在資料訪問層面上,它的意義是“在表示訂單的資料表裡增加一條記錄”;而在業務邏輯層面上,它的意義除了“領域內多了一個訂單實體”外,還可能包括“根據業務規則判斷是否是重複下單,根據金額對下訂單客戶的等級做相應提升、傳送Email和簡訊通知客戶等”。可以看到,業務層面上的“增”可能不僅是簡單封裝一個簡單的插入記錄,可能還要去做其他資料訪問——提升使用者等級,以及做一些非CRUD的業務操作——傳送簡訊通知。

在許多稍微複雜的系統中,業務往往不僅僅是封裝了一條資料訪問操作,而是還有很多如計算等業務處理,一個業務操作期間可能要多次使用資料訪問操作。退一步說,即使某個業務僅僅封裝了一條資料訪問操作,其意義和層面也是不同的,在資料訪問層面,僅僅是多了一條記錄,而業務邏輯層面,是領域內多了一個業務實體。也許其本質上都是往資料庫插入一條記錄,但人類的抽象思維可以將之在不同層面上區分,這也是人類思維層面的一種抽象能力的表現。例如,我們知道太陽升起不過是地球自轉使得從背陰面轉到了向陽面,但當人們看日出時,很少有人會說“看!我們從背陰面轉到向陽面了!”,我們會說“看!日出!”,這就是同一事物的不同層次表現。

2.3.4、儲存過程

也許是效能上的誘惑,許多人喜歡在資料庫系統中寫很複雜的儲存過程。這樣,許多業務操作就被寫到儲存過程中去了。我個人建議,除非對效能要求極高,否則最好還是不要用儲存過程實現業務。例如,在一般的系統中,某個業務操作可能需要1秒,而是用了儲存過程只用0.1秒,看上去儲存過程將效率提高了10倍。但對大多數使用者來說,1秒和0.1秒的差別並不大,但是這樣做的話,業務會變得十分不容易維護。所以,我個人覺得,除非十分必要,還是不要用儲存過程實現業務。

本文基於署名-非商業性使用 3.0許可協議釋出,歡迎轉載,演繹,但是必須保留本文的署名張洋(包含連結),且不得用於商業目的。如您有任何疑問或者授權方面的協商,請與我聯絡