一、易於腐化的軟件設計
猶記得剛剛參加工作時,是地圖廠商四維圖新集團旗下的一家子公司,主要從事規划測繪相關軟件研發的公司。當時我的項目是為勘測設計院提供相對應的應用軟件,對地理信息和規划相關的圖紙信息,幾乎已經專業水平。事實上,規划設計大概和軟件設計類似,有規划的設計、或無規划的設計,造成的結果幾乎是天壤之別。
我們或許很容易就能設想到一個毫無規划設計的城市,縱橫交錯的路網、雜亂無章式的建築布局、各種凌亂的棚戶區設計,恰好象征着軟件設計的無序性,也恰好體現了軟件企業在經費不足、組織缺乏管理、開發者能力不足、軟件隨時隨地想改就改時的行業現狀,只能說這樣的軟件是最能符合當時實際勞動生產力水平的產品。
如圖一所示,巴西棚戶區,層層疊疊、風格迥異、密密麻麻,如果作為一個外人貿然來到這樣的地方,大概很容易迷失期間、更不用說充斥在棚戶區的各類毒品和黑社會。雜亂無章的建築和街區,就像代碼中錯綜復雜的調用鏈;而借助貧民區搞事的黑社會就像是代碼中的異味或者bug,表面上看起來如此平靜、與世無爭、但是你永遠也不知道啥時候會來一冷槍。
不要以為離我們很遠,我們其實輕易就能寫出這樣的軟件工程項目。不一定是“大泥球”系統,也有可能只是一些看似簡單的業務系統,但內部代碼邏輯,可能會復雜到令人窒息的程度。也許那個時候有個別開發者也許會試圖靠自己的能力來改變局面,但是往往也會礙於屎山太大,難以下咽。
大概只有最頂級的規划設計師、耗費足夠多的資源,才能將這樣的軟件系統進行整改。然而,即便如此,如果以后沒有持續維護的手段、更好的設計、僅靠老程序員或個別架構師、盲目相信將單體服務拆分成微服務,幾乎不太可能實現軟件未來的可持續發展。
一個良好的軟件產品的一生、或許其實是一家企業一生的真實寫照。
在特定組織架構下,缺乏技術基因的組織有時候期待技術變革,卻會開啟新的泥坑。而那些渴望靠技術改變一切的技術專家,雖然擁有某些大廠微服務式架構、以及架構改造的經驗,他們也試圖通過自己的努力,為企業業務騰飛助力。而在他們過去的經驗中,往往相信組織遇到的問題,用微服務一定能解決問題。然后大肆擴招,一年內從幾個人的規模、擴招到數百人的規模,將原來的系統從單體服務、改良成為微服務。但是靠單槍匹馬根本無力拯救大勢,沒有更好的業務拆分策略,就只能按照數據庫的表名關系實現了最簡單的拆分。架構改造並非每次都會百試百靈,有時甚至連原來的需求都包不住,畢竟只能看到用戶界面層外觀上的表面邏輯,而隱藏在業務中的那數十萬行代碼,哪怕包含了企業最有價值的經驗財富,也由於代碼過於混亂,最終拋棄在源代碼管理器中,堪稱化神奇為腐朽。
二、易於腐化的面向過程開發
老系統改造也好、新系統開發也好,毫無疑問,我們最容易相信的其實是老程序員經驗,而程序員們掌控系統的方式,就是靠數據庫建模來驅動軟件開發的古老模式,而且幾乎都是面向過程式的代碼,這些代碼的流程幾乎一模一樣,只需簡單的按照步驟,一步步套模式,輕易就能學會。
1、查看用戶界面,定義需要綁定到界面的模型和層級結構。
2、設計數據庫,不管什么類型的項目,先根據客戶提供的業務表單、將其轉化成實體關系(ER圖)、然后建立對應的代碼模型。有可能使用專業軟件設計ER圖,也有可能會使用Navicat軟件設計ER圖。
3、設計接口,然后把數據拼湊成用戶界面層所需的對象。
4、代碼層次結構為傳統的三層架構,嚴格按照用戶界面層、業務邏輯層、數據訪問層進行設計,有時候會引入依賴注入框架,實現不同層次間的解耦。
但是有時候程序員不會嚴格區分需要編寫的代碼,究竟是屬於哪個層次應該囊括的內容。於是毫無疑問,如果代碼是為了實現用戶界面上某些數據綁定操作,代碼就往用戶界面層寫;或者代碼是為了實現從數據庫中抽取某些復雜數據、並構造成滿足用戶表現層邏輯的查詢對象,那么就可以看到數據訪問層代碼中那些臃腫的SQL語句或查詢方法。
正如“羅馬不是一天建成的”,屎山也同樣如此。這樣的寫法在代碼剛剛編寫之初並沒有問題,只是隨着業務變化、時間的積累、程序員的水平、方法重構、新技術新組件的引入,代碼將成為屎山。
這時,高級程序員們的價值,就在於他如何能夠在屎山中快速找到bug、並解決問題的能力,這大概是一種不能復用、不可再生的能力,因為永遠有讓人看不懂的垃圾代碼,而且每家企業都有自己的特點,不同企業間往往不能循環利用。我一位朋友經常吐槽,他感覺自己的價值就是守住公司那份擁有8年歷史的古老代碼,以便其他程序員在進行代碼修改時,不會引發莫名其妙的bug讓系統無法運轉。
三、過程式開發和事務腳本模式
在現代軟件工程學的教科書中,都會指出面向對象是解決軟件復雜性的方法,但實際上掌握這種方法的開發者並不多。由於開發者普遍缺乏抽象化思維,所以面向數據庫、面向過程式的編程習慣能夠成為業界主流,並非時代的倒退,而僅僅只是在短期效率和長期維護性上,被迫做出的艱難選擇。
假設我們設計出的符合三層架構的系統結構圖簡化后,如下圖所示:
我們來看看這種數據庫建模的開發流程中的輸出成果:
1、會定義兩種對象,分別是是面向UI層的模型(DTO)和數據實體(Entity)。在領域驅動設計中,將這兩種稱為所謂貧血模型,貧血模型,只有賦值器Set和取值器Get,(在Java里面會使用POJO 這個名詞來定義)。貧血模型是為了作為保存狀態或傳遞對象而存在,他並非按照實際用例場景對某類具體事務的抽象、也沒有與對象相關的行為。
2、定義數據訪問層來實現數據的持久化、或者從持久層實現數據的創建過程。數據訪問層存在的目的是為了構建上述貧血模型對象,這種訪問機制被成為“事務腳本”。事務腳本與對象行為割裂,而且容易導致異味產生。
3、與用戶行為相關的操作割裂的存放在不同層。有的可能放在用戶界面層、有的可能放在數據訪問層、有的可能放在業務邏輯層,造成了領域知識的丟失。
4、用戶界面層使用接口作為外觀或者一種行為、開發者會使用自己獨立的風格習慣來定義這種行為,就容易造成術語和規則不統一,也會為后期產品的維護迭代造成問題。
5、現在的軟件設計,往往要求輸出一份高保真的原型圖、也會按照敏捷項目管理的流程對這份原型圖建立持續更新的機制,確保原型圖是需求的具體表達,但是產品語言並非統一語言,也許產品語言具有業務含義,但是由於不能指導開發者進行接口、類、持久層的設計,造成了代碼與需求的割裂。在張逸老師的《領域驅動戰術實踐》提到他曾經使用dimension和metric兩種不同的對象來定義一個維度對象,為代碼造成了不必要的麻煩。我也曾經在一個項目,遇到過產品術語未能澄清,導致開發中使用style和theme兩種截然不同的定義來定義與“風格”相關術語,為代碼引入了不必要的糾結。
四、領域驅動設計是什么?
領域驅動設計引入了以下概念,但是我們無需在這篇文章中深刻理解這些概念的具體含義,我們只需知道,有這個東西。當我們開始按照領域驅動設計的方法設計一個系統時,按照前人整理的領域驅動的sample,往往就會將概念融匯貫通,達到更好的理解效果。
1、統一語言:定義好產品原型,需要建立統一語言。這是一種在內部和外部都能使用的規范化用語,包括UML、適當的圖、一致性的描述、以及專業術語和術語對應的英文描述。
2、實體:在領域中可以通過標識進行唯一值定位的對象。
3、值對象:在領域中,從其他領域或某個實體中分離出只包含某些特定屬性的對象。由於不具備唯一性特征,往往無需用於數據持久化。
4、聚合、聚合根:將具有相關性的對象聚合在一起,並以聚合根的形式統一對外提供訪問方法和屬性字段成員。
5、限界上下文:領域包含核心領域、子域和通用子域,而限界上下文則是一個具體業務的流程。每個限界上下文獨立於其他限界上下文而存在,獨立演進、功能完備。限界上下文的識別充滿技術含量。
6、領域服務:包括倉儲服務和工廠服務,前者負責實現對象與數據庫的操作過程、封裝了一系列數據庫操作的方法;后者則側重於對象的創建過程。個人認為從三層架構演進到領域驅動架構過程中,倉儲服務是最接近於數據訪問層的邏輯,也是讓大部分領域驅動架構最終又回歸到三層架構的一種通病。從對數據訪問層中抽出對象、行為、數據訪問,是戰術設計的關鍵步驟。
領域驅動設計引入了一堆新的架構形式,包括經典的四層架構、EDA(事件驅動架構)、CQRS架構(命令查詢職責分離)。而由於Evans的原書沒有過分討論如何識別領域,后來又有許多大佬在他的基礎上進行了完善,提出了許多方法,包括名詞、形容詞、動詞建模法、事件風暴、四色建模等方法,限於篇幅,且聽下回分解。
五、思維的轉變,才是最大的困難
領域驅動設計,或許是解決這些問題的一劑良方,但也或許是開啟了暗黑世界的大門。
概念晦澀難懂、程序員們不願意開始思維變革、技術上可能存在不預期的坑、都可能讓新方法的實踐陷入一灘爛泥。還有許多人以為自己看懂了領域驅動設計(包括筆者),在往項目中運用時,總是有意無意的會被過程式代碼的思維定式控制,讓架構回退到三層架構。
由於微服務架構的興起,讓復雜系統的開發維護成為大家普遍關心的問題,使得Eric Evans於十五年前提出的這套理論,在今天綻放出了新的光芒。當然領域驅動設計僅僅只是眾多面向對象編程的一種實踐,通過領域驅動設計將UML等方法靈活的運用其中,通過打破原有數據庫關系建模給代碼造成的桎梏,讓開發者能夠真正的實現面向對象編程。
然而思維模式的轉換並非易事,從過程式代碼中,抽離出與對象有關的行為,遠比理解這幾個概念要復雜,這需要大量經驗的積累。
毋庸置疑,數據庫建模驅動軟件開發具有速度快、學習成本低的顯著特點,在許多項目中,能在短期內可以給開發者帶來許多便利;而應用領域驅動設計,則可以在更長的維護周期內,給軟件維護帶來實質性好處。
兩種不同類型的開發模式,根據企業實際出發進行選擇,還只是開始,但能真正運用好領域驅動設計或者UML、面向對象設計這種軟件工程的美學思維來改造我們的系統,讓系統綻放出更加璀璨的光芒,這才是軟件設計的樂趣所在。
本文版權歸原作者和博客園共同擁有。作品采用知識共享署名-非商業性使用-相同方式共享4.0 國際許可協議進行許可。
本文來自: 溪源 | 長沙.NET技術社區。閱讀更多精彩好文,歡迎關注長沙.NET技術社區公眾號【DotNET技術圈】。
首發於溪源的個人博客www.techq.xyz