1.CodeArt是什麼? CodeArt(簡稱CA)是一套完整的創新式企業級開發框架。它將整個業務應用劃分為四個層次結構:表現層、應用層、領域模型層和基礎設施層。針對這4個層次CA提供了多項特性以滿足開發人員的需要,它的特點之一是可以幫助開發人員徹底擺脫以資料庫設計為中心的項目實施方式,令程式員不 ...
前言:
目前絕大多數公司依然採用的是傳統的項目實施方式,即:圍繞資料庫做開發。我在2005年的時候就隱約感覺這種模式是錯誤的,關係型資料庫的優勢是處理數據的存儲而非解決複雜的業務需求。以資料庫作為核心開發項目必然會導致項目的失敗。所以在往後的10多年裡不論多麼艱難我都堅持實踐領域驅動設計,積累了豐富的實戰經驗。同時,為了降低領域驅動實施的門檻,我一手打造了企業級開發框架CodeArt,現在發佈出來提供大家免費使用,希望能夠幫助各位改善項目實施的過程。
1.CodeArt是什麼?
CodeArt(簡稱CA)是一套完整的創新式企業級開發框架。它將整個業務應用劃分為四個層次結構:表現層、應用層、領域模型層和基礎設施層。針對這4個層次CA提供了多項特性以滿足開發人員的需要,它的特點之一是可以幫助開發人員徹底擺脫以資料庫設計為中心的項目實施方式,令程式員不再忙碌於數據的增刪改查等枯燥無味的低價值工作,轉而專註於系統領域的設計。具體而言,使用CA開發應用程式具備如下特點:
1) 零風險。對,你沒有看錯,CA可以保證項目始終處在零風險的實施狀態。眾所周知,軟體項目隨著需求規模的增加,複雜性會成指數級增長。各種錯綜複雜的業務關係、少量或頻繁的需求變更都會帶來開發成本的大幅度提升。類似的經歷相信大家都經歷過,很多項目在初期開發都很順利,但是隨著完成的功能越來越多,系統暴露的問題也逐漸加劇,開發團隊需要不斷的修補,可是越是修正它們,它們就會變得越糟糕,最終導致系統徹底癱瘓。然而這一切在CA的開發模式下是不存在的,我們把一個普通程式員能在不犯錯的情況下良好完成的需求規模衡量為1,那麼無論你項目規模是大還是小,CA始終可以化整為1,令程式員們面對的的需求規模僅僅是最基本的1。
2) 與常規開發模式相比,CA可以提升5至10倍以上的綜合開發效率。這裡的綜合開發效率是指開發新功能和維護、變更已完成功能的效率總和。一方面,CA提供了許多創新型模塊來大幅度降低開發過程中遇到的各種問題,這包括不需要寫任何JS的前端表現層框架、靈活百變的數據遷移對象DTO、實現了No SQL的領域模型層框架等組件。另外一方面,這些組件也會令你在項目實施中對現有功能的簡單或複雜的改動都不會導致有依賴關係的模塊的連鎖改變。在修改或者新增應用程式功能的時候,需要改動的模塊非常少,不會導致程式其他地方出現問題。
3) 100%重用性。使用CA開發項目重用度的目標只有一個,那就是100%。在常規開發中,重用這項特性很容易理解但是卻很難實現,我們在許多項目中經常看到的情況是整個系統沒有一個業務模塊是可以重用的。類似資料庫操作、緩存機制等技術模塊的重用很容易辦到,但是技術模塊上的重用控制不了業務的複雜性,也無法降低開發成本。而在CA的開發模式下,我們會利用其提供的各項特性不僅將系統多維度切割成若幹可以獨立開發的最小單元,更重要的是這些單元可以無縫的協同工作,甚至獨立分離出來提供其他項目使用。CA完美的實現了業務級別的重用,被重用的單元可以在不更改、不修改、不增加原有代碼情況下,以擴展或繼承的方式二次使用。
4) 為程式員增值。CA完美的實踐了領域驅動的開發思想,極大降低了領域驅動在項目中實施的門檻。確切的講,CA對現有領域驅動設計進行了細化和補全,同時提供了各項特性和決策判斷的思路以便開發者能輕鬆的實施領域設計。因此,程式員的工作內容不再是圍繞資料庫做永無止境的增刪改查操作,而是沉醉於領域對象該如何設計、子系統該如何切分、針對需求的變化該如何重構代碼等富有創造力的工作,令每一位程式員不再是碼農而是領域設計師,用創造力而非蠻力去處理項目中遇到的各類問題。
除了以上特點之外,CA自身是一個永久免費、開源並且終生維護、永不斷更的企業級框架。目前CA提供了.Net Framework版,在近期我們還會完成.Net Core和Java版的開發工作,讓更多的程式員能享受到CA帶來的便利。
2.CodeArt的核心思想
在正式使用CA之前,讓我們把註意力放在一個淺顯卻又深奧的話題上:軟體開發的目標是什麼?這個問題似乎很容易找到答案——滿足用戶的需求。可是如果這個目標是對的,那為什麼我們在傾聽用戶的聲音之後,按照他們意圖開發出來的成果卻常常又被他們以各種理由修改甚至推翻呢?這也許是用戶犯的小錯誤,誰叫用戶是上帝呢?所以我們每天埋頭苦幹以確保他們新的想法能落實在項目里,縱使這些想法依然會改變、縱使現有的程式不得不大量修改、縱使我們背負碼農之名也義無反顧、勇往直前,這正是程式員價值的體現,對吧?
錯。編寫程式是一項極富創造力的工作,造成困境的根本原因在於我們的用戶並不是他們所在領域里的專家。或者說,他們比我們瞭解的更多的僅僅是錶面需求。滿足眼前的錶面需求其實很容易。但是要預測他們的都還不知道的本質需求呢?隨著項目的推進,用戶可以看到的功能越多,他們就越會發覺到更多的貼近本質需求的錶面需求讓你去滿足,如果你始終跟隨用戶的指揮棒去行動,那麼你的項目必定會陷入無止境的實現失敗,最終會由於開發成本遠高於預算而宣告失敗!
因此,我們編寫程式的目標是正確的挖掘現實事物在特定領域里的本質特征。這些本質的特征決定了用戶需求的導向。也就是說,你編寫的程式越貼近用戶所在領域里的本質特征,那麼你就越能滿足用戶已知或未知的各種需求,不論是現在還是未來的變化都盡在你的掌握之中!
以領域洞見作為項目開發的基礎是CA秉承的一條重要原則。所謂領域洞見並非要求你在當前這個時間點上能預測到未來的變化而做出滿足未來需求的程式設計,這聽起來很美好但絕非人力所能及。恰恰相反,我們要做的不是虛無縹緲的過度設計,而是根據眼前已知的錶面需求,在遵循一系列的設計原則下去構建領域模型。確保這些模型是健壯的、是容易更改的、是能滿足當前需求的即可。當一旦需要修改現有模型時,我們能輕鬆、靈活的去更改現有的代碼,甚至能夠做到在不破壞原有模型的基礎上做出擴展式的補充即可滿足新的需求。以迭代式的良性變化擁抱需求的劇變才是領域洞見真正的含義。
所以,在CA的四層架構里,領域模型層是重中之重,它是整個軟體項目的基石、是保證項目穩健實施的根本。軟體的界面可以簡陋、數據存儲可以低效,但是你的領域模型一定不能亂。簡陋的界面我們可以隨時去美化而無須觸碰業務代碼。隨著數據量的增多,數據存儲的性能需要提高,我們也可以使用建立索引、分表、分區、甚至分散式部署等各種成熟的技術去優化,你依然無須擔心業務代碼是否受到牽連。因為在CA的開發模式里,優化資料庫不會影響到業務代碼的更改,業務的處理全部由領域模型層負責,資料庫所處的級別在基礎設施層的數據倉儲里,與業務代碼沒有任何交集。只要你的領域模型足夠健壯,你完全不必擔心系統的安全性、伸縮性、擴展性等常規指標,我們可以輕鬆的駕馭這一切。即便這些指標在眼前由於時間、成本等原因我們無法做到較高的度量值,但是由於領域模型為我們打下了堅實的基礎,就算在項目後期甚至正式發佈後再回頭來逐一優化這些指標也不會帶來任何問題。
然而,設計一個良好的領域模型並非想象中的那麼簡單,因為探索事物本質的代價是巨大的。物理學發展上百年才得到若幹個貼近物理現象的計算公式,而我們要在以月為單位計算的項目開發周期里摸索出符合事物在某類領域里的本質特征也是困難的。為此,CA提供了大量構建領域模型的基礎設施,大幅度降低領域設計的門檻。這包括一系列的設計原則和多種基礎類庫,只要你遵循CA的標準在項目中堅持實踐,依然可以踏入軟體藝術家的領域,使用創造力贏得項目的成功,這也是CodeArt名稱的由來。
在後面的教程里,我們以會議系統作為項目案例,模擬真實的實施情景由淺入深的揭露CA在領域模型中的設計原則和基礎類庫使用的方法。除了項目本身的實施過程是真實的以外,該案例里涉及到的企業、個人等信息均為虛構,請勿探究。
3.原始需求分析
作為第一個示常式序,我們不會涉及過多的高級話題,僅以最基本的對象保存為切入點介紹CA開發項目是如何工作的。在這個例子里我們也不會展示CA表現層的技術,雖然優秀的表現層可以帶來豐富的互動體驗和節省大量開發時間,但是由於篇幅原因我們僅將最重要的領域模型層以及相關的技術作為重點介紹,在後續例子中再逐一講解其他架構層次里的技術細節。
先介紹下會議系統的項目背景:我們的客戶是一家員工數量多達上千人的中型金融公司,他們主要從事於投資理財、保險交易等業務。由於客戶員工人數眾多並且分散到全國各地,所以他們想定製一套電子化系統來滿足異地開會的需要。
通過與客戶的溝通,我們瞭解到一些原始需求,這包括:
1) 與會人需要簽到會議以便系統識別是否有人遲到、缺席會議,這有點類似考勤的功能。
2) 儘量能全面的記錄會議的過程,這包括以文本形式記錄的會議摘要。至於是否以視頻形式錄製整個會議過程客戶表示還有待商討。
3) 可以在開會之前擬定會議的議程,指定會議的參與人。
4) 可以管理會議的資料,在開會時可以查閱、上傳各種資料。
5) 在會議進行時,與會人可以共用自己的桌面。
6) 其他需求由於篇幅原因不過多的介紹,在後面的示例里再列舉。
以上是客戶對我們提出的原始需求,所謂原始需求是指未加任何修飾,純粹由客戶提出的想法,這些想法可能會雜亂無章,甚至與使用系統的順序背道而馳(例如上述第3點應該是召開會議的第一步)。因此原始需求一般不能直接用於項目實施中,我們需要通過敏捷流程進行用戶角色建模並且搜集用戶故事。只有用戶故事才是我們真正需要完成的工作。這裡附帶提一點,雖然敏捷開發不是使用CA必須的的工作方式,但是我們強烈建議使用敏捷開發的流程配合CA編碼來實施項目。這樣無論是代碼質量還是團隊的管理都會更加優秀。關於敏捷開發的細節超出本教程的內容,不在這裡展開過多的討論,以後我會單獨開教程詳細介紹敏捷開發的實施過程。
經過一輪頭腦風暴,我們發現客戶雖然對會議相關的功能很感興趣,但是項目本身還需要基礎設施的支持,例如:用戶需要登錄才能使用系統功能、不同身份的用戶登錄系統後所能使用的功能也不盡相同。在這裡我們依然假設整個開發團隊在此之前沒有任何的編碼積累,一切都需要重新開發。
開發團隊敏銳的發覺到整個項目需要一個許可權方面的管理機制,用於系統針對不同身份的用戶提供不同的功能性服務。但是客戶並沒有在這方面給予我們過多的說明,他們只是隱約覺得會議主持人和參加會議的與會人在登錄系統後能操作的會議信息是不同的,例如會議的創建者可以更改會議的召開時間,但是與會人僅能查閱需要參加的會議的基本信息。
顯然,許可權機制具體如何運用到項目中對於整個開發團隊而言還是迷霧重重。但是我們依然可以根據手上掌握的有限資料列舉出應用許可權機制的兩個實際場景:
1) 不同身份的用戶登錄系統後看到的菜單不同。菜單是使用功能的入口,菜單不同就意味著用戶使用的功能不同。
2) 在同一個功能界面里,我們可以檢測用戶的身份以便UI隱藏或者顯示某些按鈕,例如修改和刪除按鈕只有會議主持人可以看到,普通與會人是看不到的。
在需求不明朗的時候,我們可以通過分析UI操作來加深對需求的理解。這並不是說我們開發出來的功能僅僅是為了滿足UI操作,而是通過UI操作的過程來輔助我們分析需求里會涉及到的事物,在這個階段我們不急著找尋事物的本質特征,而僅僅只是找到事物本身。這些事物是我們接下來討論的重點。
“用戶”和“菜單”是以上兩個場景里最為明顯的事物。會議主持人和與會人都是用戶在會議這個領域里的具體體現。既然如此,那麼我們可以很自然的想到,通過會議系統客戶至少可以創建用戶、修改用戶的信息(姓名、性別、聯繫方式等)、刪除無效的用戶、邀請用戶參與某場會議等。我們不必深入探究還有哪些針對用戶的操作,因為這對分析許可權方面的需求沒有幫助,我們僅需知道用戶是一個關註點即可。
再來討論菜單。對於菜單我們第一反應就是通過UI技術(winform、wpf、html等)將其硬編碼到界面上,但是硬編碼的菜單肯定無法滿足“根據用戶身份決定菜單是否顯示”的需求了,所以我們對於菜單這一事物要重新思考。
“如果菜單是可以人為創建的,那也許就可以通過某些設置讓菜單識別用戶的身份?”這是我們腦海裡浮現的一個思路。圍繞這個思路我們再考慮後續問題:對菜單做什麼樣的設置,可以讓系統根據用戶的身份決定該菜單是否能被顯示到界面里?
為菜單設置1個或者多個用戶身份,這樣當用戶登錄時,系統檢測到用戶有哪些身份,只要有其中1個身份匹配菜單里已設置好的用戶身份,那麼菜單就可以被顯示。這是一個不錯的主意,再判定這個決策是否完美之前,我們發現隨著深入的分析,“用戶身份”這個名詞多次出現在我們的視野里。因此,用戶身份也許是我們需要關註的新事物。為了便於描述,我們統一語言將用戶身份改為角色。那麼也就是說,我們可以為菜單設置多個角色,只有用戶屬於這類角色,菜單才能顯示。在這裡我們多花點筆墨說明下角色和用戶的區別:一般而言,用戶是具有姓名、性別、年齡等基本的人的特征。那麼角色呢?角色表示的用戶是乾什麼的,用戶是領導?是經理?是員工?還是客戶?所以角色是用來描述用戶身份的,角色不必理會用戶叫什麼名字,而且同一個角色可以被用於多個用戶,比如,員工這個角色的用戶就可以有多個。因此角色和用戶的關註點是不一樣的,是兩個不一樣的領域模型,不能混為一談。
請大家註意,我們在分析原始需求的同時也正在潛移默化的為系統的設計做出各種決策。每當你有了新的決策就一定要分兩邊考慮:這樣做有什麼好處?這樣做會帶來什麼壞處?一個決策的誕生都是為瞭解決某一個特定的問題,但是往往也會帶來副作用。如果副作用過大我們就需要修改甚至推翻之前做的決策。只有利遠大於弊的決策我們才會保留。
以”為菜單設置多個角色”這一決策為例分析好處與壞處。它的好處很明顯,可以讓系統根據登錄人的角色來匹配菜單里的角色,以此決定是否顯示菜單。那麼它的壞處呢?首先,角色是什麼?角色是用戶的身份,我們為菜單設置多個用戶的身份感覺有點怪怪的,當然,這並不是什麼很大的毛病,只是覺得有點奇怪而已,就好像你家養的小狗擁有一張人類的身份證一樣怪異。一般有些怪異的決策是值得我們警惕的、需要花更多的時間深入思考合理性。
另外,菜單是功能的體現。舉例而言,名稱為“發佈文章”的菜單對應的UI界面里可以發佈文章的信息,而在“文章管理”這一欄菜單對應的UI界面里,用戶可以查看已發佈的文章和修改某篇文章。所以我們會為“發佈文章”和“文章管理”這兩個菜單設置角色“站點編輯人員”。可是如果以後出現了新的角色“編輯部負責人”,那麼我們又需要將該角色加入到它可以使用的菜單里,否則新的角色無法看到文章相關的菜單,這樣操作起來雖然可以滿足需求,但是使用的體驗太差了。更重要的是,當菜單里提供的功能很多(例如首頁、桌面之類的菜單里會展現很多功能的數據),那麼我們在設置哪些角色可以查看菜單的時候需要根據功能考慮很久。當菜單對應的UI界面發生了改變,裡面有可能取消或增加某項功能,這時我們又要重新設置菜單的角色以適應變化,這樣操作起來太過繁瑣,簡直無法忍受。
綜上所述,”為菜單設置多個角色”這一決策確實存在很大的問題。同樣的,在改進這個決策之前我們又發現了一個新的事物:“功能”。我們認為在系統里可以描述出項目里有哪些可以使用的功能是有必要的。因為這樣可以將功能定義與菜單相關聯,表示菜單提供了哪些功能。另外也可以將功能定義與角色相關聯,表示角色可以使用哪些功能。這樣以功能定義作為橋梁,系統依然可以識別出角色可以查看哪些菜單。與角色和菜單直接關聯相比,系統提供的功能是已知且有限的。我們只用在系統完成後,根據當前的功能點設置一次功能信息,這樣就可以添加任意多的角色和菜單與之匹配。
因此,將”為菜單設置多個角色”這一決策修正成“為菜單設置多個功能項”,隨著這一改進帶來的連鎖決策變化是:“可以在系統中創建功能項的描述”、“可以為角色設置多個功能項,代表這個角色可以使用哪些系統功能”,“用戶登錄後,系統得到用戶的屬於哪些角色,並查找出這些角色擁有哪些可以使用的功能。再將這些功能與菜單提供的功能去匹配,匹配到的菜單就顯示,匹配不到的菜單就隱藏”。
至此,我們已經分析到足夠多的信息以便展開編碼工作。不論上述決策是否完美,是否真正的貼近事物本質,至少我們有了編碼的依據。有了這些依據我們儘管大膽的去編寫代碼。CA不贊同將需求全面剖析清楚後再行動,而是只要有了明確的編碼目標後立即展開工作,再以迭代的方式持續分析需求同時改進代碼。CA會幫助你將風險控制到最低點,就算討論出來的決策在以後需要改變也是很輕鬆的事情。