【程式設計師日記】---當“微服務”遇到了“電餅鐺“

京東雲開發者發表於2023-03-29

作者:京東物流 趙勇萍

之後的日子裡,我可能會陸陸續續寫一寫跟程式設計技術感悟相關的文章,一來可以梳理一下對技術和工作的思考,二來也可以記錄一下技術成長的的過程。

換個叫法的話,就叫做程式設計師日記吧。

電餅鐺

今天就從電餅鐺說起。

上週,我家的電餅鐺壞了,原因可能是清洗過後線路短路導致的。那個老式電餅鐺確實用了好些年,且功能單一,基本上除了開關鍵,再也沒有什麼可以按鈕的地方了,不過老媽卻一直用的很順手。

而對我來說,這確實個好訊息,終於可以換一個好電餅鐺了。於是在網上買了一個七百多的蘇泊爾的電餅鐺,這一下子感覺高大上了許多,很多內建模式,可以支援煎蛋,煎餅,炸雞翅等多種模式,對溫控的把握也十分精準。

不過,對於我老媽來說,她並沒有顯得多興奮,我將使用說明一一教給她用,但老媽最終只選擇一種用法:開啟開關,選擇自定義模式,一切都靠經驗去判斷電餅鐺的溫度和對食材的感覺,其他所有的內建模式,對她老人家來說,好像確實是多餘的。

對此,我有點陷入沉思,總覺得有一種似曾相識的感覺。 仔細思考,其實這種情況在程式設計過程中屢見不鮮。其實這不就是我們微服務架構中經常會遇到的一種情況麼....

好的,接下來,如果把我和我老媽的上述行為抽象為程式碼,可以寫成如下:

老媽做煎蛋:

/**
 *老媽做煎雞蛋
 */
public class MainApplicaiton{
    /**
     *電餅鐺
     */
    @Resource
    private DianBingCheng dianBingCheng;
    /**
     *老媽的判斷服務類
     */
    @Resource
    private MotherCheckService matherCheckService;
    /**
     * 老媽的行為服務 
     */

    private MotherBehaviorService MotherBehaviorService;

    public void execute() {
        //開啟電餅鐺
        MotherBehaviorService.Open(dianBingCheng);
        //檢查溫度
        matherCheckService.CheckTemperature(dianBingChengAggregate);
        //開始煎雞蛋
        MotherBehaviorService.fryEggs();
        //檢查是否熟了
        matherCheckService.CheckRipe();
        //完成,關機盛盤
        matherService.complete();
    }
}

我做煎蛋:

public class MainApplicaiton {
    /**
     * 電餅鐺聚合根
     */
     @Resource
     private DianBingChengAggregateRoot dianBingChengAggregate;

     public void execute() {
        //開機
        dianBingChengAggregate.open();
        //煎蛋模式
        dianBingChengAggregate.fryEggs();
        //完成,關機盛盤
        dianBingChengAggregate.complete();
     }
}

大家可以看出這兩者的實現區別,我們有時候需要思考一下,我不就是處理一個日常特別簡單的事情,一定要引入那麼多的服務類麼?

貧血模型和充血模型

可能大家感覺出來了,這兩種實現,就是領域驅動設計中典型的貧血模式和充血模式。這裡不得不先說一下貧血模型和充血模型。

貧血模型是事務指令碼模式。對於程式設計師來說,指令碼呢肯定是要比寫設計說明書要快的多了。比如,我們要設計一個訂單流程,包括下單,取消,售後,拆單等情況下的訂單流轉,那麼我們就就會任選一個Service類,寫一個方法就能搞定,而這時,原來的那個order類中,就會非常非常的“清爽”,裡面全是GET方法和Set方法,沒有任何行為。而很多人喜歡給這個order類一個定義,叫OrderDomain,訂單領域模型。當然我更喜歡叫這種模型為DB模型,是面向資料庫的,而不是面向領域業務的。

而充血模型是才是典型的領域模型模式。他實現起來相對複雜,但這種複雜也是相對的,還是上面的例子,我們會在Order這個物件中放入響應的動作行為的方法,例如我們更新訂單狀態不會用setStatus()這種方法,而會封裝類似orderCancel(),orderComplete()的這種業務行為方法,當然這種方式會讓這個類略顯“複雜”。

總結一句話,真正的領域模型會把資料和行為聚合在一起,形成聚合根,對外提供基於行為的方法,而非指令碼化的增刪改查。只有這樣才能夠更好的對微服務進行拆分。

如果僅是貧血模型其實不是對系統架構危害最大的,想我們已經很熟悉的MVC,透過貧血模型也可以寫出複雜的高效快捷的系統。

但是,有一種危害就會很大了:

就像好多同學用這物件導向的語言,寫著程式導向的程式碼一樣,很多同學喜歡用冠以領域驅動設計的噱頭,卻寫著MVC式的“面向資料庫程式設計”, 它最大的問題是你引入了領域模型設計的所有成本,但卻沒有帶來任何一絲的收益

只有當你充分使用了物件導向設計來組織複雜業務邏輯之後,才能抵消這種成本。如果將所有行為都寫入一個一個的Service類,領域是被割裂的,那最終你會得到一組事務處理指令碼,從而錯過了領域模型帶來的好處,而且當業務足夠複雜時,你將會得到一堆爆炸的事務處理指令碼。

此外,你還會發現,

本來屬於一個領域的能力,卻被散落在了工程的各個角落。這樣一來,根本無法形成一個可複用的能力。

當然有同學也會提出疑問:“_我的業務場景中領域之間其實關係比較緊密,我會經常遇到會觸發同時跟不同領域行為相關的業務,在這種情況下,我就透過一個個的Service擴充套件,這就是一種顯而易見的解決方案呀~~_ ”

其實有這種想法的同學,主要是對領域驅動設計理解不深刻的,同時又對傳統的MVC框架開發根深蒂固。

其實領域驅動設計早已給出它的最佳實踐,那就是領域事件。而面向事件的程式設計思想對於後端來說,是一種非常高效和優秀的思想。透過領域事件,我們可以實現領域之間的解耦,同時也維護了領域模型的獨立性和資料一致性。而關於領域事件又是一個很大的話題,以後有機會再聊。

思考

在我們每個人的大腦裡,對於一件事,如果沒有概念和理解的話,我們會有意識的躲避那件事,或者用自己熟悉的概念去套用。微服務,DDD,中臺這些詞彙,恰恰是這樣的概念。這是有了這些概念,才讓我們不斷的努力去學習它,思考他,鑽研他。

在微服務和DDD這波浪潮裡,很多同學都想用這些概念來包裝自己,就像我面試過的很多候選者,他們中的很多都在簡歷中寫了精通領域驅動設計,同時也能說出一些聚合根,實體,值物件等概念,但是實際落地就變成了貧血模式的程式碼。

所以,對於程式設計師來說,最核心的能力並不是你會幾種語言,會幾個架構或者幾個名詞概念,而是理解那些概念並轉化成自己的程式設計思想。

同時,

勇於創新和敢於推翻自己的已有認知,也是一名程式設計師能否有一直前進的動力的前提