主頁 > 軟體設計 > 戲說領域驅動設計(廿五)——領域事件

戲說領域驅動設計(廿五)——領域事件

2022-05-07 09:06:36 軟體設計

  任何事物都在變化著包括領域驅動設計這門學問,Evans在首次提到DDD概念后,后來出現了陸續又出現了很多的專家與學者對其理論進行了擴充比如:“領域事件”、“事件源”、“命令查詢責任分離”等,也正是由于這些補充,不僅讓DDD的適用范圍變得更大也讓后來出現的微服務架構系統受益良多,為系統落地提供了非常優秀的理論指導,這節我們主要討論領域事件,不夸張的說,在現代化的業務系統中它的應用普度度非常高,將其看成一種事實上的標準也并不為過,尤其在使用基于Saga的分布式事務時,領域事件完全是不能少的,此外,DDD中不推薦一個事務更新多個聚合,那如果有這種需要的時候要怎么做呢?答案還是“領域事件”,所以讓我們開始今天的學習之旅,

一、概覽

  主流的基于事件的業務處理流程大概如下圖所示,為什么說是主流呢?有些特殊情況下可能會使用多執行緒+遠程服務呼叫的方式進行事件的投遞,但這種情況大多都發生在遺留的系統中,很多系統中早已經引入了訊息佇列中間件或者一些訊息佇列組件,使用它們作為訊息的載體已經是主流,所以后續的內容中一旦涉及到訊息的投遞我們默認就是指使用訊息佇列 ,

 

  單體時代,想要實作模塊間的交流最簡單的方式是通過行程內函式呼叫,比較直觀,程式員用起來也更方便,到了微服務的時代,由于業務被劃分到多個獨立部署的服務中,想要實作業務串聯方式之一是使用行程間通訊技術比如RPC或基于HTTP呼叫,但使用遠程呼叫的方式所帶來的隱患比較多,一是由于同步的呼叫會產生性能瓶頸,其實基于進行內呼叫也是一樣,單執行緒情況之下整個業務執行的時間等于其所呼叫的所有方法的執行時間之和; 二是分布式部署的服務需要通過網路連接進行協作,你不能假設網路是穩定的,而不穩定的網路所帶來的隱患也很多,比如性能、后期運維等,所以使用訊息及訊息佇列中間件作為服務間的資訊交換方式成為另外一種主流,不論是在微服務的內部還是在微服務之間,而且呢,由于各服務都是與訊息中間件進行互動也不用知道其它服務的地址,能大大減少服務間的相互依賴(即使引入了服務治理工具也不代表沒有依賴,而是服務的客戶端不再像過去一樣需要了解服務端的IP地址和埠等資訊),引入領域事件的另一個優勢就是系統的擴展性被增強:在使用基于遠程呼叫的方式實作某個業務時,當業務需要進行擴展時很多時候你需要增加對另外的服務的呼叫;而使用事件的機制,您只需要再引入一個事件的監聽者即可,成本非常低,也符合了我們所追求的“開閉原則”,雖然訊息這種方式看起來要美好很多,但需要額外引入新的訊息中間鍵,必然會加大學習與運營的成本,不過這個賬得看你怎么算,通過硬體與人員的投入雖然有額外的支出,但能讓系統更加穩定,吞吐量更高,實際上又節約了成本,再說了,為了應對請求的高峰有的時候你必須要引入訊息佇列進行緩沖以實作削峰填谷,事件本質上不就一種訊息嗎?大部分情況下可以復用系統中的基礎設施,反正一個羊是趕,兩個羊也是放,也不差領域事件那點消耗,

領域事件的提出其實是在Evans那本書之后,有的時候我在想:在沒有領域事件的情況下,他是如何處理多聚合的協作呢?猜測的結果有兩個:一是和當時的時代背景有關,03或04年他提出這個概念,當時單體是主流并不會有那么多的子服務存在,因此在實踐中應該是允許一個事務更新多個聚合的,也就是通過應用服務完成聚合的協作,二是當時EJB比較流行,里面有企業訊息總線的使用,可以通過它實作聚合間的協作,但作者并未給訊息賦予領域事件之名,具體原因不可考,總得來說領域事件的使用的確讓哪怕技術一般的團隊也能開發出較高吞吐量的系統,

二、領域事件本質

  領域事件的本質需要從兩個維度進行說明:業務與技術,在業務方面,領域事件表達了在領域中發生的某些事件,為了表達這個事件我們對其進行了建模并使其成為通用語言的一部分,單純的構建一個領域事件其實沒什么作用,在業務中由于某個領域物件的動作被觸發會引發與之關聯的另外的領域物件也受到影響,那么我們要怎么通知受波及的物件呢?答:領域事件,通過領域事件我們可以驅動業務的流向,其實您仔細想一想會發現很多的業務都是由于某個事件的發生而推動其流程前進的,所以我有的時候在想“基于事件的架構”是不是更符合業務本質或者說更有助于系統的實作,此外,在領域驅動設計中還有一種架構風格叫“事件溯源(ES)”,其也使用領域事件,雖然在架構風格和開發風格上有別于我們傳統的模式,但其本質上也是由事件進行驅動的,只不于更注重于物體驅動物體屬性的變更,

  有這樣的一個需求:“訂單支付后需要給其所屬賬戶增加10點成就值”,在使用微服務架構的系統下,您可以很明顯的看出來系統中應該包含兩個服務:“訂單服務”用于處理訂單相關的業務; “賬戶服務”用于處理成就值業務,這段需求中您也可以發現一個明顯的領域事件“訂單支付后”,在引入了領域事件后這個業務的處理流程可分解為:訂單服務在訂單支付后產生“訂單支付”事件;賬戶服務可以根據事件觸發積分邏輯,此處,為了實作事件在服務間的投遞通常會引入事件發布與訂閱組件,具體細節后面說明,因為領域事件的引入,您可以讓微服務系統發揮出最大的效能,每個系統都專注于完成各自的責任;從技術的角度來看由于使用了訊息佇列,整個業務的執行也會由原來的同步變為異步,性能更高,代碼案例如下所示,

public class OrderService {
    public void pay(Long orderId, Money cost) {
        Order order = this.orderRepository.findBy(orderId);
        OrderPaid orderPaid = order.pay(cost);
        this.eventBus.post(orderPaid);
    }
}
public class AccountService {
    public void handle(OrderPaid orderPaid) {
        Account account = this.accountRepository.findBy(orderPaid.getAccountId());
        account.increaseRewardPoints();
    }
}

  讓我們再進行一個反推,如果沒有領域事件要如何處理示例業務呢?您需要在應用服務中在執行訂單的支付業務后再通過遠程呼叫的方式讓賬戶服務執行積分的增加,大致的代碼如下所示,

public class OrderService {
    public void pay(Long orderId, Money cost) {
        Order order = this.orderRepository.findBy(orderId);
        order.pay(cost);
        this.remoteAccountService.increaseRewardPoints(10L);
    }
}

  哪種代碼更好一點?目測還是使用領域事件的方案更優秀:異步操作,性能是杠杠的,遠程呼叫的方式就差了點意思,案例中只展示了基本的邏輯,如果想要確保“訂單支付后需要給其所屬賬戶增加10點成就值”這個業務能夠順利完成,你還得加上一個分布式事務,這可就復雜了,當然了,使用了領域事件的方式你也得做一些作業來保證訊息不丟失,但總得來看方案二要復雜一點,如果一個業務涉及到多個服務共同參與才能完成,那這個性能低得可就不是一點半點了,是不是在您的心里已經首先把方案二給否了?我這性子已經夠急了,您這比我還急,先別著急下結論,親!具體使用哪種方案還得看需求呢,請聽我慢慢道來,

  首要的一點,您心里得有一個譜,咱們這個案例是基于微服務風格的,那考慮問題的時候就得站在微服務的角度而不能仍然使用單體的思維來看待問題,說白了就是需要把眼光放寬一點,分布式系統有一個重要的特性您時刻都不能忘掉的即“CAP”,大師已經證明了您只能選擇一種,要不是“AP”要不就是“CP”,不僅是那些我們常用的中間件如此,您所做的業務系統也需要一同考慮,為什么很多人會忽略這一點?因為我們使用的這些中間件也好,工具也好,人家已經幫你決定了到底“AP”或“CP”,比如Zookeeper,雅虎幫您確認這個就是“CP”的,用戶不用操心這些事情,直接使用即可,這種問題造成了很多的軟體工程師在建設分布式系統的時候時常忽略“CAP”這個東西,也就造成了對于上述的案例先入為主的認為方案一比較好,那為什么我說評估方案的好壞要看業務需求呢?假如業務強烈要求你必須要保證賬戶的積分必須與訂單支付保持同步,那方案二才是首選,當然,這里所謂的“強烈要求”需要工程師做好判斷,從用戶的角度來看他們肯定要求資料需要時刻保持同步尤其是不懂技術的客戶,可是大多數的時候其實他們是容忍這種同步存在著延遲的,可以假想一下,如果沒有系統的支撐,通過手工來實作業務是不是也存在不一致呢?說到這里您應該知道為什么DDD強調最終一致性了吧?因為的確是大多數情況下不需要嚴格保持資料的強一致性的,我在前面的文章中曾強調過在微服務風格系統中使用Saga代替強分布式事務是一種事實上的標準,也是由于業務的特性造成的,也就是說大多數業務其實只要實作AP就足夠了,不過話又得說回來了,假如你做的系統出現長時間的資料不一致比如一天,那您也別怪用戶懟你,誰也不能容忍如此夸張的延遲,我們所說最終一致性雖然沒有一個標準規定這個最終要經歷多久,那也不能幾小時、幾天都不一致吧?

  以DDD的眼光來看,其實方案二的問題是在建模上,沒有對于需求中的“訂單支付后”這個動作進行建模,不夠純粹,而領域事件的好處是其能夠更加精確的表達通用語言,使用了領域事件后,您可以在需求中提煉出很多的領域模型,這樣會使得建模的作業做得很細致,十分有利于挖掘到業務的本質,當然,這話就有點虛了,具體的好處是你對業務本質認識的越清楚做出的系統就會更加健壯,可擴展性也更強,寫了這么多東西,其實雖然只有這一句話“領域事件能夠更加精確的表達通用語言”對應了標題,不過那些陪襯的內容也是精華,加緊找個小本本兒記下來,

三、領域事件與領域命令

  領域事件從技術的角度來看其實就是訊息,類似的還包括領域命令,說白了就是給訊息一個業務術語(使用訊息表示兩者是比較普遍的情況,我們此處只談主流的使用方式),可就是這些術語才能對應我們的主題“領域驅動設計”,叫“訊息驅動”總是差點意思,讓我們先解釋一下這兩者的異同,

  相同方面:1)兩者都需要使用通用語言來命名;2)都是對動作的建模,只不過一個表示已經發生,一個表示未發生;3)一般都以訊息的方式來實作;4)都需要遵從相同的使用約束比如都應該放到BO層中;不應當在其中放入領域物體;5)一般都會觸發額外的業務動作;6)針對兩者的投遞方式,主流方式是使用訊息佇列,

  不同方面:1)從業務上來看兩者所表達的含義完全不同,領域事件表示某個已經發生的業務動作,是對于發生后的事件的建模;而領域命令所表示的動作還尚未發生;2)語意不同,事件所觸發的動作具備被動色彩:某些業務動作被引發是由于某個事件發生了,您稍微注意一下會發現我這里使用了“某些業務動作”,說明一個事件可能觸發多個業務行為,此外,事件的發布方在生成事件后并不期待事件的訂閱方給出回應,領域命令在業務上表示主動的含義,命令產生方主動的發起某個動作,它十分期待收到命令的那個接收者給出回應,比如通過訊息佇列給出一個回應事件,這里還是需要注意一下命令的接收者數量:只能有一個,

  使用領域命令的場景以我個人的經歷沒法概括出全部,但在此列出有代表性的且經過個人實踐過的兩點:1)CQRS架構的應用,一般C端面使用異步的領域命令,因為使用了這種架構一般是由于高并發的需要,使用異步的訊息模式能更好的應對;2)Saga,Saga的使用模式是接收事件并發送命令,使用事件的場景相對就會普遍很多,我覺得在使用DDD的戰術方式進行系統建設的時候幾乎多多少少的都會涉及到 ,最起碼在有事務需求的時候少不了,

  理論說得天花亂墜,那么領域事件到底如何產生呢?咱們這不是嚴謹的學術型文章,所以我基于日常的實踐總結出兩種方式:1)領域模型或服務在做出某個動作后,將事件以回傳值的形式生成;2)領域事件的組成需要的資訊相對復雜,需要在應用服務中進行構建,方式一我在前面展示過代碼此處便不再重復說明,方式二如下列代碼所示,“(1)”部分所使用的“ApplyFormTerminated”事件需要“OperatorInfo”資訊,而這個資訊并不參與業務邏輯,所以我們直接使用事件的建構式在應用服務中創建,

public CommandHandlingResult terminate(Long id, OperatorInfo operatorInfo) {    
    OprApplyForm oprApplyForm = this.oprApplyFormRepository.findBy(id);
    if (oprApplyForm == null) {
        throw new InvalidOperationException(OperationMessages.APPLY_FORM_NOT_EXIST);
    }

    oprApplyForm.terminate();

    TransactionScope tScope = TransactionScope.create(UnitOfWorkFactory.INSTANCE, oprApplyFormRepository);
    this.oprApplyFormRepository.update(oprApplyForm);
    CommitHandlingResult commitResult = tScope.commit();
    if (commitResult.isSucceed()) {
        this.localEventBus.post(new ApplyFormTerminated(operatorInfo, oprApplyForm.getId())); // (1)
    }
}

四、事件的組成

  事件本質上是一個物體物件,正常情況下不會在里面加入業務方法,即便有也不能修改其內部的屬性,我個人在用的時候還會將其當作DTO一般來看待并讓其具備值物件的不變特性,不會將事件作為某個物體的屬性,也不會在其中嵌入任何的物體或值物件,所有的屬性皆使用基本型別,實踐中,我們一般會給事件一些公共屬性如事件源即由誰來觸發的事件、事件產生的日期、事件ID等、請參看如下示例,

public class DomainEventBase {
    private String sourceService;
private Object sourceAggreateId;
private String id; private Date occurredOn; }

  此處我多廢話兩句,針對事件的來源“sourceService”,我一般情況下會把產生事件的類的全名+服務名賦給它,有的時候我們在應用中會發布各種各樣的事件,在排查問題的時候你都不知道這個事件到底是誰發出來的,又沒有檔案來作為指導,專案著急上線也沒人寫那個東西,大多數檔案都是系統上線后、驗收前后補的,做過開發的人你懂的……,這個欄位可以很有效的幫助排查問題,“sourceAggreateId”表示產生這個事件的聚合的ID,注意一點,我們這里把事件稱之為“領域事件”,表示其作用范圍在整個領域內,比較現實的情況是并不是所有的限界背景關系的實作都使用物件驅動的方式,存在著大比例數量的服務使用了事件腳本,在這種情況下雖然沒有聚合的概念但不代表不能產生事件,所以我一般也會把某個資料物體的ID賦給“sourceAggreateId”,最后要說的是“id”這個屬性,表示事件的ID,建議把它加到事件中,因為對于事件的冪等性處理幾乎是一種事實上的標準,您可以使用一些業務資訊作為冪等的判斷標準,也可以使用事件ID,比如把它放到Redis中,收到事件后可以判斷ID是否在Redis中存在來決策是否要正常的處理這個事件,

五、事件的載體

  前面我們說過事件在技術上可以等同于訊息,不過并不是一個嚴格的定義,你當然可以使用比如REST進行事件的傳輸,這種方式雖然能滿足通用語言的需要但不能享受事件所帶來的性能上的提升,既然主流的使用方式是訊息佇列 ,那我們在實踐其實有很多的選擇,可以使用基于記憶體的BlockingQueue、Guava EventBus,也可以使用大型的分布式訊息佇列如Kafka、RabbitMQ等,涉及訊息中間件的部署與結構不是本文的重點,所以我們只談應用,這兩種方式在實踐中我都使用過,基于記憶體的自治性很好,也就是說你不需要依賴于外部的訊息佇列,不會因為佇列出現問題而導致應用不可用,基于記憶體的優勢還在于你通常情況下只需參考一個Jar包即可,拎包入住,在不怕訊息丟失的場景這是一個很好的選擇,所以您在使用前要評估一下是否可以容忍訊息的丟失,畢竟應用一重啟訊息也就丟了,但無論如何最好別自己寫一套新的,好多的現成工具可用何必重新造輪子,你能保證你寫得一定比Guava EvenBus好?

  另外一點就是訊息佇列的可靠性需要多加思考,比如如何避免訊息的丟失就是一個很值得投入精力的地方,當然,想保障訊息不丟失,首先在訊息佇列中間件的選擇上就不能隨意了,你整個記憶體型的訊息佇列還要要求訊息處理的可靠性基本上沒戲,我個人經歷的專案中使用過兩種分布式MQ:RabbitMQ和Kafka,在此我們只以前者為例介紹一下如何保障訊息的不丟失,通常下我們可選擇三種方式來進行保障:1)生產者使用Confirm機制,出現投遞問題后將訊息寫入到資料庫以用于重試;2)配置訊息佇列的時候開啟“Durable”模式并將訊息在服務器端進行存盤(注意:此處使用的是訊息佇列集群,單實體無論你怎么折騰都沒戲);3)消費者開啟ACK機制,這里面的前兩點訊息佇列都可以幫忙實作,而在消費端的訊息不丟除了ACk能起到部分作用外,還需要消費者進行保障,簡單來說只要訊息到達消費者就必須保障其成功的處理,類似于“TCC”事務中的“Confirm”處理,這一點不僅是針對RabbitMQ,包括Kafka、RocketMQ等都是一樣的要求,

  還有一點需要著重說明:在訊息的發送端僅使用“Confirm”機制是不能保障訊息完全不丟失的,比如下列代碼,“(1)”處的代碼提交了一個資料庫的事務,假如此刻系統掛掉,事件也就一并丟失了,這種情況比較極端但不代表不發生,據小道訊息說“本地訊息表”方案可以解決這個問題,但到底要不要真的引入還請慎重,我們在生產者、消費者和訊息佇列配置上下得功夫已經不少了,已經能大大的保障訊息不丟,而引入本地訊息表又要做很多的作業,所以在考慮人工的介入還是嚴格的系統約束間要找到平衡,盡管作為一個技術人員我不應該說這種不負責任的話,但實作本來與理想就是存在差距的,

public class orderService {
    public void pay1(Long orderId, Money cost) {
        Order order = this.orderRepository.findBy(orderId);
        OrderPaid orderPaid = order.pay(cost);
        
        this.orderRepository.update(order);
        this.uniteOfWork.commit(); // (1)
        
        this.eventBus.post(orderPaid);
    }
}

  其實我個人也經常在專案中使用記憶體型的訊息佇列Guava EvenBus,當時的使用場景是對業務告警進行接收并用于后續的處理,雖然可能面臨訊息丟失風險,但偶然丟個一條兩條其實也不會造成多大的影響,因為業務例外有一個特性:其往往是重復錯誤,丟失部分訊息并不會有多大的問題,之所要提到這個事情其實就是想提醒讀者在專案建設的時候要一定要考慮系統建設的成本,原則上我們肯定要求不能有任何訊息的丟失,但這個事情得從兩個方面看而且絕對不可以上綱上線,極左或極右都不可能把事情做好,

六、事件處理

  我們已經說過,一個事件會有多個訂閱者, 在六邊型架構中,事件的“Adapter”處在架構的左側作為事件的輸入,但您不應該在Adapter中完成事件的處理而是應該和一般的REST呼叫一樣使用應用程式服務進行業務的協調處理,這里有一點需要特別的注意即事件的“冪等性”,實際上在基于訊息的業務場景中大部分情況下都需要考這個事情 ,可能由于網路、訊息組件和消費者處理例外等原因需要進行訊息的重發;當事件有多個訂閱方的時候,如果有一個訂閱方出現失敗可能也需要進行業務補償,而最簡單的補償方式就是把事件重發一次,總之呢,同一個訊息被重復的收到多次是非常常見的場景,那您在使用的時候就必須要投入精力做好保障,前面我們曾經說過,您可以給事件一個唯一ID比如“UUID”并在消費端把ID進行存盤以達到排重的目的;您也可以通過使用業務標記進行排除,這種方式在使用Saga的時候會經常被使用以達到事務的隔離效果,下面代碼片段來自于我曾經做過的一個專案,此處使用業務資訊來決策某個事件是否被收到過如“(1)”處,

public void handle(WorkOrderAccepted workOrderAccepted) {
    if (this.status == ResourceBuildStatusEnum.UN_START) { // (1)
        this.status = ResourceBuildStatusEnum.SAVING_WORK_ORDER;
        this.updatedDate = new Date();
        this.message = this.status.getDescription();

        SaveWorkOrder saveWorkOrder = new SaveWorkOrder();
        saveWorkOrder.processManagerId = this.getId();
        this.commands.add(saveWorkOrder);
    }
}

  針對事件的存盤,這個其實要看具體的需要,如果不是使用ES架構的服務,至少要對核心的事件進行持久化,十分有利于后續系統的運維,由于事件是只讀的,其存盤的記錄也不會進行更改,所以不論是使用MySQL這種關系型資料還是使用MongoDB這種NoSQL,并沒有太大的限制,主要看您的系統現狀,不過在運維作業中有一點請務必要注意:請對事件記錄進行周期性轉存,一是可以方便后續的安全審計,二是可以減少其資料占用量以避免與其它業務資料發生空間爭搶,我個人在使用的時候直接存到了MySQL中,和業務資料進行了分離,每隔一個月備份一次資料,其實也只起到了備份的作用,平常幾乎不查,對了,最好在事件生產側進行存盤,萬一丟了呢,

 七、反思

  微服務架構下的事件使用,存在這樣一個場景,我們還是以本章中的“訂單支付后需要給其所屬賬戶增加10點成就值”這個需求為例,假如訂單服務發布了一個“OrderPaid”事件,在賬戶服務中要如何進行處理呢?我們是否需要設計一個和“OrderPaid”結構一模一樣的類且保持“OrderPaid”命名不變,簡單來說就是把這個事件的代碼復制到賬戶服務中,另外一個選擇是我們在賬戶服務中建立一個和“OrderPaid”結構一樣但叫做“ChangeRewardPoint”的領域命令,使用命令代替原來的事件來處理“積分變更”這個業務,請發揮您的聰明才智,也期待您的回復,

總結

  本節講解了領域事件的使用,在實踐中請您結合自身的業務需求尤其是基于“CAP”理論來決策是否應該使用,不要被先入為主的想法蒙蔽雙眼,我們還講解了事件的通常結構、事件的載體和事件的存盤,您別一時用得痛快結果由于不能全面考慮造成后續運維成本的加大,我個人的作業經歷中有一段時間是作為運營運維的角色存在,相信您在我的文章中總會看到我會提及系統的運維,個人其實更中意軟體設計與研發的作業,可也正是因為這段運維經歷讓自己在考慮事情的時候不會那么局限,能夠站在不同的維度去思考,

  客觀來講,基于事件驅動的服務用起來的確很痛快,一是建模的粒度比較細,讓系統的擴展點增加了很多,很多的時候加個功能不過是增加一個事件的消費者而矣,并不會因為新加入的邏輯引發全域BUG或性能損耗,二是系統的性能會有很多的提升,服務解耦處理做得也比較優雅,然而事情有利也有弊,請客觀的、務實的、謹慎的進行選擇,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/469861.html

標籤:其他

上一篇:做SaaS的程式員們,是時候關注企業架構了

下一篇:想學會SOLID原則,看這一篇文章就夠了!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more