應對複雜軟件的思考

因爲本身身處SAAS行業,在經歷了幾輪複雜需求的蹂躪以後,我一直試圖尋找一種解法,能夠儘可能cover住複雜多變的需求。在過去的一年中,經過反覆閱讀和實踐,彷佛讓我對此有了一些清晰的思路,因此我想寫一點東西總結一下本身的這一年裏的思考。git

在咱們的項目初期,項目的規模可能比較小,代碼量不多,咱們的代碼或許還能整理的比較乾淨,就像這幾組交換機的網線同樣,比較有條理。數據庫

整齊的網線

可是隨着功能複雜以後,項目也隨之變得龐大,整個代碼就可能會和這個機房同樣,很是的混亂。在經歷幾回這種情況以後,因而我便在想,到底是什麼問題致使了這種混亂。編程

凌亂的機房

首先來看一段代碼(Kotlin Code):多線程

fun executeRequest(request: Request) : String {
    // 校驗身份
    val isValidate = validateRequest(request)
    if( isValidate ) {
        return "Request is not valid"
    }
    // 處理業務
   dealBiz(request)
    try {
        // 存儲數據
        saveToDB(request)
    } catch (exception:Exception) {
        return "Occur error when save results to DB"
    }
    // 發送消息
    val isSendSuccess = sendMessage(request)
    if (isSendSuccess == false) {
         return "message send unsuccessfully."
    }
    return "success"
}

這是咱們比較常見的一些代碼結構,其實看起來問題也不算很大,可是隨着業務複雜,業務邏輯的控制和控制邏輯耦合的很厲害,閱讀這種"麪條代碼"的成本愈來愈高。每一位新進入項目的夥伴猶如進入了一個「代碼迷宮」。來來回回去尋找本身須要的那一段代碼,實際上這個時候已經造成了:架構

只有上帝和我能看得懂的「上帝代碼」了。併發

這顯然是咱們不肯見到的代碼,在左耳聽風專欄裏有一篇《編程的本質》裏講到:異步

有效地分離 Logic、Control 和 Data 是寫出好程序的關鍵所在。函數

那什麼又是Logic,Control,Data 呢?學習

  • Logic : 就是通常的業務代碼,相似上面代碼中的dealBiz(),sendMessage()等等
  • Control : 對業務邏輯的流程控制,好比遍歷數據、查找數據、多線程、併發、異步等等
  • Data :函數和程序之間傳遞的這部分信息

若是有效地將這幾種代碼分離,代碼可讀性將會大大提高。經過這種拆分,咱們也下降除了本身以外的維護者閱讀代碼翻譯業務內容的成本。經過分離,咱們能夠將代碼寫成這樣:測試

fun executeRequest(request: Request) : String {
    return Result
            .of(request)
            .flatMap { validateRequest(request) }
            .flatMap { dealBiz(request) }
            .flatMap { saveToDB(request) }
            .flatMap { sendMessage(request) }
            .fold(
                success = { return "success" },
                failure = { return it.message }
            )
}

固然這裏說了如何寫細節的代碼,那麼代碼架構又如何去作纔可能保證能夠應對這麼多的變化?

通常的項目中咱們把一個軟件系統進行分層,這是咱們目前作工程項目的一個共識,咱們最初學習的分層架構就是經典的三層架構了。它自頂向下分紅三層:

  • 用戶界面層(User Interface Layer)
  • 業務邏輯層(Business Logic Layer)
  • 數據訪問層(Data Access Layer)

數據訪問層這塊,其實不少系統已經變成了面向數據編程,最終作成了「數據庫管理系統」。按照傳統的三層模型,用戶界面的開發依賴Service層,而Service層又依賴着DAO,DAO對應着數據庫。你們相互依賴,業務邏輯一旦修改,就意味着要從DAO層開始修改,數據庫也跟着被修改,而每每隨着咱們開發的深刻,業務的模型會被不斷調整,這樣數據庫可能就要頻繁的變更。代碼也開始變得複雜... ...

而在領域驅動設計中提出了另一種四層架構,在此以前,我想先分享《實現領域驅動設計》一書講的六邊形模型。

六邊形架構

咱們在設計系統的時候,每每過於關注數據庫,Http接口等基礎設施的設計,而忽略了咱們須要關注的業務。在複雜系統中,最容易變化的也是業務形態,產品常常會要求改來改去,由於業務自己就在不斷地演進,若是咱們一開始就基於數據庫做全部的設計,那麼勢必一旦趕上業務的修改,庫表確定也須要對應先進行變化。假如咱們融入六邊形架構,將數據庫和暴露的Controller都視爲是基礎設施,先去關注業務的模型和代碼,Class的修改比要數據庫改起來要簡單的多。另一方面,也大大提升了程序的可測試性:在沒有準備一堆基礎設施(數據庫,接口,異步通知等等)狀況下,能夠先測試邏輯的完整性。

另外,有時候隨着業務增加有的基礎設施是會須要進行替換的,採用六邊形架構以後,這種更換的成本就會下降。另外若是出現須要使用Web Service的客戶,咱們也沒必要糾結於以前的HTTP接口,直接開出一套新的協議代碼供客戶使用,而不會糾結領域部分代碼有邏輯上的缺失。

採用六邊形架構以後,咱們的領域模型也會更加獨立,更精簡,在適應新的需求時修改也會更容易。在《架構整潔之道》之中提到的「整潔架構」也與「六邊形架構」大同小異。

Bob大叔的Clean Architecture

其實這兩種架構也是依賴倒置原則很好的實現:

  1. 高層模塊不該該依賴低層模塊,二者都應該依賴其抽象
  2. 抽象不該該依賴細節
  3. 細節應該依賴抽象

此時再回顧原來定義的四層架構:

  • 用戶展現層
  • 應用服務層
  • 領域層
  • 基礎設施層

他們的依賴關係如圖:

四層架構

這樣咱們能夠將業務核心代碼放入領域層之中,要應對的各個場景代碼放入應用服務層中。將協議轉換、中間件和數據庫的適配都放入基礎設施層裏。在應用層與Controller之間的那些VO做爲用戶展現層,以作出整潔架構。

當咱們開始學習Java的時候,都知道Java是一門面向對象的語言,咱們本能夠將現實世界翻譯到代碼的世界之中,但實際上咱們每每在項目中只會將對象定義成貧血模型,最終寫成面向過程的代碼。如何作才能讓這個複雜的世界反應到代碼裏呢?

讓咱們再從需求提及,對於一個複雜的軟件,任何一個項目的參與者(包括初創的成員),都很難靠本身就看清整個項目的全貌,咱們猶如圖中的盲人,你們可能最後對項目的理解都是不一致的。此時每一位參與者都猶如「盲人摸象」中的「盲人」,對需求(大象)只有片面的理解,因而乎,有的人以爲大象是水管的形狀,有的人以爲大象是扇子同樣的形狀,有的人說大象長得跟柱子同樣... ...

盲人摸象

經過討論,咱們會對自個人認知進行一些修正,最終大體得出一個需求的全貌。

討論的價值

好比咱們要去識別一些系統邊界,在DDD的戰略設計中很是強調劃分界限上下文。好比我做爲一個個體,在不一樣的場景中,個人身份、角色都不大相同。

對自我身份的識別

猶如在上圖中,在「地鐵」、「家庭」和「公司」中,個人身份是不同的,可是我依舊是我,找出業務的場景,也就意味着我找到了系統的邊界。經過分析場景識別邊界來找出系統的核心領域和支撐領域,以此來最終肯定系統的數量,下降系統的耦合。

咱們還能夠用四色建模方法來識別出咱們系統中發生的整個流程,發現到底是誰經過什麼方式觸發了什麼事情,最終又影響了哪些對象。

四色建模(圖片來自infoq.cn)

最終經過咱們找出的事件,整理出一個可以讓咱們進行溝通的模型。在咱們的模型被構建得相對完善之時,其實代碼也差很少已經被構建出來了,由於這個時候再去回想面向對象的設計,咱們發現模型即代碼,代碼即模型。

四色建模的要點

一直以來我都但願經過一些好的「工程實踐」來提升團隊的效率以及咱們的代碼質量,我想這也是思考這些架構的意義吧。我想用《架構整潔之道》中的一句話來作本文的總結:

軟件架構的最終目標是,用最小的人力成原本知足構建和維護系統的需求。

正如《人月神話》裏說的同樣,軟件工程裏沒有「銀彈」,即便作了整潔架構也沒法避免需求的變化和延期,只是但願當咱們身處需求的困境中時,仍能給本身以更多的選擇。

參考資料&活動 :
相關文章
相關標籤/搜索