DDD學習與感悟——向屎山衝鋒

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/12/25/17925655.html
-Advertisement-
Play Games

落地DDD是一件很困難的事情。首先在思想認知層面就比較難以突破。這篇文章記錄我對DDD的學習、感悟與項目工程代碼重構實戰心得! ...


軟體系統是通過軟體開發來解決某一個業務領域或問題單元而產生的一個交付物。而通過軟體設計可以幫助我們開發出更加健壯的軟體系統。因此,軟體設計是從業務領域到軟體開發之間的橋梁。而DDD是軟體設計中的其中一種思想,旨在提供一種大型複雜軟體的設計思路和規範。通過DDD思想可以讓我們的業務架構、系統架構、部署架構、數據架構、工程架構等都具備高擴展性、高維護性和高測試性。

但是落地DDD是一件很困難的事情。首先在思想認知層面就比較難以突破。

DDD本身是一種思想,不是某種具體的技術,因此在代碼實現和系統架構層面沒有約束。而由於市面上成熟的ORM框架(比如hibernate、mybatis等),使得大部分軟體開發都是直接面向資料庫開發。在傳統開發中的應用分層架構又和DDD思想的分層架構很類似。從而導致很多人在初學DDD時有一定的理解偏差,從而導致無法落地DDD思想。

這篇文章記錄我對DDD的學習、感悟與項目工程代碼重構實戰心得!

一、Domin Primitive

領域“元數據”的意思。主要是講解領域的基本準則。這也是使用DDD思想的基本準則。

1.1 隱性的概念顯性化

exp:電話號碼通常是由區號編碼+號碼組成。在實際的業務中會有很多需要電話號碼的業務。比如登錄認證、導購分銷等業務;我們需要對電話號碼進行基礎性校驗;獲取區號編碼等;在常規操作下,會在每一個用到電話號碼的方法入口都會寫大量的這種校驗代碼和判斷代碼,儘管我們可以將它的校驗和獲取區號編碼抽離成util類(實際上大多數工程中都是這麼做的),但這種方式治標不治本。基於DDD思想可以發現這裡有一個隱性概念:區號編碼。

我們可以基於DDD思想,將電話號碼創建為一個擁有獨立概念和行為的值對象:PhoneNumber,將基礎性校驗和獲取編碼等無狀態行為封裝在值對象中。這樣在方法中就不需要再充斥著寫大量的校驗和判斷。

1.2 隱性的上下文顯性化

exp:在銀行轉賬場景中,通常我們會說A賬戶給B賬戶轉1000元。這裡的1000元實際上有兩層含義,數字1000,貨幣元。但我們通常會忽略貨幣單位元。導致在實現轉賬功能時,沒有考慮到單位。一旦有國際轉賬時,就又會陷入到大量的if else中。

我們基於DDD思想,將錢創建為一個擁有獨立概念和行為的值對象:Money,這樣我們所說的錢才具備完整的概念。通過這種方式就可以將貨幣這個隱性上下文顯性化,從而避免當前未識別到但是未來可能會爆雷的bug。

1.3 封裝多對象行為

exp:在跨境轉賬的場景中,需要轉換匯率,我們可以將轉換匯率封裝成一個值對象。通過封裝金額計算邏輯和各種校驗邏輯,使得整個方法極其簡單。

1.4 DP和值對象的區別

DP是阿裡大神提出來的概念;值對象是DDD思想中的概念。

學習之後,我個人認為DP是對值對象的進一步補充,使其擁有了更加完整的概念。在值對象【不變性】的基礎上補充了【可校驗性】和【獨立行為】。當然也是要求【無副作用】。所謂的無副作用就是【狀態不可變】。

1.5 DP和DTO的區別

DTO DP
功能 數據傳輸對象,屬於技術細節 屬於領域中的業務概念
數據關聯性 不具備數據關聯性 數據之間有強關聯性
行為 無行為 擁有十分豐富的行為和業務邏輯

1.6 使用DP VS 不使用DP

不使用DP 使用DP
API介面清晰度 含混不清 方法簽名清晰易懂
數據校驗、錯誤處理 校驗邏輯分佈多個地方、大量重覆代碼 校驗邏輯內聚,在方法邊界外完成
業務代碼的清晰度 充斥大量膠水代碼,淹沒業務核心邏輯 代碼簡潔明瞭,業務邏輯一目瞭然
測試複雜度 TC數量:N_M_P(N個參數,每個參數M種校驗,有P個方法在調用) TC數量:N+M+P
其他好處 整體安全性大大提升、不可變性、線程安全

二、應用架構

2.1 DDD思想下的標準應用架構

傳統的MVC架構分為展現層、業務邏輯層和數據訪問層,更加註重從展現層到數據訪問層自上而下的交互,編寫出來的代碼像是腳本式代碼。

而基於DDD原則,工程架構被分為應用層、領域層和基礎設施層。將工程中不同的功能和職責劃分到不同的層級中。核心的業務邏輯放在領域層中。

2.1.1 應用層

按照DDD的思想,應用層負責協調用戶界面和領域層之間的交互。可以通俗的認為是對領域服務的編排,其本身不包含任何業務邏輯。

2.1.2 領域層

領域層負責實現核心業務的邏輯和規則。按照DDD的思想,這一層包含實體模塊、值對象模塊、事件、領域服務。

2.1.3 基礎設施層

基礎設施層不處理任何業務邏輯,只包含基礎設施,通常包含資料庫、定時任務、MQ、南向網關、北向網關等。

2.2 我對演進出六邊形架構的理解

2.2.1 再談應用層

在實際業務邏輯當中,除了用戶界面層之外,還有其他外部系統會調用本服務,比如xxljob、MQ、或者提供給外部系統調用http或者rpc介面等。因此在實際當中,應用層應當是協調外部系統與領域層之間的交互。

按照標準架構層級依賴關係來看,應用層依賴了領域層和基礎設施層。由於依賴了基礎設施層,因此破壞了應用層本身的可維護性和測試性。因此我們需要基於介面進行依賴倒置。

為了防止領域概念外泄,需要對應用層進一步的抽象為外部服務和內部服務,所有外部服務必須通過內部服務調用領域層。這樣就可以防止領域模型的外泄。

2.2.2 再談領域層

同樣的,按照標準架構層級依賴關係,領域層依賴基礎設施層,但這也破壞了領域層本身的可維護性和可測試性。因此我們基於DDD中的資源庫思想,抽象repository層,通過介面實現依賴反轉。讓領域層不再依賴基礎設施層。從而提高領域層本身的可維護性和可測試性。

2.2.3 再談基礎設施層

對於基礎設施層而言,它主要作用是提供基礎設施的能力,比如資料庫、MQ、遠程服務調用等。進一步抽象可以發現它們就是埠和適配器。通過埠實現與外部系統的交互,通過適配器完成數據和概念的轉換。

2.2.4 演進出六邊形架構

通過依賴反轉,神奇的事情發生了。基礎設施層變成了最外層。

我們結合對應用層、領域層和基礎設施層進一步的理解再加上反轉後的應用架構,便可以得到六邊形架構:

2.3 工具類、配置類的代碼應該放在哪裡?

在一個實際的工程當中,除了上面所說的三層之外,通常會使用到一些工具類(JSON解析工具類、字元串工具類等)。各層可能都會使用到工具類。

從工具類的定位來看,它應當屬於基礎設施層,但是基礎設施層屬於最上層,如果放在基礎設施層,那麼就會破壞依賴順序。因此我們在邏輯劃分上可以把工具類歸類為基礎設施或者通用域,在具體的工程結構中,可以單獨一個模塊放工具類。

在實際工程中還有一種類型的代碼是配置相關的。從業務維度劃分的話可以分為業務類配置和基礎設施類配置。因此我們需要根據配置的類型將其放在對應的位置。比如為了靈活應對業務,我們通常會配置一個動態開關,來動態調整業務的邏輯,這種業務開關類的配置就應該放在領域層;再比如資料庫的配置屬於基礎設施配置,這類配置就應當放在基礎設施層。

2.4 我對於項目的六邊形架構的實踐

我們團隊做的的職責是業務底座,包含一系列的基礎能力建設。其中對於IDaaS系統而言,基於六邊形架構實現出以下工程結構:

三、repository模式

3.1 什麼是repository模式?

在DDD思想中,repository表示資源庫的概念,用於區分數據模型和領域模型。它操作的對象是聚合根,因此它屬於領域層。

3.2 為什麼要使用repository模式?

repository模式有兩個非常重要的作用:1、與底層存儲進行解偶;2、為解決貧血模型提供了一種規範。

3.3 什麼是貧血模型?

由於過去ER模型以及主流ORM框架的發展,讓很多開發者對實體的概念還停留在與關係形資料庫映射這個層面。從而導致實體只有空洞的屬性,而實體的業務邏輯散落各個service、util、helper、handler等各種角落中。這種現象就被稱為貧血模型現象。

如何判斷自己的工程是否有貧血模型現象?

1、大量的XxxDO或者Xxx:實體對象只包含與資料庫表映射的屬性,沒有行為或者及其少量的行為;

2、業務邏輯在各種service、controller、util、helper、handler中:實體的業務邏輯散落在不同層級、不同類、不同方法中,相似場景有大量的重覆代碼。

3.4 為什麼貧血模型不好?

無法保證實體對象的完整性和一致性:貧血模型下,實體屬性的狀態和值只能由調用方保證,但是屬性的get和set是公開的,因此所有調用方都可以調用。所以無法保證對象的完整性和一致性。

操作實體對象的邊界很難發現:由於對象只有屬性,屬性的邊界值、調用範圍不受實體自身控制,各個地方都可以調用,邊界值和範圍也只能由調用方自行保障。如果實體的邊界值有所變化,那麼所有調用方都需要調整,這種情況下很容易導致bug的產生。

強依賴底層:貧血模型下的實體和資料庫模型映射、協議等。因此如果底層改變,那麼上層邏輯需要全部跟著改變。“軟體”變成了“固件”。

總結一句話:貧血模型下,軟體的可維護性、可擴展性、可測試性極差!

擴展:
軟體的可維護性=底層基礎設施變化時,需要新增/修改的代碼量是多少(越少可維護性越好)
軟體的可擴展性=新增或變更業務邏輯時,需要新增/修改的代碼量是多少(越少可擴展性越好)
軟體的可測試性=每條TC執行的時長 * 新增或變更業務邏輯時產生的TC(時長越低/TC越少,測試性好)




3.5 實際情況中,為什麼貧血模型難以消滅?

1、資料庫思維

隨著ER和ORM框架的發展,讓多數開發者在剛入門的時候(自學、培訓等方式),就認為實體就是資料庫表映射;從而簡單的將面向業務領域開發轉變成了面向資料庫開發,漸漸地就認為軟體開發就是CRUD。

2、簡單

儘管有些架構師或者開發人員知道貧血模型不好,但是企業為了占領市場,需要快速推出產品。因此工期被壓縮的很厲害。而貧血模型恰好簡單,在軟體初期階段,可以快速實現業務邏輯。從而迫使開發人員不得不“先實現了再說”。這種現象也是行業的普遍現象。

3、腳本思維

有些開發人員具備一定的抽象思維,將一些共性的代碼寫成util、helper、handler等類。但寫代碼依然是腳本思維。比如一個方法中,先來個欄位校驗代碼,再來個對象轉換代碼,然後調用遠程服務,對遠程服務返回的結果再來個對象轉換,……最後調用Dao類的方法保存對象。這種代碼在很多工程中太常見了。

基於這些因素,導致貧血模型難以消滅。

這些因素的根本原因是什麼?

根本原因就是,大部分的開發人員混淆了數據模型和領域模型這兩個概念。

數據模型(Data Model):數據模型解決的是數據如何持久化、如何傳輸的問題;

領域模型(Domin Model):領域指的是某一個獨立的業務領域或者問題空間,領域模型就是解決這個業務領域或者問題空間而設計的模型;解決的是業務領域的問題。

在DDD中,repository就是用於區分數據模型和領域模型提出來的概念。

3.6 使用repository之後,數據模型和領域模型如何轉換?

使用repository之後,數據模型和領域模型都各司其職。通過Assembler和Converter進行模型之間的轉換。

在代碼中,動態轉換映射 VS 靜態轉換映射

雖然Assembler/Converter是非常好用的對象,但是當業務複雜時,手寫Assembler/Converter是一件耗時且容易出bug的事情,所以業界會有多種Bean Mapping的解決方案,從本質上分為動態和靜態映射。

動態映射方案包括比較原始的 BeanUtils.copyProperties、能通過xml配置的Dozer等,其核心是在運行時根據反射動態賦值。動態方案的缺陷在於大量的反射調用,性能比較差,記憶體占用多,不適合特別高併發的應用場景。而BeanUtils等copy類工具隱藏了內部copy的過程,很容易引發bug且不易排查。

MapStruct通過註解,在編譯時靜態生成映射代碼,其最終編譯出來的代碼和手寫的代碼在性能上完全一致,且有強大的註解等能力。會節省大量的成本。

3.7 代碼層面模型規範和比較

DO Entity DTO
命名規範 XxxDO Xxx XxxDTO/XxxRequest/XxxVO/XxxCommand等
代碼層級 基礎設施層 領域層 應用層
欄位名稱標準 於資料庫欄位保持一致 業務語言 和調用方商定
欄位類型標準 和資料庫欄位保持一致 根據業務特征確定事基礎類型還是值對象 和調用方商定
是否需要序列化 不需要 不需要 需要
轉換器 Assembler Assembler/Converter Converter

3.8 代碼層面repository規範

1、介面名命名規範

repository中的介面名不要使用底層存儲的名稱(insert、update、add、delete、query等),而是儘量使用具有業務含義的命名。比如save、remove、find等。

2、介面的參數規範

repository操作的對象是聚合根。因此只能操作聚合根或者實體。這樣才能屏蔽底層的數據模型,避免數據模型滲透到領域層。

四、領域層設計規範

4.1 實體類

大多數DDD架構的核心都是實體類,實體類包含了一個領域里的狀態、以及對狀態的直接操作。Entity最重要的設計原則是保證實體的不變性(Invariants),也就是說要確保無論外部怎麼操作,一個實體內部的屬性都不能出現相互衝突,狀態不一致的情況。

4.1.1 創建即一致

constructor參數要包含所有必要屬性,或者在constructor里有合理的預設值。

4.1.2 使用Factory模式來降低調用方複雜度

由於創建即一致的原則,導致實體的構造方法可能會很複雜,因此可以使用Factory模式來快速的構造出一個新的實體。降低調用方的複雜度。

4.1.3 儘量避免public setter

一個最容易導致不一致性的原因是實體暴露了public的setter方法,特別是set單一參數會導致狀態不一致的情況。如果需要改變狀態,儘量語義化方法名稱。

4.1.4 通過聚合根保證主子實體的一致性

通常主實體會包含子實體,這時候主實體就需要起到聚合根的作用,即:

  • 子實體不能單獨存在,只能通過聚合根的方法獲取到。任何外部的對象都不能直接保留子實體的引用

  • 子實體沒有獨立的Repository,不可以單獨保存和取出,必須要通過聚合根的Repository實例化

  • 子實體可以單獨修改自身狀態,但是多個子實體之間的狀態一致性需要聚合根來保障

exp:常見的電商域中聚合的案例如主子訂單模型、商品/SKU模型、跨子訂單優惠、跨店優惠模型等。




4.1.5 不可以強依賴其他聚合根實體或領域服務

一個實體的原則是高內聚、低耦合,即一個實體類不能直接在內部直接依賴一個外部的實體或服務。

對外部對象的依賴性會直接導致實體無法被單測;
以及一個實體無法保證外部實體變更後不會影響本實體的一致性和正確性。




正確依賴外部的方式

只保存外部實體的ID:這裡我再次強烈建議使用強類型的ID對象,而不是Long型ID。強類型的ID對象不單單能自我包含驗證代碼,保證ID值的正確性,同時還能確保各種入參不會因為參數順序變化而出bug。

針對於“無副作用”的外部依賴,通過方法入參的方式傳入。比如上文中的equip(Weapon,EquipmentService)方法。

4.1.6 任何實體的行為只能直接影響到本實體(和其子實體)

這個原則更多是一個確保代碼可讀性、可理解的原則,即任何實體的行為不能有“直接”的”副作用“,即直接修改其他的實體類。這麼做的好處是代碼讀下來不會產生意外。

另一個遵守的原因是可以降低未知的變更的風險。在一個系統里一個實體對象的所有變更操作應該都是預期內的,如果一個實體能隨意被外部直接修改的話,會增加代碼bug的風險。

4.1.7 可以利用enum來代替繼承關係,後續也可以利用Type Object設計模式來做到數據驅動

4.2 領域服務

當一個業務邏輯需要用到多個領域對象作為輸入,輸出結果是一個值對象時,就說明需要使用到領域服務。

4.2.1 單對象策略型

這種領域對象主要面向的是單個實體對象的變更,但涉及到多個領域對象或外部依賴的一些規則。

在這種類型下,實體應該通過方法入參的方式傳入這種領域服務,然後通過Double Dispatch來反轉調用領域服務的方法。

什麼是Double Dispatch

exp:對於“玩家”實體而言,有一個“equip()”裝備武器的方法。
按照常規思路,“玩家”實體需要註入一個EquipmentService,然而實體只能保留自己的狀態,
除此之外的其他對象實體無法保證其完整性,因此我們不通過註入的方式使用EquipmentService;
而是通過方法參數引入的方式來使用。即“玩家”實體的"equip()"方法定義為:
public void equip(Weapon weapon, EquipmentService equipmentService) {
	if(equipmentService.canEquip(this, weapon)) {
			this.weaponId = weapon.getId();
	}
}
這種方式就稱為Double Dispatch方式。
Double Dispatch是一個使用Domain Service經常會用到的方法,類似於調用反轉。




4.2.2 跨對象事務型

當一個行為會直接修改多個實體時,不能再通過單一實體的方法作處理,而必須直接使用領域服務的方法來做操作。在這裡,領域服務更多的起到了跨對象事務的作用,確保多個實體的變更之間是有一致性的。

4.2.3 通用組件型

這種類型的領域服務提供了組件化的行為,但本身又不直接綁死在一種實體類上。他的好處是可以通過組件化服務降低代碼的重覆性。

介面組件化來實現通用領域服務

exp:在游戲系統中,原價、NPC、怪物都是可移動的。因此可以設計一個Movable介面,
讓玩家、NPC、怪物實體實現Movable介面。然後再實現一個MoveService,從而實現一個移動通用服務。




4.3 策略對象(Domain Policy)

Policy或者Strategy設計模式是一個通用的設計模式,但是在DDD架構中會經常出現,其核心就是封裝領域規則。

一個Policy是一個無狀態的單例對象,通常需要至少2個方法:canApply 和 一個業務方法。

canApply方法用來判斷一個Policy是否適用於當前的上下文,如果適用則調用方會去觸發業務方法。
通常,為了降低一個Policy的可測試性和複雜度,Policy不應該直接操作對象,而是通過返回計算後的值,
在Domain Service里對對象進行操作。




4.4 副作用的處理方法 - 領域事件

什麼是副作用?

“副作用”也是一種領域規則。一般的副作用發生在核心領域模型狀態變更後,同步或者非同步對另一個對象的影響或行為。比如:當用於積分達到100時,會員等級升1級。

在DDD中,解決“副作用”的手段是領域事件。通過EventBus事件匯流排可以實現領域事件的傳播。

目前領域事件的缺陷和展望

由於實體需要保證完整性,因此不能夠直接依賴EventBus,所以EventBus只能保持全局singleton。但是全局singleton對象很難被單測,這就容易導致Entity對象很難被完整單測覆蓋全。

五、寫在最後

通過對於DDD的學習與實踐,越來越能夠體會到它作為一種軟體設計思想和指導,對於大型複雜軟體的建設十分有幫助。對於歷史遺留屎山工程的重構也提供了一個很好的指導方向。

作者:京東科技 孫黎明

來源:京東雲開發者社區 轉載請註明來源


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

-Advertisement-
Play Games
更多相關文章
  • 本文分享自華為雲社區《【調優實踐】SQL改寫消除相關子查詢》,作者: 門前一棵葡萄樹 。 一、子查詢 GaussDB(DWS)根據子查詢在SQL語句中的位置把子查詢分成了子查詢、子鏈接兩種形式。 子查詢SubQuery:對應於查詢解析樹中的範圍表RangeTblEntry,更通俗一些指的是出現在FR ...
  • 翻譯自 Apache Paimon官方文檔 概覽 概述 Apache Paimon (incubating) 是一項流式數據湖存儲技術,可以為用戶提供高吞吐、低延遲的數據攝入、流式訂閱以及實時查詢能力。 簡單來說,Paimon的上游是各個CDC,即changlog數據流;而其自身支持實時sink與s ...
  • 如何在 WindowManager.addView 中使用 Jetpack Compose 一、引出問題 Android 開發中,很常見的一個場景,通過 WindowManager.addView() 添加一個 View 到屏幕上。Android 最新的視圖框架 Jetpack Compose,如何 ...
  • 註解的使用有助於減少樣板代碼的編寫,並提供了一種聲明性的方法來描述代碼的意圖和行為。可以用於實現依賴註入,資料庫映射、運行時許可權處理等功能。 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 聊聊圖片預載入 關於圖片的載入,不同的需求有不同的實現,比如圖片過多時候的懶載入,為了保證效果的預載入。 如何進行圖片的預載入 前端實現圖片的預載入,其實是利用了瀏覽器的緩存,我們通過 a 標簽來提前載入圖片,如下: const img ...
  • 上次我們說了,keycloak的login-status-iframe頁面的作用,並解決了跨域情況下,iframe與主頁面數據傳遞的方法,這一次,我們主要分析login-status-iframe.html這個文件的源碼,然後分析在我們系統中如何與這個頁面對接。 login-status-ifram ...
  • 一個複雜的 Chrome 擴展程式通常由 `content_scripts`,`background`,`action popup`,`side panel`,`options page`,`devtools` 等部分組成,這些部分所負責的功能各不相同,所處的運行環境各不相同,所能訪問的 `chro... ...
  • 一、要實現的效果(縱向固定表頭的表格,橫向表頭數量動態化) 二、這是後臺返回的數據格式(以企業為數組,每個企業里有個站點數組pointFactors) 三、代碼實現步驟 (1)定義縱向固定表頭 1 // 縱向表頭數組 tableColumns 2 const tableColumns = ref([ ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...