後臺開發進階:白話DDD從入門到實踐

来源:https://www.cnblogs.com/88223100/archive/2022/12/27/Advanced-background-development_vernacular-DDD-from-introduction-to-practice.html
-Advertisement-
Play Games

DDD(領域驅動設計)是 Eric Evans 於 2003 年提出的解決複雜的中大型軟體的方法,開始一直不慍不火。直到 Martin Fowler 於 2014 年發表的論文《Microservices》引起大家對微服務的關註,DDD 才重新慢慢的回到了大眾的視野中。 DDD 這幾年升溫的同時,... ...


 

 

嘗試用大家都能聽得懂的話,結合我們在增值業務中的具體實現,分享一下我們從入門到實踐 DDD 的一些心得。

0. 寫在前面的

DDD(領域驅動設計)是 Eric Evans 於 2003 年提出的解決複雜的中大型軟體的方法,開始一直不慍不火。直到 Martin Fowler 於 2014 年發表的論文《Microservices》引起大家對微服務的關註,DDD 才重新慢慢的回到了大眾的視野中。

DDD 這幾年升溫的同時,也收到了很多行業人員對 DDD 的負面意見,主要原因大概有“晦澀難懂過於抽象”、“很難找到實際的案例參考”、“不知道怎麼落地”等。

筆者在學習 DDD 的過程中,也遇到了這些問題。不過在經過幾個月的學習-實踐,逐漸掌握了 DDD 的一些思想後,感覺還是確實有所受益,所以這裡嘗試用白話去總結我們從入門到實踐的過程,儘量每一個概念都用我們的具體實現做出例子,希望能對想一起學習 DDD 的同事有所幫助。

 

1.一個維護中的業務系統引出的思考

我們後臺+前端大概 6-8 個開發同事這幾年一起維護了一個帶貨類的項目,這個項目我們用了最傳統的三層模型來搭建,大概是如下的模型:

圖片

當這個項目維護幾年之後,逐漸出些了一些有意思的情況,我挑選一些主要環節發現的代表性問題介紹下:

情況 1(代碼層面):少部分代碼可讀性在長期不同人員的修改下變得越來越差。如某個帶貨的核心 rpc 邏輯沒有任何嵌套平鋪在一個函數,單函數代碼行數達到幾百行,可讀性和維護性極差,成功化身為“技術護城河”。

情況 2(微服務層面): 某些微服務初始職能劃分較為簡單,導致少量模塊在後續快速的迭代中快速膨脹。如其中的 mp 模塊,原本職能是用來承接 B 端門戶的功能,當我們決定拆分這個龐大的模塊時,這個模塊已經承載了 204 個 rpc。過多的能力承擔讓它編譯變慢、變成鏈路單點、改動較多、一旦出現問題影響較大。

情況 3(業務團隊層面):帶貨項目會使用一些其他業務系統的介面和數據結構,當這些業務系統想要修改這些介面和數據結構的時候 ,偶爾可能沒有察覺這裡的依賴導致線上問題, 或者溝通過來發現耦合處比較多不容易改動。

對這個項目的維護引出了我們一些思考,在一個複雜業務系統中:代碼結構要如何設計、微服務的橫/縱向職能要如何劃分、業務團隊之間如何交互,才能持續在長期快速、多人協作的迭代中保證系統可維護性、拓展性、高內聚低耦合和穩定性。

而傳統的開發模式不管是面向過程(POP)還是面向對象(OOP)的思維,都沒辦法從微服務層面指導我們找到這些問題的答案。大概有兩種方法解決這個問題:

1)尋找一個總是有時間、總能做出正確決策的中心節點同事,介入每一處全局/細節的設計並統一做出決策。2)尋找一個新的規則/規範來做指導,讓每一位開發都能有做出正確決策的依據。在 Tencent 的氛圍和環境中,2)無疑是更合理的,所以我們想到了領域驅動設計(DDD)。

 

2.DDD 的分層架構

DDD 最有標誌性的一點,就是將傳統軟體設計三層模型轉化為了四層模型,這個轉化如下圖所示:

圖片

乍看之下,四層架構引入了很多概念,如領域服務、領域對象、 DTO、倉儲等等。我們先不用在意這些細節概念,因為下一節我們會逐個分析併列舉我們的實現例子。我們先關註這幾個關鍵的層:用戶界面層、應用層、領域層、基礎設施層。我們來看下他們的職能分工:

用戶界面層:網路協議的轉化/統一鑒權/Session 管理/限流配置/前置緩存/異常轉換

應用層:業務流程編排(僅編排,不能存在業務邏輯)/ DTO 出入轉化

領域層:領域模型/領域服務/倉儲和防腐層的介面定義

基礎設施層:倉儲和防腐層介面實現/存儲等基礎層能力

這裡必須要說的是,這四層不一定是指物理四層,也可以在一個微服務中拆分邏輯四層。四層架構有很多變種,如六邊形架構、洋蔥架構、整潔架構、清晰架構等等。這些繁多的概念我們這裡不過多討論,僅以洋蔥架構為例,著重強調 DDD 中的依賴倒置(DIP),以便後面更容易介紹倉儲/防腐層等概念。

依賴倒置(DIP):
1.高級模塊不應依賴於低級模塊。兩者都應依賴抽象。
2.抽象不應依賴細節。細節應依賴於抽象。
圖片

如上,洋蔥架構越往裡依賴越低,越是核心能力。基礎設施層在最外面,依賴其他層,這是是因為 DDD 中其他層等需要定義自己需要的基礎能力介面,而基礎設施層負責依賴並實現這些介面,從而實現整體依賴倒置。這體現了 DDD 的由全局入細微、自頂層向下層的設計思維。

3.DDD 的概念和實踐

1)戰略和戰術

DDD 的落地過程,其實就是戰略建模戰術建模

戰略建模,是指:通過 DDD 的理論,對業務需求進行拆解分析,劃分子域,梳理限界上下文,通過領域語言從戰略層面進行領域劃分以及構建領域模型。並且在在構建領域模型的過程中梳理出業務對應的聚合、實體、以及值對象。

戰術建模,是指:以領域模型基礎,通過限界上下文作為服務劃分的邊界進行微服務拆分,在每個微服務中進行領域分層,實現領域服務,從而實現領域模型對於代碼映射目的,最終實現 DDD 的落地實施。

圖片

當然,戰略和戰術的建模除了要考慮業務形態,還要考慮到組織架構,就如同康威定律中的表達,溝通架構會影響技術架構

康威定律:任何組織在設計一套系統(廣義概念上的系統)時,所交付的設計方案在結構上都與該組織的溝通結構保持一致。

2)領域

DDD 在解決複雜的問題的時候,使用的是分而治之的思想。而這個分而治之的思想,就是從領域開始,一個領域就是一個問題空間,而我們在拆分這個問題空間的時候,也就是在劃分子領域和尋找它的解系統的過程。

實踐例子:

如我們某個新的增值業務,就是看成是的大的增值業務域,接下來我們通過 DDD 來指導拆分它。

圖片

3子域

如果一個領域太大太複雜,涉及到的業務規則、交互流程、領域概念太多,就不能直接針對這個大的領域進行建模。這時就需要將領域進行拆分,本質上就是把大問題拆分為小問題,把一個大的領域劃分為了多個小的領域(子域)。

子域可以分為三類:

核心子域:業務成功的核心競爭力。

通用子域:不是核心,但被整個業務系統所使用 。

支撐子域:不是核心,不被整個系統使用,完成業務的必要能力。

子域的劃分除了分治了大的問題空間,也劃定了工作的優先順序。我們應該給予核心域最高的優先順序和最大的資源。在實施 DDD 的過程中,我們也是主要關註於核心域。

實踐例子:

子域的劃分,需要比較強的業務知識和產品研發集體討論,準確和深入的業務見解在這一階段尤為重要。這裡我們不對業務知識深入討論,僅展示下我們的對增值業務域的拆解結果。

圖片

這裡要說的是,套餐域在實現的過程中由於產品需求變化概念被廢棄了,但是由於我們的子域拆分,套餐域和其他域實現上沒有任何耦合,所以廢棄套餐域概念的廢棄就像拆掉一個積木一樣,對整套系統沒有任何影響,也不會遺留任何不必要的包袱代碼。

4)限界上下文

要理解限界上下文,首先要先介紹通用語言。通用語言是 DDD 非常重要的一點。比如商品這個概念,在商品域里是指備上架的商品, 包含了 id、介紹、文檔等。在交易域里其實是指訂單中被交易的實體,關註的是 id、成交時刻的售價等參數、成交數量。而如果不能明確這些概念和他們的關係就會讓開發人員的實現變的隨心所欲和模糊。

而限界上下文是就是劃分一個邊界,當領域模型被一個顯示的邊界所包圍時,其中每個概念的含義應該是明確且有唯一的含義。

我覺得初學者最常碰到的問題,肯定"明明已經有子域了,為什麼還會有限界上下文這個概念"。子域是一個子問題空間,而限界上下文的作用是指導如何設計這個問題空間的解系統。換句話說,限界上下文才是真正用來指導微服務劃分。一般來說一個子域對應一個或多個限界上下文。

劃分限界上下文可以參考如下的規則:1) 概念是否有歧義:如果一個模型在一個上下文裡面有歧義,就說明可以繼續拆分限界上下文。

2)外部系統:可以把與外部系統交互的那部分拆分出去降低外部系統對我們我們的核心業務邏輯的影響。

3)組織架構:不同團隊最好在不同的限界上下文裡面開發,避免溝通不順暢、集成困難等問題。可以參考上述"康威定律"。

實踐例子 1:

如上所述,商品這個概念,是需要用限界上下文在不同場景區分開的。當然這也會導致兩個限界上下文之間會有依賴。通過 DDD 的概念可以指導我們進行如下實現。

圖片

其中 gateway/gatewayimpl 是防腐層的實現,DTO 是指數據傳輸對象,APP 是指商品應用層。兩個不同顏色的商品是指兩個上下文中分別進行定義的不同的實體或值對象。

實踐例子 2:

交易域中,有兩個訂單的概念,其中第一個訂單的概念是指業務層訂單, 第二個訂單的概念是指內部基礎層訂單。業務訂單更關註發生交易的成交商品信息,這個訂單是用戶需要的。基礎層訂單更關註交易底層的過程信息,這個訂單更多是我們內部人員需要的,用戶不理解。

當時有個思路是想讓基礎層團隊的同學額外開發直接支持基礎層訂單存儲業務信息,這明顯是不符合 DDD 限界上下文劃分規則 1)和 3)的,是需要通過限界上下文解耦開的。所以我們在交易域中拆分兩個上下文,後續從微服務層面也是相互獨立的微服務,各自管理各自的領域實體和值對象。

圖片

5)防腐層

當兩個限界上下文相互調用的時候,需使用防腐層(ACL)來進行兩個限界上下文的隔離,並實現 value object 的轉換。避免不同上下文直接互相調用,不然一旦被調用上下文被修改則可能產生較大影響。

實踐例子:

實現鏈路可以參考 3.4 的例子 1,在商品域中,我們的防腐層是按照如下的目錄方式實現的, 領域層來定義領域層需要的防腐介面,基礎設施層繼承並實現防腐介面,在基礎設施層直接調用其他限界上下文。

productdomainsvr (商品限界上下文)
├── domain(領域層)
│   ├── aggregate
│   │   ├── spu.cpp                        //1)spu領域對象需要調用其他限界上下文生成id
│   │   └── spu.h
│   └── gateway
│       └── gen_id_gateway.h         //2)領域層定義調用其他限界上下文生成id的防腐介面
├── infrastructure(基礎設施層)
│   └── gatewayimpl
│      └── acl(防腐層)
│         ├── gen_id_gateway_impl.cpp //3)基礎設施層實現領域層定義的防腐介面,真實調用其他上下文
│         └── gen_id_gateway_impl.h

6)領域事件

兩個限界上下文除了通過使用防腐層直接調用,更多的時候是通過領域事件來進行解耦。

並不是所有領域中發生的事情都需要被建模為領域事件,我們只關註有業務價值的事情。領域事件是領域專家所關心的(需要跟蹤的、希望被通知的、會引起其他模型對象改變狀態的)發生在領域中的一些事情。

其實,領域事件的本質就是事件,我們常見的 kafka、wq 等都可以作為領域事件的實現基建。通過領域事件,可以把很輕鬆兩個限界上下文解耦

實踐例子:

在我們的增值業務中,交易域的"支付成功"就是一個領域事件,計費域訂閱這個領域事件,從而可以根據這個事件調整客戶的計費資源包實體。

圖片

可以想象,如果這裡沒有採用領域事件, 而是交易域直接調用計費域的 rpc 通知交易成功,那麼當後續有其他域需要接受“支付成功”這個事件,或者,計費域被調用的介面出現故障。都會讓交易域陷入麻煩,前者需要交易域不停的堆疊調用外部 rpc 的代碼並讓系統變得不穩定,後者則直接會讓計費域的故障影響到用戶交易。

7)實體/值對象

實體是指上下文中唯一的且可持續變化的基礎單元,在其生命周期中可以通過穩定的唯一 id 來標識。實體在我們代碼中以領域對象的形態存在,同時具備屬性和方法,實體是 DDD 用來實現充血編程、解決貧血症的關鍵。

與實體相對應的就是值對象,如果沒有唯一標識就是值對象。值對象一般是嵌套在實體裡面的。

實踐例子:

商品域中的實體和值對象如下

實體描述關鍵值對象
SPU 指一個被上架的服務。 spu_id, spu_type,狀態等。
SKU 指一個服務具體的單項套餐。 sku_id, 規格,價格等。
折扣 自定義折扣。 折扣 id,折扣類型,折扣比例等。

8)聚合/聚合根

把關係緊密的實體放到一個聚合中,每個聚合中有一個實體作為聚合根,所有對於聚合內對象的訪問都通過聚合根來進行,外部對象只能持有對聚合根的引用。每個聚合都可以有一個獨立的上下文邊界。

聚合應劃分的儘量小,一個聚合只包含一個聚合根實體和密不可分的實體,實體中只包含最小數量的屬性。設計這樣的小聚合有助於進行後續微服務的拆分。

如果一個 rpc 所實現的功能是跨聚合的,那跨聚合的編排協調工作應該放在應用層來實現。

實踐例子:

我們可以在 6)中的例子劃分如下的聚合。

聚合實體是否是根
聚合 1 服務 SPU
服務 SKU  
聚合 2 折扣

在底層存儲落表上, spu 實體/折扣實體作為表的一行, 而 sku 實體在這種聚合建模的指引下我們設計成 spu 聚合根的一列。

在微服務拆分上,如果想拆到最細粒度, 可以把兩個聚合按照各自上下文拆成獨立的微服務。當然這種落地實現並不是 DDD 強行要求的,我認為一些時候我們也可以從開發維護效率的角度考慮, 將一些有關聯的小上下文放在一個為微服務上。我們在處理商品域上選擇了後者。

9)DTO/領域對象/Data object

當一個請求進入 DDD 所設計的系統中,這個請求的形態會根據所在的層級發生如下變換,DTO<->領域對象<->Data object。

DTO 是指對外傳輸的其他服務需要理解的結構,領域對象是指同時包含了屬性和方法的領域實體封裝,Data object 則是真正用於最終存儲的數據結構。

圖片

這裡其實很容易發現,DTO 的存在雖然符合其他調用方最少知識原則(LKP),但如果連最簡單的查詢請求都需要做這三級的轉換,那無疑是會加重開發的複雜度,變成為了設計模式而設計模式。

最少知識原則(迪米特法則,LKP):一個軟體實體應當儘可能少地與其他實體發生相互作用。這裡的軟體實體是一個廣義的概念,不僅包括對象,還包括系統、類、模塊、函數、變數等。

所以 DDD 在這裡一般會使用 CQRS(讀寫責任分離)架構,來保證一些簡單的查詢請求不會因為領域建模而變得過於複雜。CQRS(讀寫責任分離)基於 CQS(讀寫分離),使用了 CQRS 的 DDD 對象轉換流程如下:

圖片

實踐例子:

我們的實現是在領域對象中封裝了轉換的 convert 函數(當然也可以在基礎設施層將 convert 方法拆分出來做單獨的封裝),用於將 DTO 轉換為領域對象,或者將領域對象轉換為 DO。下麵是我們明細域的實際轉換代碼和轉換過程。

//1.領域對象中定義convert方法
class DetailRecord {
public:
    int ConvertFromDTO(const google::protobuf::Message& oDto);
    int ConvertToDO(detailrecordinfrastructure::DetailRecordDO & oDo);
    /*...*/
};
//2.應用層調用方法將DTO轉化為領域對象, 然後調用倉儲介面進行持久化
int DetailrecordApplication::InsertDetailRecord(unsigned int head_uin, const InsertDetailRecordReq& req,  InsertDetailRecordResp* resp) {
   int iRet = 0;
   class DetailRecord oRecord;
   iRet = oRecord.ConvertFromDTO(req); //生成領域對象,可以同時利用領域對象的方法進行自檢等操作
    /*...*/
   iRet = m_oDetailRecordGateway->Save(oRecord); //調用倉儲介面進行持久化
    /*...*/
   return iRet;
}
//3.在倉儲中將領域對象轉化為Dataobject,進行落存儲操作,併發布領域事件
int DetailRecordGatewayImpl::Save(DetailRecord & oEntity){
   detailrecordinfrastructure::DetailRecordDO oDo;
   int iRet = oEntity.ConvertToDO(oDo);
    /*...*/
   iRet = oKvMapper.insert(oDo);      //實際落存儲
    /*...*/
   iRet = oEventMapper.publish(oDo);  //發送領域事件
    /*...*/
   return iRet;
}

10)倉儲

倉儲是領域層由定義介面,它抽象了業務邏輯中對實體的訪問(包括讀取和存儲)的技術細節。它的作用就是通過隔離具體的存儲層技術實現來保證業務邏輯的穩定性。註意,倉儲只是介面的定義是在領域層,但是它的實現是在基礎設施層

倉儲不是資料庫 Dao!!!

倉儲不是資料庫 Dao!!!

倉儲不是資料庫 Dao!!!

重要的事情說三遍,倉儲是從業務邏輯的角度抽象出來的介面,所以倉儲的介面在實現上,一般是一個聚合對應一個倉儲實現,倉儲的需要用領域對象做參數。倉儲介面的命名也可以取 save 這種更業務的命名, 而避免傳統 dao 的 insert/set 等這種明明。

實踐例子:

通過 3.9 的例子,我們可以發現,倉儲用於持久化的介面里,不但包含了寫 kv 的操作,還包含了發佈領域事件等操作,這就是因為倉儲是從業務邏輯角度抽象出來的介面,領域層只需要理解 save 這個業務操作,而不應該理解 save 的過程包含了落存儲、發佈領域事件等具體流程。

//1.領域層定義DetailRecord倉儲的介面
class DetailRecordGateway {
    public:
       /*...*/
       virtual int Save(DetailRecord & oEntity) = 0;
       /*...*/
};
//2.基礎設施層繼承領域層的倉儲介面進行實現
class DetailRecordGatewayImpl : public DetailRecordGateway {
    public:
       /*...*/
       virtual int Save(DetailRecord & oEntity);
       /*...*/
 };
//3.倉儲save介面具體實現
int DetailRecordGatewayImpl::Save(DetailRecord & oEntity){
   detailrecordinfrastructure::DetailRecordDO oDo;
   int iRet = oEntity.ConvertToDO(oDo);
   /*...*/
   iRet = oKvMapper.insert(oDo);      //實際落存儲
   /*...*/
   iRet = oEventMapper.publish(oDo);  //發佈領域事件
   /*...*/
   return iRet;
}

11)領域服務

當一些能力不適合放在某個領域對象中實現,又因為過於複雜不應該放在應用層來實現。可以把這些操作封裝成領域服務的中方法,由應用層編排領域層的領域對象和領域服務方法來完成具體的業務功能。

 

4.DDD 的代碼腳手架

我們基於對 DDD 的理解和 WXG 的 svrkit 框架,設定我們的代碼腳手架。腳手架的目錄如下所示,希望可以給想一起實踐的同事拋磚引玉,也歡迎大家來找我們一起討論:

項目目錄
├── adapter(物理用戶界面模塊)
├── domainsvr(領域微服務)
│   ├── detailrecorddomainsvr(明細域微服務)
│   │   ├── adapter(用戶界面層)
│   │   ├── application(應用層)
│   │   │   ├── detailrecord_application.cpp(應用層方法)
│   │   ├── domain(領域層)
│   │   │   ├── aggregate(聚合根)
│   │   │   │   ├── detail_record.cpp(領域對象)
│   │   │   │   └── detailrecordaggregate.proto(聚合根的值對象)
│   │   │   ├── entity(非根實體)
│   │   │   │   └── detailrecordentity.proto(非根實體的值對象)
│   │   │   ├── gateway
│   │   │   │   └── detail_record_gateway.h(倉儲介面)
│   │   │   └── detailrecord_domain_service.cpp(領域服務)
│   │   ├── infrastructure(基礎設施層)
│   │   │   ├── gatewayimpl
│   │   │   │   ├── acl(防腐層實現)
│   │   │   │   └── detail_record_gateway_impl.cpp(倉儲實現)
│   │   │   └── detailrecordinfrastructure.proto(Data object定義)
│   │   └── detailrecord.proto(DTO定義)
└── infrastructuresvr(物理基礎設施模塊)

 

 作者:kunqian

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Advanced-background-development_vernacular-DDD-from-introduction-to-practice.html


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

-Advertisement-
Play Games
更多相關文章
  • # In[1]magicians = ['alice', 'david', 'carolina']for i in magicians: print(i)'''4.1.1 深入地研究迴圈'''a = list(range(1, 10, 2))print(a)print(sum(a))''' 4.3. ...
  • 「如果港口是國民經濟的晴雨表,那麼智慧港口就是窺探港口未來發展的視窗。」 12月16日,2022智慧港口大會在浙江嘉興舉行,會議以“加快數字化轉型 賦能高質量發展”為主題,由中國港口協會、中國交通通信信息中心、浙江省海港集團聯合主辦,來自行業主管部門及港航管理部門、各大港口集團、科研院校、解決方案供 ...
  • “某中心受病毒攻擊,導致服務中斷,線上業務被迫暫停” “某公司員工誤操作刪庫,核心業務數據部分丟失,無法完全找回” “由於伺服器斷線,某醫院信息系統癱瘓近4小時,期間病人無法使用醫保卡掛號和結算” …… 數據丟失風險防不勝防,企業構建數據備份方案迫在眉睫! 此次騰訊雲資料庫備份服務DBS攜手富途證券 ...
  • 摘要:解決數據問題的本質,還要從數據層面入手,資料庫的價值就十分關鍵。 過去很長一段時間,不動產行業的數字化程度都是比較低的,特別在業務層面,存在大量碎片化和多主體的問題,導致在數據層面的標準化和數據結構統一化不足;而且在不動產行業全生命周期中,每個階段都頻繁涉及到數據流轉問題,對數據一致性和安全性 ...
  • 1.在聯盟創建伺服器應用 參考文檔:開發準備 2.獲取用戶級Access Token 2.1 獲取code 參考文檔:接入華為帳號獲取憑證 2.1.1 先按照跳轉鏈接進行配置url https://oauth-login.cloud.huawei.com/oauth2/v3/authorize? r ...
  • 上文搭建了組件庫 cli 的基礎架子,實現了創建組件時的用戶交互,但遺留了 cli/src/command/create-component.ts 中的 createNewComponent 函數,該函數要實現的功能就是上文開篇提到的 —— 創建一個組件的完整步驟。本文咱們就依次實現那些步驟。(友情 ...
  • “作為博客園的使用者而不是開發者,就不能對博客進行調優了?看好了,我只示範一次。” —— 我說的 0x00 大綱 0x01 前言 用過很多博客和寫作平臺,但是最終還是選擇了博客園,畢竟,自定義 CSS 和自定義 JS 是真的香!某天突發奇想,決定對自己的博客進行下優化,現將其中的一些心得與大家分享。 ...
  • 目前在家庭物聯網這一塊,絕大部分的電子消費品都是基於wifi聯網的設備。從商家那裡達到消費者手中之後,簡單開機使用無法體現其全部價值,還是需要經過消費者給設備配網的過程,把設備從信息孤島接入互聯互通的世界。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...