戲說領域驅動設計(二十)——值對象

来源:https://www.cnblogs.com/skevin/archive/2022/04/02/16044675.html
-Advertisement-
Play Games

相比於工廠模式,抽象工廠模式的每個工廠可以創建產品系列,而不是一個產品; 抽象工廠用到的技術:介面、多態、配置文件、反射; 抽象工廠模式的設計原則: 實現客戶端創建產品和使用產品的分離,客戶端無須瞭解創建的細節,符合迪米特法則; 客戶端面向介面定義產品,符合依賴倒置原則; 客戶端面向介面定義工廠,而 ...


  值對象這個東西在DDD里算是比較抽象的,好多人學了半天也學不明白。我這種聰明人也費了好大勁,總算苦心人天不負,現在也能用個有模有樣了。戰術模式中不論是領域服務、對象工廠還是資源庫,基本上您能聽懂是什麼意思,在BO層中所承擔的角色也比較明確,唯獨這個值對象有點坑爹。遙想當年我在使用C#的時候,裡面有一個值類型,與別人討論的時候經常會把這個東西搞混,就我現在寫東西還下意識把“值對象”寫成“值類型”呢。《實現領域驅動設計》書中針對值對象給了大概8類特性概括,如下圖所示。不過要我說,也就那麼有限幾點值得註意的。如果從編程的角度來看,所謂的值對象其實也很普通,所以讓我們以白話的形式盤盤它。

一、特性

  以我來看,最難的地方就是值對象與實體類型在建模時候的決擇。有些對象可以是實體,也可以是值對象,反正你怎麼想都有理。很多初學者包括我自己也時常搞迷惑,之所以出現這種問題以我現在的經歷來看有三個原因:一是在建模的時候偏離了領域驅動的約束而以資料庫為參照,畢竟我們在上學的時候學習編程都是從資料庫驅動開始的。資料庫要求每一個表都應該有個主鍵,而有的時候值對象需要單獨存在一個表中,這種情況就容易造成把值對象看成實體。其實開始設計的時候是想著圍繞著領域來搞的,不過做著做著就跑偏了,這是由於潛意識造成的偏差。第二、很多程式員您別看一說理論的時候都能夠口吐芬芳,但並沒有真正搞明白到底值對象是乾什麼的,它的本質到底是什麼,為什麼DDD先驅要特意提出這樣一個概念,沒有理論基礎在實踐中走彎路很正常;最後一個原因是程式員在設計的時候目光過於片面,只關註於眼前的需求,有些模型從當前的需求點上去看的確是值對象,如果轉而從大業務流程的角度去觀察,從全局的角度去考慮,事情就會有變化,這涉及到工作方式的變更,我感覺沒有解只能靠自己去悟了。對了,經常有人問上面的圖是怎麼畫的:PPT,還湊合吧?

二、詳解

  廢話不多說,讓我們先從值對象的特征搞起。首先先說這東西的主要作用是什麼,您不要一看上圖那一堆的圓圈就頭暈,是我在公司內部講解PPT時用的,主要以唬人為主。在這裡就不能這麼玩兒了,咱們得是小衚衕里趕豬——直來直去。“構成某物——我認為這是值對象最為重要的特性。這麼說吧,90%的情況下是由於這個原因才把一個模型設計為值對象的。既然是構成,他就會依附於所構成的目標對象。比如電商購物網站的訂單,一般會包含價格信息、付款信息、收貨信息,如下代碼所示。

public class Order extends EntityModel<Long> {
    private string 編號;
    private string 收件人姓名;
    private string 收件人電話;
    private string 收件人所在區;
    private string 收件人所在街道;
    private string 收件人所在街道;
    private BigDecimal 總價;
    private BigDecimal 優惠價;
    private Integer 支付方式;
    private BigDecimal 支付金額;
}

  上面的代碼把所有的屬性都定義為原始類型,這樣設計實體其實也沒有什麼問題,還挺直觀的。不過有些面向對象專家就感覺非常的不爽:這種設計缺少抽象能力和麵向對象精神,他看著彆扭。而且呢,這樣的設計會造成其所屬對象比如這裡的“Order”所承受的責任有點重,假如我想知道一個訂單的待支付價格(待支付價是在總價與優惠價中選擇一個小的),就需要把這個方法寫到“Order”里,如果抽象出一個價格信息對象呢?就可以讓它來負責處理本條業務,那“Order”對象的責任也自然就減輕了。我性子急,既然專家認為上述代碼不理想,就讓我們跟隨面向對象專家的思路,把一些相對內聚的元素組合起來成為單獨的類,比如價格信息、支付信息和客戶信息等。

public class Order extends EntityModel<Long> {
    private string 編號;
    private Customer 客戶信息;
    private Price 價格信息;
    private Payment 支付信息;
}

public class Customer {
    private Contact 聯繫人信息;
    private Address 地址信息;
}

public class Contact {
    private string 收件人姓名;
    private string 收件人電話;
}

public class Address {
    private string 收件人所在區;
    private string 收件人所在街道;
    private string 收件人所在街道;
}

public class Payment {
    private Integer 支付方式;
    private BigDecimal 支付金額;
}

public class Price {
    private BigDecimal 總價;
    private BigDecimal 優惠價;
    
    public BigDecimal getFinalPrice() {
        return min(總價,優惠價);
    }
}

  這段代碼已經足夠OO了,如果想再追求極致可以把“價格”類中兩個屬性的類型從“BigDecimal”變為一個自定義類型比如“Money”。讓我們再看看“Order”的現狀,裡面的大部分的屬性的類型已經從基本類型變成了自定義的類,實例化後當然就是對象了。DDD給這些對象一個新的名字:值對象。回過頭來再看本文最上面的圖,您會發現好多東西全是廢話。“構成某物”,這還用解釋?沒封裝成值對象前也是“Order”的構成要素,多封裝一層後本質也沒什麼變化啊;“無標識符”,廢話!原來也沒有啊,都是依附於訂單的,只要訂單有ID就行了,後面你完全可以根據訂單找到那些值對象;“概念整體”,有點腦子都知道啊?第二個版本的代碼就是通過把一些內聚的元素封裝為一個個值對象的,對象不是整體是什麼?所以說學習值對象的時候你得去做一個像我這樣的推導,才會發現別看說得熱鬧,也不過如此。

  所謂的“修飾某物”,就是說值對象通常用於描述他所在的實體或值對象,一般會作目標對象的屬性而存在,並不會孤立的存於世上。比如上面的“價格”值對象,是用來修飾訂單的,脫離訂單談價格沒有意義。這個特性可讓幫助您在設計時決策一個對象是否應該被作為值對象看待。綜合“構成某物”特性,我們就會發現值對象在其生命周期中需要依附於某物且只屬於某物,不存在被共用的情況。拋開訂單談論“支付信息”或“客戶信息”我感覺就是在搞笑;一個客戶可能會下多個訂單,但每個訂單都包含一個獨立的客戶對象,它不會跨訂單共用。其實,通過這兩種特性,已經可以幫助您在99%的情況下決策一個對象到底是實體還是值對象了,說“決策”都誇張了點,應該是可以輕易的判別了。不過文章寫到這份肯定不行,不夠深入啊。我們還是要著重解釋值對象的“概念整體”特征,這個涉及值對象的本質,只有瞭解這些您才知道為什麼在使用值對象的時候有許多的約束比如“不可變性”,也可以幫助你理解值對象的內涵並推導出值對象的其它特征。

  通過前面的代碼案例可知我們通過把一些概念內聚的屬性組合在一起形成了值對象。這說明瞭什麼?既然是有意的合併在一起,你就應該視值對象為一個整體!整體的意思就是不可分割(如果還能分割,您當初所做的合併就等於白乾,工作自相矛盾),比如您去菜市場買龍蝦,你跟老闆說只想買肉不買皮,你說他會不會砍你?有了“整體”概念或約束作為前提,你在值對象上的任何操作都必須以整體為導向,必須始終把它當成一個整體來看待。比如你想修改值對象的某個屬性值,就整另外一個值對象把原來的替換掉而不是單獨修改那個屬性,這叫整體替換。

  為什麼要這樣?值對象也是一種領域模型,也要始終保持其內部業務規則的一致性(這個叫對象的不變性)。為了達到這個目的,我們需要對對象的屬性做各種驗證,需要在執行某個方法時判斷此操作是否會打破規則限制,實體對象我們是這樣做的,非常麻煩。而引入值對象的一個初衷之是為了簡化對象的使用,反正我所有的方法都不會修改屬性,根本不需要做任何判斷。如果我放開修改限制,非常容易造成對象的變質。比如聯繫人信息,姓名“張三”+電話“123321”在我構建這個值對象時已經驗證過是合法的。如果允許修改單個的屬性,您把電話變成了“ABC”,造成了人是張三但電話是李四的,小心人家投訴你打騷擾。假如只有少量的實體和值對象,您並不會從此受多大的益。一個系統中數以百計的對象,我都不多說比如100個,其中90個是值對象,這個比例比較合理。由於值對象先天的不可變性,您寫代碼時不用那麼多的約束判斷,這得省多大事兒?這麼說吧,值對象越多,你受益越大。

  我們其實也可以把值對象想成一個Java中的Long類型的數據,您總不可能在修改它的時候只修改高32或低32位吧?要不就不修改,要修改就全部修改。好了,有了這樣一個概念作為前提,那麼我們就可以推導出以下四點。

  • 在設計值對象的時候不應該有“setter”方法來支持部分屬性值的修改,只能通過構造函數進行全屬性賦值;
  • 判等的時候,應該是每個屬性值都相等才能算兩個值對象是相等的,你可以把值對象想象成由幾個原始類型屬性組成的,判等肯定要比較每一個屬性;
  • 值對象可以包含豐富的業務方法包括命令型的,但業務方法不應該修改值對象的某個屬性值;
  • 修改屬性時只能通過整體替換。

  上述四點正好可以對應開篇圖中所說的“不可變性”、“屬性判斷”、“無負作用”和“替換性”。

  寫到此,我們做一下總結:“構成某物”和“修飾某物”兩個性質幫助我們決策一個對象是實體還是值對象;“概念整體”決定了值對象在設計和操作時所要遵循的規範(參考上一段所論述的四點)。至於無標識符,這個沒什麼可談的。值對象需要依附於某個實體而不能獨立存在,所以你給他標識符也沒個卵用,要追蹤某個值對象只要通過其所屬的實體。在總結了值對象的特性後,我們來說一下使用值對象到底有哪些好處。

  好處一:簡單;由於您不能單獨修改值對象的某個屬性,也就不用加上那麼多的約束和驗證,反正只有查詢操作怎麼玩也不會壞的。驗證的責任在實體對象構造的時候都處理過了,我們就不用再費二次的力氣處理這些;好處二:更符合面向對象精神。通過把業務方法拆到值對象中能減少實體設計的複雜度並讓代碼看起來更加符合單一責任原則 。下麵我寫了兩段代碼,您看看哪個更好。

//訂單狀態
public class Status {
    WAITING_PAY, PAIED, COMPLETED;
    
    public boolean canPay() {
        return this == Status.WAITING_PAY;
    } 
}

public class OrderA extends EntityModel<Long> {
    private Status status;
    
    public boolean canPay() {
        return status == Status.WAITING_PAY;
    }
}

public class OrderB extends EntityModel<Long> {
    private Status status;
    
    public boolean canPay() {
        return status.canPay();
    }
}

  我把“訂單狀態”封裝為一個實體對象“Status”,方法“canPay”在實現時:類“OrderA”中實現了具體的邏輯;類“OrderB”中由“Status”來代理。如果邏輯很簡單或狀態枚舉很少,這兩種寫法沒有區別。就怕訂單狀態枚舉特別多的時候,由值對象自已完成相應的邏輯更優雅,復用度也會更高。這叫“知識專家”原則,即哪個對象擁有完成一個業務邏輯所需的知識就把責任放在哪個對象上面。

  關於值對象的修改,這裡有一個小技巧分享。當需要修改某個值對象的時候,最好別讓客戶直接以“New”的方式來構建值對象再傳入到方法里;相反,您可以將這個值對象所需要的屬性以參數的形式傳入到方法中,在方法內部包裝成值對象後再將原值對象替換掉,這樣可以隱藏值對象的創建過程,如下代碼片段所示。

public class Order extends EntityModel<Long> {
    private PaymentDetail paymentDetail;
    
    public void pay(String payer, BigDecimal payment) {
        this.paymentDetail = new PaymentDetail(payer, Money.of(payment));
    }
}

final public class PaymentDetail extends ValueModel {
    private String payer;
    private Money payment;
}

   文章寫至這份兒上您應該明白了為什麼有人說在DDD落地代碼時,大部分的對象都最好設計成值對象了吧?也明白為什麼在設計對象的時候能使用值對象就不使用實體了吧?最起碼的好處是代碼更清晰、量更少、維護性更高。看起來顯得專業,人人都誇你心靈手兒巧。

三、存儲

  值對象的存儲其實也沒什麼可講的,主要是太簡單了。無怪乎是兩種:1)把值對象的值和實體放在同一張表中;2)把值對象放在單獨的表中。方式一相對簡單,就是把值對象內的各個屬性映射成和實體表在一起的欄位,如下圖所示。

 

  方式二,把值對象放到單獨一個表中,如下示例所示。需要註意的是示例中“審批環節”對象在存儲到資料庫後有了一個ID屬性。這其實不影響我們的設計,莫慌。在領域模型中值對象遵守了其設計規範,沒有標識;落到了資料庫中那就是數據層面的事兒了,在關係資料庫中每個表都有個ID這是資料庫本身的約束。再說了,有就有了唄,您又不用。不過說到這塊,我突然想起了一個特別典型的案例。下麵的案例您應該能看出來審批單和審批環節的關聯關係。我們以審批單ID為“A0001”的數據為例,在經過某些業務後,需要把審批環節表中ID為“N0001”行的狀態從“1”變成“2”。這怎麼搞?您在載入審批單對象的時候肯定要把兩個審批環節信息一同載入,載入後審批環節是個值對象,它都沒有ID的屬性,更別提“N0001”了。此種情況要怎麼解?把值對象變成實體?

  兩種思路:一是在更新審批環節前,把資料庫中存在的數據拿出來和待持久化的信息做對比,有變化的就是要變更的,自然就可以獲取到ID屬性了。這種方法有點扯,麻煩不說有時候甚至是不好靠譜,也就是說你根本比不了,比如上面的案例就不行。再假如在審批環節表中再加一個欄位“meta”,裡面存儲了JSON格式的審批元數據,現在我把JSON中的某個節點的值改變了,你怎麼比?字元串比較,別鬧……JOSN格式啊大哥,兩個節點的順序不同,但節點名和值都相同,您說他是不是相等的?什麼什麼?排序後再比較?我……算了吧,我們還是說方式二吧,有圖有真像。

  直接把舊值對象對應的數據刪除,再插入新的。感覺世界一下子清凈了,還費那個勁比較每個屬性,太不專業了。當然,上面例子僅出於演示作用,其實並不嚴謹,你還是需要提供一些關鍵屬性(比如審批環節+審批人ID)來幫助實體識別出待修改的值對象,這個關鍵屬性可不一定非得是ID,也就是完全不需要使用實體來替換原來的設計。

  存儲的時候,你的系統中如果有NoSQL資料庫也可以考慮去用一用。我在幾年前做過一個業務,當時設計了一組關係挺複雜的對象。持久化的時候使用了MySQL,其實當時也沒得選,按理放到MongoDB中最佳。這沒辦法,你也不能和運維去哭訴啊,加個中間件運維就需要投入更多的人力。結果,存儲特別費勁,其實如果只是花費點精力也還好,查詢才坑爹。這個案例不太好講,總之呢如果以列的方式存儲對象屬性,不知道有多少列合適,因為對象屬性的數量是動態的,即使能做也會有好多的列為空值;如果按行存,查詢支撐不了。這個事情後來也沒辦法,只能是把複雜的搜索需求取消掉了。所以舉這個案例其實是想強調我們在存儲實體的時候要靈活一點,別隻認識MySQL,那東西也不是萬能的。平常多積累一些知識,關鍵時刻只有你應用的漂亮、得體,妥妥職場上最靚的仔。大家都崇拜強者,尤其是姑娘們,努力吧兄弟!

四、案例

  為了能把知識講透了,我這顆不安的心強烈要求我再多舉一些例子,它的建議我接受了,咱們不管乾貨稀貨索性就多整點。以後園子里評什麼敬業獎的時候,感覺得有一份歸我。其實也不是給我,是給我那顆敬業的心。Let's go……

  1、訂單與訂單項:最常見的案例,必須安排。訂單項肯定是值對象,你想看買了什麼東西就必須通過訂單進行導航。脫離了訂單的訂單項就是一個沒媽的孤兒,找不到存在的理由,可憐吶!

 

   2、賬戶與實名信息:太簡單了,不解釋。

 

   3、電商系統中的地址管理:引功能用於管理登錄賬號的不同送貨地址。地址對象在訂單中屬於值對象;在本場景中是實體,不買東西您還不讓我管理我的送貨地址?又沒吃你家大米!

 

  4、微博:必須是實體啊,要不然別人怎麼引用?

 

 

  5、論壇中的貼子與評論:你猜呢?我覺得評論算是一種特殊的帖子,他關聯了被評論的對象而貼子則不需要,本質上都是一種“被髮布的內容”。另外,修改貼子的時候不需要把評論一同編輯了;修改評論也不會影響貼子的狀態,兩者是一種弱關聯。錶面上看刪除了貼子其對應的評論也不復存在(此外應該用被隱藏更為合適),但帖子並沒有被一併刪除。我們在看評論的時候其實都是通過貼子導航過去的,這個案例中僅僅是由於貼子被刪除而失去了導航點,並不代表貼子與評論具有相同的生命周期,所以我猜這兩個都是實體。再說了,我還能針對評論再給出新的評論呢,你不把它當成實體合適嗎?

  6、角色與許可權:兩實體唄。許可權可以屬於多個角色,角色也包含多個許可權,多對多的關係。多對多的時候,兩邊肯定都是實體。下麵的圖只畫了一個方向的多重關聯,因為那是設計模型,設計模型中不應該存在多對多的情況。

 

總結

  這章字寫得有點多,本來一個沒什麼可講的東西我都能給它整齣花樣來,你能不服?不管怎麼著,有了這些知識,我相信您應該知道何為值對象以及怎麼使用了吧?等你寫面向對象的代碼多了,就會發現其實值對象真的在所有的對象中占了大多數,9:1都不誇張。再提醒一句:務必把值對象當成整體來看,從這個角度理解您會通透很多。

  寫完今天的這篇已經二十章了,可累壞了。不過好處也是大大的,能把自已所學與別人分享,這個過程讓人快樂與滿足。讓我們繼承往前行……對了,屏幕前的你也要多多努力啊,去成全自己的夢想。

附:

  在走查本文的時候,發現了一個細節沒有重點說明:值對象除了不能有標識符(ID)和不能修改屬性外,其實和實體是一樣的,是可以有業務方法的,您可千萬別把值對象當成DTO來用,這也是一個充血模型呢。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文將引導你如何使用ZEGO Flutter SDK 快速輕鬆的構建一個跨平臺音視頻聊天應用,減少開發成本。 ...
  • 最近,有朋友私信讓我就`git 使用`做篇文章分享,分享一下我在日常工作中是如何使用`git`的。我當場就收費兩包辣條,最後討價還價,朋友用1.5包辣條騙到了這篇文章,等他欣喜的走了我打算直接分享出來,氣死這個吝嗇鬼,當然最終還是希望本文對你有所幫助。 ...
  • 用戶流失了,觸達難? 活動做了那麼多,轉化仍然很低? 運營也需要提前思考,預測用戶動向,提前精準觸達,才能事半功倍。結合HMS Core分析服務的預測服務和智能運營,洞察營銷時機,實時落地營銷策略,提升用戶運營效率。 預測服務擁有精準預測模型和深度人群洞察,支持查看近一周的預測準確率,幫助運營者做出 ...
  • flex三連問,幫助我們更好的理解佈局利器 問題: flex的值 auto, none, 0, 1, initial分別是什麼?有什麼作用?有什麼表現? flex-basis和width的區別?單值flex-basis:0與auto的區別?flex-basis:100px與width:100px一樣 ...
  • 前言 小學數學老師教過我們,0.1 + 0.2 = 0.3,但是為什麼在我們在瀏覽器的控制臺中輸出卻是0.30000000000000004? 除了加法有這個奇怪的現象,帶小數點的減法和乘除計算也會得出意料之外的結果 console.log(0.3 - 0.1) // 0.1999999999999 ...
  • 註意如果你的mac是M1處理器 那抱歉當前文章可能不支持了,因為當前模擬器不支持。 3步完成mac uniapp 模擬器配置 1.下載網易mumu模擬器 https://mumu.163.com/mac/index.html 2.安裝 設置 下載完成後安裝運行就是這樣的 選擇屏幕旋轉 手機模式 3. ...
  • 具體示例 //代碼 console.log(JSON.stringify({ x: 5, y: 6 },null,2)); //輸出結果 { "x": 5, "y": 6 } JSON.stringify() 介紹 JSON.stringify()方法將一個JavaScript對象或值轉換為JSON ...
  • 原型模式不是通過new生成新的對象,而使通過複製進行生成; 原型模式適用於相同類型的多個對象的生成; 原型模式分為兩種:淺克隆/淺表副本(Shallow Clone)和深克隆/深表副本(Deep Clone); 淺克隆:Shallow Clone,只複製值類型變數,不複製引用類型變數的克隆;(只複製 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...