當前位置: 妍妍網 > 碼農

CURD系統怎麽做出技術含量驚艷面試官完善版

2024-02-09碼農

引子

十幾年前的一天,有段時間在讀張愛玲的小說,同事看見了就說原來你愛看這種東西。我到現在都不知道他說的是哪種東西。那時候我只是單純的覺得張愛玲寫的東西有種特質,這種特質掌握了做什麽事情都不會太差。後來我想明白了,這種特制叫做:善於運用技巧。

舉個例子,張愛玲有個神作【金鎖記】,女主經歷了不幸的一生心靈扭曲,害了兒女的一生。她女兒名字叫長安。女兒30歲了,終於有人上門提親,她卻說自己女兒怎麽怎麽不好,還抽大煙,最後嚇跑了提親的人。這時候是這麽描寫長安的:

長安悄悄的走下樓來,玄色花繡鞋與白絲襪停留在日色昏黃的樓梯上。停了一會,又上去了,一級一級,走進沒有光的所在。

張愛玲施展她高超的繪畫功力,手繪了一個電影中的分鏡。不寫長安的表情,不寫她的心裏,只給她的腳一個特寫鏡頭。黑白的主體輪廓,昏黃的背景,畫的盡頭沒有光,簡直是野獸派畫風的一幅畫。

鏡頭不動,只有人物的腳在動。整體包含三個畫面:第一個畫面是走下樓梯;第二個畫面,停了一會兒,畫外音:聽到了提親時的對話正好說到抽大煙的事;第三個畫面是沈重的腳步走回了沒有光的所在,那就是她的余生。

此時無聲勝有聲,一切盡在不言中。

在 有朋友開玩笑說都用上了領域驅動了,就不叫CURD系統了吧。這裏我解釋一下,怕大家對DDD領域驅動設計有什麽誤解。

DDD是為解決軟體復雜性而生,但不僅僅可以被用於復雜的系統。它裏面提到了很多技巧,其實 CURD就是其中一種技巧。這些技巧適用於任何系統,哪怕是非常小型的系統,比如 裏提到的謙卑物件模型、充血模型。

今天咱們再提高一下CURD系統的門檻,連DDD技巧,咱也不用。就用寫咱們平時都在寫的最簡單的介面。能說明設計理念,也能驚艷到面試官:

大綱

  • n+m返回值設計

  • 終態設計

  • 兩碼一態

  • 三種設計方法

    n+m返回值設計

    概述

    一個介面入參有n個值,需要的返回值是m個值。在返回值中可以把入參和返回值一起返回。

    這種設計便於追蹤和排查問題:

    為了防止呼叫方沒有日誌追蹤號、打印日誌過多等原因造成不方便跟蹤請求的問題,可以設計返回值將入參一起打印。這樣呼叫方可以透過一條日誌方便的獲取到入參和返回值。線上排查問題會非常方便。

    建議使用場景

    假設有個場景,呼叫方的系統設計不方便請求追蹤,比如沒有執行緒追蹤號。在高並行量場景下可能日誌是這樣的:

    1、thread-1| className|參數1:11,參數2:11

    2、thread-2| className|參數1:22,參數2:22

    3、thread-1| className|參數1:33,參數2:33

    4、thread-2| className|返回值:44

    5、thread-1| className|返回值:55

    6、thread-1| className|返回值:66

    你能決定55和66對應的誰是1,誰是3的返回值嗎?

    但是如果返回值包含了入參,就好辦多了,舉個返回值例子:

    thread-1| className|{參數1:11,參數2:11,返回值:55}

    這樣就一目了然。有的時候可能是呼叫方本身的設計問題,但是如果被呼叫方能透過巧妙的設計幫助呼叫方。一旦遇到問題,呼叫方可以自己先進行排查,不用馬上聯系被呼叫方幫忙。即節約了自身的成本,又體現了專業性,何樂不為。

    不建議使用場景

    1、公司內有規範的、完善的、各個部門嚴格執行的全鏈路追蹤標準

    2、入參很大,不建議全量打印,可選取部份關鍵或者完全不用

    終態設計

    概述

    在 裏我講過有限狀態自動機。有限狀態機涉及狀態流轉。狀態從分類上可以分成三種:初始狀態、中間狀態和終態。這段時間不是一直在講TCP底層通訊嘛,來一張TCP狀態的流轉圖體會一下:

    有限狀態機的重點在於有限,要有起點和終點。也就是一定要有終態。在 我講過:

    在傳統的單機系統中,呼叫一個函式,要麽返回成功,要麽返回失敗。這就是兩態系統(2-state system)。

    在分布式系統中,由於系統是分布在不同機器上的。還可能有一種狀態叫:超時。成功、失敗和超時是分布式系統呼叫的三態。

    超時不是終態,而是一種中間狀態:最終有可能下遊是成功了,也有可能是失敗了。這時候我們需要在超時之後推定一種狀態,推定成功或者失敗。究竟是成功還是失敗因功能而已。

    建議使用場景

    比如付款操作,不知道是否成功就推定是成功的,那使用者可能沒有付款就拿到了商品或者享受了服務。商家就會資金損失。所以一般會推定失敗。讓使用者再次支付。最終透過查詢或者對賬發現使用者實際是支付成功的,可以再把錢給使用者退回去,保證交易的公平性。

    退款恰恰相反,需要推定成功。告訴使用者,錢退給你了。最終透過查詢或者對賬發現實際是退款失敗了,可以系統重新發起退款,直到真正退成功為止。

    後台管理系統也很需要這種終態設計。比如釋出系統,釋出了一個功能,釋出系統如果出現了問題,這次釋出沒有結束。使用者可能沒有辦法進行下一次釋出。這時候可以設定超時自動結束,防止未結束的流程始終在那裏,起碼會幹擾視線,增加判斷成本。

    不建議使用場景

    這十幾年的開發還沒有遇到過什麽場景不需要終態。但是有些終態是隱式的。舉個例子:

    在 裏提到的 事件溯源模式 ,就是說比如在一張數據表中,只做數據記錄用,只有插入操作沒有更新操作。比如一個使用者行為記錄表,會記錄使用者什麽時候登陸,因為使用者是APP登陸的,所以2年都沒有登出操作。是不是就沒有終態呢?我們從這張表本身來看,因為是事件記錄,所以落庫的那一瞬間,狀態就是「記錄完成」,本身就是終態。

    兩碼一態

    概述

    兩碼是指系統碼和業務碼,一態是說透過兩碼就可以確定最終的狀態。

    很多情況下咱們是已經隱式的使用了兩碼一態,比如自己設計了一個單點登入介面給外部呼叫。一般是這樣設計:

    實際上這個介面有個返回值,比如登入成功會返回使用者資訊或者直接返回 是否成功的布爾值 。但是這個介面是給外部呼叫的,需要包裝一層,最終就是返回 Re sult <User > 或者Result<Boolean>。Result是怎麽定義的呢?

    @Datapublic class Result<T> {privateboolean isSuccess;privateString errorCode;privateString errorMessage;private T data;}

    有沒有感覺和自己平時寫的程式碼很像?

    分析一下:最外層isSuccess很多人都把它當成最終的狀態來使用。比如在這個登入的場景下就代表了登入成功。但是如果isSuccess=true但是data==null?這就讓人很迷惑,究竟是成功還是沒有成功呢。成功了為啥我的數據沒有吐給我?

    稍微合理一點的是 isSuccess = true 代表系統處理成功,就是沒有丟擲什麽異常。究竟業務是否成功看data。

    在更加規範的場合是這麽定義的:


    @Data
    public class Result<T> {
    private String sysCode;
    private String sysMessage;
    private String bizCode;
    private String bizMessage;
    private T data;
    }

    如果系統碼sysCode為成功,是否業務成功需要看bizCode;如果sysCode失敗,bizCode就不用看了,也很可能根本就沒有拿到;如果sysCode為失敗,看系統碼是多少就可以定位問題範圍,或者至少說這個問題開發人員應該查查;如果bizCode為失敗,如果業務碼也能定位問題範圍,而這種問題一般不需要開發人員來處理。

    建議使用場景

    比如後台管理系統中,可以定義:連線資料庫異常、空指標異常等為系統失敗,對兩種異常分配不同的系統碼,比如S001、S002;系統正常碼可以分配S000;必填參數為空、參數校驗失敗為業務碼,比如B001、B002;業務正常可以分配B000。最終狀態由兩個碼共同決定。當系統碼異常開發人員需要處理。當業務碼異常可以聯系營運人員培訓一下怎麽老填錯或者不處理。

    比如我在之前的文章 中設立過一個場景

    假設我在超市買了我喜愛的經典搭配:烤腸+酸奶。然後我就微信掃碼付款了。付款時序圖大體是這樣的:

    不用理解這個圖,關系不大。我要說的是像支付這種場景後面會涉及多個環節,比如微信支付自身還要呼叫銀行呢。

    這時候的系統碼和狀態碼中可以加一位,代表那個環節。比如系統碼定義:

    WS000、 W S 001…… BS 000 、B S001 ……

    S代表系統。W代表微信,比如下遊請求銀行,銀行處理成功了,自身異常了,會設定這個碼;B代表銀行,比如銀行自身異常了會設定這個碼。

    同理,業務碼 定義

    WB 00 0 WB 001…… BB 000 、BB 001 ……

    第二個B代表業務。W代表微信,比如自身校驗沒有透過,根本不會請求銀行,會設定這個碼;第一個B代表銀行,比如銀行最後發現使用者余額不足會設定這個碼。

    不建議使用場景

    如果是給一個行程內部自身使用的,當然就沒有必要定義這麽復雜。