零開始的領域驅動設計

来源:https://www.cnblogs.com/frank0812/archive/2018/10/23/9834434.html
-Advertisement-
Play Games

領域驅動的火爆程度不用我贅述,但是即便其如此得耳熟能詳,但大多數人對其的認識,還只是停留在知道它的縮寫是DDD,知道它是一種軟體思想,或者知道它和微服務有千絲萬縷的關係。Eric Evans對DDD的詮釋是那麼地惜字如金,而我所認識的領域驅動設計的專家又都是行業中的資深前輩,他們擅長於對軟體設計進行 ...


領域驅動的火爆程度不用我贅述,但是即便其如此得耳熟能詳,但大多數人對其的認識,還只是停留在知道它的縮寫是DDD,知道它是一種軟體思想,或者知道它和微服務有千絲萬縷的關係。Eric Evans對DDD的詮釋是那麼地惜字如金,而我所認識的領域驅動設計的專家又都是行業中的資深前輩,他們擅長於對軟體設計進行高屋建瓴的論述,如果沒有豐富的互聯網從業經驗,是不能從他們的分享中獲取太多的營養的,可以用曲高和寡來形容。1000個互聯網從業者,100個懂微服務,10個人懂領域驅動設計。

可能有很多和我一樣的讀者,在得知DDD如此火爆之後,嘗試去讀了開山之作《領域驅動設計——軟體核心複雜性應對之道》,翻看了幾張之後,晦澀的語句,不明所以的專業術語,加上翻譯導致的語句流暢性,可以說觀看體驗並不是很好,特別是對於開發經驗不是很多的讀者。我總結了一下,為何這本書難以理解: 
1. 沒有閱讀軟體設計叢書的習慣,更多人偏向於閱讀偏應用層面的書籍,“talk is cheap,show me the code”往往更符合大多數人的習慣。 
2. 沒有太多的開發經驗支撐。沒有踩過坑,就不會意識到設計的重要性,無法產生共情。 
3. 年代有些久遠,這本書寫於2004年,書中很多軟體設計的反例,在當時是非常流行的,但是在現在已經基本絕跡了。大師之所以為大師,是因為其能跨越時代的限制,預見未來的問題,這也是為什麼DDD在十幾年前就被提出,卻在微服務逐漸流行的現階段才被大家重視。

誠然如標題所示,本文是領域驅動設計的一個入門文章,或者更多的是一個個人理解的筆記,筆者也正在學習DDD的路上,可能會有很多的疏漏。如有理解有偏頗的地方,還望各位指摘。

認識領域驅動設計的意義

領域驅動設計並不會絕對地提高項目的開發效率。 

圖1:複雜性與開發周期關係

 

遵循領域驅動設計的規範使得項目初期的開發甚至不如不使用它來的快,原因有很多,程式員的素質,代碼的規範,限界上下文的劃分…甚至需求修改後導致需要重新建模。但是遵循領域驅動設計的規範,在項目越來越複雜之後,可以不至於讓項目僵死。這也是為什麼很多系統不斷迭代著,最終就黃了。書名的副標題“軟體核心複雜性應對之道”正是闡釋了這一點。

 

模式: smart ui是個反模式

可能很多讀者還不知道smart ui是什麼,但是在這本書寫作期間,這種設計風格是非常流行的。在與一位領域驅動設計方面的資深專家的交談中,他如下感慨到軟體發展的歷史:

2003年時,正是delphi,vb一類的smart ui程式大行其道,Java在那個年代,還在使用jsp來完成大量的業務邏輯操作,4000行的jsp是常見的事;2005年spring hibernate替換了EJB,社區一片歡呼,所有人開始擁護action,service,dao這樣的貧血模型(充血模型,貧血模型會在下文論述);2007年,Rails興起,有人發現了Rails的activeRecord是漲血模型,引起了一片混戰;直到現在的2017年,微服務成為主流系統架構。

在現在這個年代,不懂個MVC分層,都不好意思說自己是搞java的,也不會有人在jsp裡面寫業務代碼了
(可以說模板技術freemarker,thymeleaf已經取代jsp了),
但是在那個年代,還沒有現在這麼普遍地強調分層架構的重要性。

這個章節其實並不重要,因為mvc一類的分層架構已經是大多數java初學者的“起點”了,大多數DDD的文章都不會贅述這一點,我這裡列出來是為了讓大家知曉這篇文章的時代局限性,在後續章節的理解中,也需要抱有這樣的邏輯:這本書寫於2004年。

模式: Entity與Value Object

我在不瞭解DDD時,就對這兩個術語早有耳聞。entity又被稱為reference object,我們通常所說的Java bean在領域中通常可以分為這兩類,(可別把value object和常用於前臺展示的view object,vo混為一談) 
entity的要義在於生命周期和標識,value object的要義在於無標識,通常情況下,entity在通俗意義上可以理解為資料庫的實體,(不過不嚴謹),value object則一般作為一個單獨的類,構成entity的一個屬性。

舉兩個例子來加深對entity和value object的理解。

例1:以電商微服務系統中的商品模塊,訂單模塊為例。將整個電商系統劃分出商品和訂單兩個限界上下文(Bound Context)應該是沒有爭議的。如果是傳統的單體應用,我們可以如何設計這兩個模塊的實體類呢? 
會不會是這樣?

複製代碼
class Product{
    String id;//主鍵
    String skuId;//唯一識別號
    String productName;
    Bigdecimal price;
    Category category;//分類
    List<Specification> specifications;//規格 
    ... 
}

class Order{
    String id;//主鍵
    String orderNo;//訂單號
    List<OrderItem> orderItems;//訂單明細
    BigDecimal orderAmount;//總金額
    ...
}

class OrderItem{
    String id;
    Product product;//關聯商品
    BigDecimal snapshotPrice;//下單時的價格
}
複製代碼

 

看似好像沒問題,考慮到了訂單要保存下單時候的價格(當然,這是常識)但這麼設計卻存在諸多的問題。
在分散式系統中,商品和訂單這兩個模塊必然不在同一個模塊,也就意味著不在同一個網段中。
上述的類設計中直接將Product的列表存儲到了Order中,也就是一對多的外鍵關聯。這會導致,每次訪問訂單的商品列表,都需要發起n次遠程調用

反思我們的設計,其實我們發現,訂單BC的Product和商品BC的Product其實並不是同一個entity,在商品模塊中,我們更關註商品的規格,種類,實時價格,這最直接地反映了我們想要買什麼的欲望。而當生成訂單後,我們只關心這個商品買的時候價格是多少,不會關心這個商品之後的價格變動,還有他的名稱,僅僅是方便我們在訂單的商品列表中定位這個商品。

如何改造就變得明瞭了

複製代碼
class OrderItem{
    String id;
    String productId;//只記錄一個id用於必要的時候發起command操作
    String skuId;
    String productName;
    ...
    BigDecimal snapshotPrice;//下單時的價格
}
複製代碼

是的,我們做了一定的冗餘,這使得即使商品模塊的商品,名稱發生了微調,也不會被訂單模塊知曉。這麼做也有它的業務含義,用戶會聲稱:我買的時候他的確就叫這個名字。記錄productId和skuId的用意不是為了查詢操作,而是方便申請售後一類的命令操作(command)。

在這個例子中,Order 和 Product都是entity,而OrderItem則是value object(想想之前的定義,OrderItem作為一個類,的確是描述了Order這個entity的一個屬性集合)。關於標識,我的理解是有兩層含義,第一個是作為數據本身存儲於資料庫,主鍵id是一個標識,第二是作為領域對象本身,orderNo是一個標識,對於人而言,身份證是一個標識。而OrderItem中的productId,id不能稱之為標識,因為整個OrderItem對象是依托於Order存在的,Order不存在,則OrderItem沒有意義。

例子2: 汽車和輪胎的關係是entity和value object嗎? 
這個例子其實是一個陷阱題,因為他沒有交代限界上下文(BC),場景不足以判斷。對於用戶領域而言,的確可以成立,汽車報廢之後,很少有人會關心輪胎。
輪胎和發動機,雨刮器,座椅地位一樣,只是構成汽車的一些部件,和用戶最緊密相關的,只有汽車這個entity,輪胎只是描述這個汽車的屬性(value object);
場景切換到汽修廠,無論是汽車,還是輪胎,都是汽修廠密切關心的,每個輪胎都有自己的編號,一輛車報廢了,可以安置到其他車上,這裡,他們都是entity。

這個例子是在說明這麼一個道理,同樣的事物,在不同的領域中,會有不同的地位。 

圖2:《領域驅動設計》Value Object模式的示例

在單體應用中,可能會有人指出,這直接違背了資料庫範式,但是領域驅動設計的思想正如他的名字那樣,不是基於資料庫的,而是基於領域的
微服務使得資料庫發生了隔離,這樣的設計思想可以更好的指導我們優化資料庫。

 

模式: Repository

哲學家分析自然規律得出規範,框架編寫者根據規範制定框架。有些框架,可能大家一直在用,但是卻不懂其中蘊含的哲學。

——來自於筆者的口胡

記得在剛剛接觸mvc模式,常常用DAO層表示持久化層,在JPA+springdata中,抽象出了各式各樣的xxxRepository,與DDD的Repository模式同名並不是巧合,
jpa所表現出的正是一個充血模型(如果你遵循正確的使用方式的話),可以說是領域驅動設計的一個最佳實踐。

開宗明義,在Martin Fowler理論中,有四種領域模型: 
1. 失血模型 
2. 貧血模型 
3. 充血模型 
4. 脹血模型 
詳細的概念區別不贅述了,可以參見專門講解4種模型的博客。他們在資料庫開發中分別有不同的實現,用一個修改用戶名的例子來分析。

class User{
    String id;
    String name;
    Integer age;
}

失血模型: 
跳過,可以理解為所有的操作都是直接操作資料庫,在smart ui中可能會出現這樣的情況。

貧血模型:

複製代碼
class UserDao {
    @Autowired
    JdbcTemplate jdbcTemplate;

    public void updateName(String name,String id){
        jdbcTemplate.excute("update user u set u.name = ? where id=?",name,id);
    }
}

class UserService{

    @Autowired
    UserDao userDao;

    void updateName(String name,String id){
        userDao.updateName(name,id);
    } 
}
複製代碼

貧血模型中,dao是一類sql的集合,在項目中的表現就是寫了一堆sql腳本,與之對應的service層,則是作為Transaction Script的入口。
觀察仔細的話,會發現整個過程中user對象都沒出現過

充血模型:

複製代碼
interface UserRepository extends JpaRepository<User,String>{
    //springdata-jpa自動擴展出save findOne findAll方法
}

class UserService{
    @Autowoird
    UserRepository userRepository;

    void updateName(String name,String id){
        User user = userRepository.findOne(id);
        user.setName(name);
        userRepository.save(user);
    }
}
複製代碼

充血模型中,整個修改操作是“隱性”的,對記憶體中user對象的修改直接影響到了資料庫最終的結果,不需要關心資料庫操作,只需要關註領域對象user本身。Repository模式就是在於此,屏蔽了資料庫的實現。與貧血模型中user對象恰恰相反,整個流程沒有出現sql語句。

漲血模型: 
沒有具體的實現,可以這麼理解:

void updateName(String name,String id){
    User user = new User(id);
    user.setName(name);
    user.save();
}

我們在Repository模式中重點關註充血模型。
為什麼前面說:如果你遵循正確的使用方式的話,springdata才是對DDD的最佳實踐呢?
因為有的使用者會寫出下麵的代碼:

複製代碼
interface UserRepository extends JpaRepository<User,String>{

    @Query("update user set name=? where id=?")
    @Modifying(clearAutomatically = true)
    @Transactional
    void updateName(String name,String id);
}
複製代碼

歷史的車輪在滾滾倒退。本節只關註模型本身,不討論使用中的一些併發問題,再來聊聊其他的一些最佳實踐。

複製代碼
interface UserRepository extends JpaRepository<User,String>{

    User findById();//√  然後已經存在findOne了,只是為了做個對比
    User findBy身份證號();//可以接受
    User findBy名稱();//×
    List<許可權> find許可權ByUserId();//×
}
複製代碼

理論上,一個Repository需要且僅需要包含三類方法loadBy標識,findAll,save(一般findAll()就包含了分頁,排序等多個方法,算作一類方法)。
標識的含義和前文中entity的標識是同一個含義,在我個人的理解中,身份證可以作為一個用戶的標識(這取決於你的設計,同樣的邏輯還有訂單中有業務含義的訂單編號,保單中的投保單號等等),在資料庫中,id也可以作為標識。findBy名稱為什麼不值得推崇,因為name並不是User的標識,名字可能會重覆,只有在特定的現場場景中,名字才能具體對應到人。
那應該如何完成“根據姓名查找可能的用戶”這一需求呢?最方便的改造是使用Criteria,Predicate來完成視圖的查詢,哪怕只有一個非標識條件。
在更完善的CQRS架構中,視圖的查詢則應該交由專門的View層去做,可以是資料庫,可以是ES
findByUserId不值得推崇則是因為他違背了聚合根模式(下文會介紹),
User的Repository只應該返回User對象。

軟體設計初期,你是不是還在猶豫:是應該先設計資料庫呢,還是應該設計實體呢?在Domain-Driven的指導下,你應當放棄Data-Driven。

模式 聚合和聚合根

難住我的還有英文單詞,初識這個概念時,忍不住發問:Aggregate是個啥。文中使用聚合的概念,來描述對象之間的關聯,採用合適的聚合策略,可以避免一個很長,很深的對象引用路徑。對劃分模塊也有很大的指導意義。

在微服務中我們常說劃分服務模塊,在領域驅動設計中,我們常說劃分限界上下文。在面向對象的世界里,用抽象來封裝模型中的引用,聚合就是指一組相關對象的集合,我們把它作為數據修改的單元。每個聚合都有一個聚合根(root)和一個邊界(boundary)。邊界定義了聚合內部有什麼,而根則是一個特定的entity,兩個聚合之間,只允許維護根引用,只能通過根引用去向深入引用其他引用變數。

例子還是沿用電商系統中的訂單和商品模塊。在聚合模式中,訂單不能夠直接關聯到商品的規格信息,如果一定要查詢,則應該通過訂單關聯到的商品,由商品去訪問商品規格。在這個例子中,訂單和商品分別是兩個邊界,而訂單模塊中的訂單entity和商品模塊中的商品entity就是分別是各自模塊的root。遵循這個原則,可以使我們模塊關係不那麼的盤根錯節,這也是眾多領域驅動文章中不斷強調的劃分限界上下文是第一要義。

模式 包結構

微服務有諸多的模塊,而每個模塊並不一定是那麼的單一職責,比模塊更細的分層,便是包的分層。
我在閱讀中,隱隱覺得這其中蘊含著一層哲學,但是幾乎沒有文章嘗試解讀它。
領域驅動設計將其單獨作為了一個模式進行了論述,篇幅不小。重點就是論述了一個思想:包結構應當具有高內聚性

這次以一個真實的案例來介紹一下對高內聚的包結構的理解,項目使用maven多module搭建。
我曾經開發過一個簡訊郵件平臺模塊,它在整個微服務系統中有兩個職責,
一:負責為其他模塊提供簡訊郵件發送的遠程調用介面,
二:有一個後臺頁面,可以讓管理員自定義發送簡訊,並且可以瀏覽全部的一,二兩種類型發送的簡訊郵件記錄。

在設計包結構之前,先是設計微服務模塊。

api層定義了一系列的介面和介面依賴的一些java bean,model層也就是我們的領域層。
這兩個模塊都會打成jar包,外部服務依賴api,api則由app模塊使用rpc框架實現遠程調用。
admin和app連接同一個數據源,可以查詢出簡訊郵件記錄,admin需要自定義發送簡訊也是通過rpc調用。
簡單介紹完了這個項目後,重點來分析下需求,來看看如何構建包結構。 

mvc分層天然將controller,service,model,config層分割開,這符合DDD所推崇的分層架構模式
(這個模式在原文中有描述,但我覺得和現在耳熟能詳的分層結構沒有太大的出入,所以沒有放到本文中介紹),
而我們的業務需求也將簡訊和郵件這兩個領域拆分開了。
那麼,
到底是mvc應該包含業務包結構呢?
還是說業務包結構包含mvc呢?

mvc高於業務分層

//不夠好的分層
sinosoftgz.message.admin
    config
        CommonConfig.java
    service
        CommonService.java
        mail
            MailTemplateService.java
            MailMessageService.java
        sms
            SmsTemplateService.java
            SmsMessageService.java
    web
        IndexController.java
        mail
            MailTemplateController.java
            MailMessageController.java
        sms
            SmsTemplateController.java
            SmsMessageController.java
    MessageAdminApp.java

業務分層包含mvc

複製代碼
//高內聚的分層
sinosoftgz.message.admin
    config
        CommonConfig.java
    service
        CommonService.java
    web
        IndexController.java
    mail
        config
            MailConfig.java
        service
            MailTemplateService.java
            MailMessageService.java
        web
            MailTemplateController.java
            MailMessageController.java
    sms
        config
            Smsconfig.java
        service
            SmsTemplateService.java
            SmsMessageService.java
        web
            SmsTemplateController.java
            SmsMessageController.java
    MessageAdminApp.java
複製代碼

業務並不是特別複雜,但應該可以發現第二種(業務分層包含mvc)的包結構,才是一種高內聚的包結構。
第一種分層會讓人有一種將各個業務模塊(如mail和sms)的service和controller隔離開了的感覺,當模塊更多,每個模塊的內容更多,這個“隔得很遠”的不適感會逐漸侵蝕你的開發速度。
一種更加低內聚的反例是不用包分層,僅僅依賴首碼區分,由於在項目開發中真的發現同事寫出了這樣的代碼,我覺得還是有必要拿出來說一說:

//反例
sinosoftgz.message.admin
    config
        CommonConfig.java
        MailConfig.java
        Smsconfig.java
    service
        CommonService.java
        MailTemplateService.java
        MailMessageService.java
        SmsTemplateService.java
        SmsMessageService.java
    web
        IndexController.java
        MailTemplateController.java
        MailMessageController.java
        SmsTemplateController.java
        SmsMessageController.java     
    MessageAdminApp.java

這樣的設計會導致web包越來越龐大,逐漸變得臃腫,使項目僵化,項目經理為何一看到代碼就頭疼,
規範的高內聚的包結構,遵循業務>mvc的原則,可以知道我們的項目龐大卻有條理。

其他模式

《領域驅動設計》這本書介紹了眾多的模式,上面只是介紹了一部分重要的模式,後續我會結合各個模式,儘量採用最佳實踐+淺析設計的方式來解讀。

 

微服務之於領域驅動設計的一點思考

技術架構誠然重要,但不可忽視領域拆解和業務架構,《領域驅動設計》中的諸多失敗,成功案例的總結,是支撐其理論知識的基礎,最終匯聚成眾多的模式。在火爆的微服務架構潮流下,我也逐漸意識到微服務不僅僅是技術的堆砌,更是一種設計,一門藝術。我的本科論文本想就微服務架構進行論述,奈何功底不夠,最後只能改寫成一篇分散式網站設計相關的文章,雖然是一個失敗的過程,但讓我加深了對微服務的認識。如今結合領域驅動設計,更加讓我確定,技術方案始終有代替方案,決定微服務的不是框架的選擇,不僅僅是restful或者rpc的介面設計風格的抉擇,而更應該關註拆解,領域,限界上下文,聚合根等等一系列事物,這便是我所理解的領域驅動設計對微服務架構的指導意義。

 

https://mp.weixin.qq.com/s?__biz=MzI0OTIzOTMzMA==&mid=2247483947&idx=1&sn=b6b5b0c76ddf678ab361f76c5a805ef9&chksm=e995c066dee249708b192e70fcdae56ed820fe3156458ece546a16a4ae73a9270332591bf784&mpshare=1&scene=2&srcid=0729GtSlD9MY4PY5fEASD46g&from=timeline#rd


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

-Advertisement-
Play Games
更多相關文章
  • Learun FrameWork 強大工作流引擎,讓OA更智能 互聯網的發展促使企業在信息化的道路上不斷探索,而隨著企業信息化進程的不斷深入,OA協同辦公的概念也逐步進入大眾的視野。 OA的選型關乎企業的生存發展,除了需要重視“OA技術、OA品牌、OA產品、OA服務”四大要素之外,更重要的其實是讓O ...
  • 前言: 在生產環境中,未避免單點故障,每個微服務都會做高可用部署。 通白的說,就是每一個一模一樣的服務會根據需求提供多分在多台機器上。 那麼在大併發的情況下,如何分配服務可以快速得到響應,就成為了我們要解決的問題。 Ribbon就是一款優秀的客戶端負載均衡機制。 什麼是客戶端負載均衡呢? 就是由服務 ...
  • 本文探討如下幾個問題: 什麼是架構屬性 約束和架構屬性的關係 有哪些架構屬性 各個架構屬性涉及知識點 什麼是架構屬性 首先,問個很簡單的問題!請看下麵的Java代碼: 請問上面的代碼中: name和age被稱為Person這個類的什麼? skill又稱為Person這個類的什麼呢? name和age ...
  • 文章提綱 概述要點 理論基礎 詳細步驟 總結 概述要點 理論基礎 詳細步驟 總結 概述要點 設計模式的產生,就是在對開發過程進行不斷的抽象。 我們先看一下之前訪問數據的典型過程。 在Controller中定義一個Context, 例如: private AccountContext db = new ...
  • java併發的一系列框架和技術主要是由java.util.concurrent 包所提供。包下的所有類可以分為如下幾大類: locks部分:顯式鎖(互斥鎖和速寫鎖)相關; atomic部分:原子變數類相關,是構建非阻塞演算法的基礎; executor部分:線程池相關; collections部分:併發 ...
  • 真值和假值 相等操作符(==和 ) 下麵分析一下不同類型的值用相等操作符(==)比較後的結果 toNumber 對不同 類型返回的結果如下: toPrimitive 對不同類型返回的結果如下: 操作符。如果比較的兩個值的類型相同,結果就如下;如果比較的兩個值類型不同,返回的就是false 下麵的例子 ...
  • 關於MVC架構中的Repository模式 關於MVC架構中的Repository模式 關於MVC架構中的Repository模式 關於MVC架構中的Repository模式 個人理解:Repository是一個獨立的層,介於領域層與數據映射層(數據訪問層)之間。它的存在讓領域層感覺不到數據訪問層的 ...
  • 本文長度為3032字,預計讀完需1.1MB流量,建議閱讀8分鐘。 閱讀目錄 為什麼沒有DNS? 如何實施? 優缺點 結語 閱讀目錄 為什麼沒有DNS? 如何實施? 優缺點 結語 為什麼沒有DNS? 如何實施? 優缺點 結語 為什麼沒有DNS? 如何實施? 優缺點 結語 為什麼沒有DNS? 如何實施? ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...