輕量級領域驅動設計DDD Lite在嵌入式系統重構中的應用


前言

目前,關於領域驅動設計(Domain Driven Design)DDD的培訓,材料,視頻都比較多,大家對DDD的一些概念都有所了解,但是在實際使用過程中,有很多的問題。例如

  1. 為什么DDD的架構要表示成六邊形和洋蔥形呢?
  2. 從六邊形圖來看,有領域層的概念么?如果有的話,用戶接口層能否直接訪問領域層?
  3. 四種領域模型,應該優選那種模型?
  4. 領域跟功能模塊有啥區別?
  5. DDD看上去與嵌入式開發挺契合的,但是結合業務,怎么理解和運用?
  6. DDD的格局有點大,思維的方式很好,但是套到我們的驅動開發上,是否能很好的匹配?

本文結合我們在顯示重構使用DDD完成建模的實踐,探討一下如何在嵌入式開發中把DDD落地。歡迎大家提出問題,我們一起探討。

1. 軟件架構的演進和DDD的出現

首先,我們回顧一下軟件架構的演進的歷史和DDD出現的背景。

軟件基礎體系結構演進的歷史

如下圖,軟件的基礎體系架構的演進的大體時間線和一些標志性的技術。整個軟件體系架構演進可以分為4個階段:
c887fb3706a45a128b4c4c34a695c8f6_1922x1044.png@900-0-90-f.png

  • 單機時代

    • 所有軟件層都在一個PC機上實現。
    • 那時候就有了軟件分層的思想,主流的軟件架構是MVC(數據模型,視圖,控制器)
  • 局域網時代: 1985 ~ 1995

    • 客戶端和服務器在一個局域網中
    • 軟件的功能細分成了客戶端和服務器即C/S結構。UI和業務邏輯在客戶端,服務器端是數據庫。當時的王者是VB/Sybase/Oracle。
    • 軟件架構還是基於MVC三層的架構,但是面向對象設計OOD和UML設計開始流行,面向對象的編程(OOP)開始成為主流的編程語言。
    • 面向對象的23中設計模式,也是在這個時代誕生的(1995 年,GoF合作出版了《設計模式:可復用面向對象軟件的基礎》一書,共收錄了 23 種設計模式)。
  • 互聯網web1.0時代 : 1996 ~ 2006

    • 互聯網開始迅速流行,泡沫也開始產生,到了2000年到達了頂峰。
    • 這個時代的基礎體系架構是瀏覽器,客戶端,服務器的架構(B-C-S)。UI被Web UI代替,同時Client端承載着web 服務器(提供web服務)和應用服務器(業務邏輯)兩個作用,服務器端還是數據庫服務器。
    • 后來,web服務器和應用服務器也開始逐步的移植到了后端,基礎體系結構也就進化到了瀏覽器-服務器(BS)結構。
    • 這時,應用服務器和數據庫服務器都是由使用者自己搭建物理主機系統和維護其運行,或外包給IDC和應用軟件服務商。這種系統一般稱作on promise - 在場或私有軟件系統。
    • 這個時代,基於互聯網的架構模式開始大量涌現,REST,EDA,SOA等。主流的架構模式是Monolithic Architecture 單一式架構,即所有的功能都放在一起,只做了分層,縱向的屬於同一業務的模塊之間是形式松散上或只是邏輯上的Function Group 功能群組形式組織在一起。
    • 隨着后端系統的愈加復雜和龐大,單一式架構已經難以維護和局部升級。
    • 為了解決這個問題,在2004年,Evans提出了領域驅動設計DDD(Domain Driven Design), 但是當時並沒有收到太多的關注。 主要的原因還是基礎設施體系還不夠靈活,把一個單一系統分解成多個系統,維護和部署的工作量會成倍的增長。
  • 雲時代Web2.0 - 2007 ~

    • 亞馬遜開始推出EC2的雲上虛擬機服務,軟件體系架構就進入到了雲時代。
    • 企業不再需要自己搭建和維護物理主機了而是直接跑在雲廠商提供的虛擬機上(IaaS模式),隨着雲廠商提供越來越多的服務,包括API網關,數據庫,Web服務等一些列的服務(PaaS),尤其是在容器技術成熟之后,使得在不同的地域和雲廠商上靈活的部署服務成為可能
    • 雲端的軟件技術也突飛猛進,SOA,CQRS,ES,DCI, Actor等架構模式都從相繼出現。
    • 軟件的整體架構模式也從Monolithic Architecture 單一式架構躍升到了現在非常流行的 Microservice Architecture微服務架構。
    • 而最近幾年,以BaaS(Backend As A Service)和FaaS(Function As A Service)為基礎的無服務器架構Serverless也越來越流行
    • 隨着基礎設施體系的容器化和構建部署DevOps體系的自動化,使得部署和維護多個小系統的代價大大降低,微服務架構所帶來的好處也極大的體現出來,而領域驅動設計DDD因為和微服務架構的思想十分契合,而且因此也收到了廣泛的追捧。

我們接下來看一下,領域驅動設計是如何把對一個實際的系統進行設計建模的。

2. 領域驅動設計的要點

領域驅動設計由Eric Evans在2004年出版的書中首次提出。后面經過了很多改進。已經形成了一套從戰略到戰術一套的建模方法。關於領域驅動設計的細節,大家可以參考參考文獻【5】~【7】。這里只概括一下要點。
8469efe74592a3f76845ce68626705e4_703x563.jpg@900-0-90-f.jpg

DDD建模可以分為戰略和戰術兩個層面

  1. 戰略設計:主要是和領域專家一起通過事件風暴的方法做需求分析和領域建模。包括
    1. 識別限界上下文(Bounded Context)和划分子領域,核心子域,支撐子域,通用子域。
      1. 核心子域:決定產品和公司核心競爭力的子域是核心域,它是業務成功的主要因素和公司的核心競爭力。核心子域包含了業務的核心邏輯和核心的模型。
      2. 通用子域:沒有太多個性化的訴求,同時被多個子域使用的通用功能子域是通用域。通常是提供一些通用的服務,例如日志,鑒權,性能管理,調試等
      3. 支撐子域:還有一種功能子域是必需的,但既不包含決定產品和公司核心競爭力的功能,也不包含通用功能的子域,它就是支撐域。支撐子域的功能甚至不用自己開發,可以直接購買。例如電商里的支付,物流等。
    2. 在識別限界上下文中建立該領域內的統一語言(Ubiquitous Language)
    3. 建立界限上下文之間的上下文關系(Context Map,上下文關系有9種,詳情請參見文獻【7】的第4章)
    4. 根據上下文映射選擇合適的集成類型(Model Integrity,主要是RPC,RESTful API和消息機制,詳情請參見“集成類型Model Integrity常用的架構模式”)

如下圖是一個限界上下文,上下文關系,子域的一個示意圖。

  • 圖中虛線是子域的邊界,實線是限界上下文的邊界
  • 限界上下文之間的連線,代表了上下文關系。一般用“U”代表上游(供應商),"D"代表下游(客戶)。

注意:子域是從要解決的問題的角度划分的,限界上下文是從解決方案的角度划分的。兩者並不一定能一一對應或嚴格包含。
7d7bd86219b9e022fe04ba3555e1fc35_1801x1054.png@900-0-90-f.png

  1. 戰術設計:主要是為每一個子領域進行設計,包括
    1. 識別子域的最核心的業務數據,稱之為聚合根實體
    2. 描述聚合根實體,還需要哪些實體和值對象
    3. 識別這個子域的領域事件,以及這個這個事件的業務流程
    4. 識別觸發領域事件的命令和發出命令的用戶是誰

注意:子域不一定要做的很大,包含很多的功能,有時子域可以簡單到只包含一個模塊,一個算法。這種簡單的子域在實現時可以被實施成一個模塊,從復雜的核心域中分離出來。

分層、六邊形和洋蔥架構

分層架構思想是軟件架構的一個重要而基本的思想。

軟件分層架構的演進

在單機時代,主流的軟件架構是MVC(Model View Control)架構,即數據模型-視圖-控制器模式。這種架構最開始是在C/S體系下做界面UI時的架構。

后來局域網和web1.0的時代,服務器后端軟件依據此思想演進成了控制層-服務層-數據訪問層(Controller-Service-DAO)的3層模式,分別對應MVC中的View層-Control層-Model層。

領域建模的分層模型的思想,也是從MVC的這種架構演進出來的。

916f2bd625dabefee898459336ec9abb_1692x674.png@900-0-90-f.png

DDD中的分層架構

在DDD中,會經常聽說六邊形架構,洋蔥架構和分層架構。示意圖如下。

28d1c15e20d6187b9a9020ad9dc6addc_1704x449.png@900-0-90-f.png

雖然表示的方式不同,但本質是一樣的。都在強調一個分層的思想。

  1. 適配器層或用戶接口層:負責接口轉換,即是最外層請求處理類,將外部請求轉化為內部API能理解的輸入。

    1. 對於gRPC調用來說,是protobuf請求對象轉換處理類;
    2. 對於REST接口可能是一個controller類;
    3. 對於消息機制來說,對應的是消息的監聽器
    4. 如此采用端口適配器模式,可以不影響內部服務,只需在適配器層進行增改。
  2. 應用層:整個系統的功能外觀,封裝了領域層的復雜性並隱藏了其內部實現機制。映射到系統用例模型,意味着系統用例模型中的所有用例都可以在應用層接口中找到對應的方法。組織領域層完成這些功能。

  3. 領域層:實現業務邏輯。包括實體、值對象、領域服務、領域事件。

  4. 基礎設施層:如數據庫相關,消息總線等。這一層接收的是領域層處理后不會發生變化的,需要持久化的數據和信息

這里就回答了第1個問題
問題1. 為什么DDD的架構要表示成六邊形和洋蔥形呢?

  • 這里主要強調各個接口的平等性,每一個外部用戶都是通過接口和適配器與內部交互。例如,對外提供REST服務的API,外部用戶是Web UI,由外部用戶主動發起命令。而數據庫的接口,外部用戶是數據庫,命令則由領域服務發起。雖然方向不同,但是在DDD中都認為是外部服務的接口。
  • 類似於4+1視圖中的用例視圖的表示方法,一般把對領域服務發起命令的外部用戶放在左邊,而對接收領域服務命令的外部用戶放在模型的右邊。
  • 因此,不管是六邊形和洋蔥形,其目的都是把系統分為外部和內部。
  • 這里還有一層意思,就是 “依賴反轉”或“依賴倒置” ,即外部的用戶不依賴於領域服務的內部實現,而只依賴於接口定義。這樣,可以有效的隔離領域內的業務邏輯和用戶的業務邏輯,做到解耦。

關於六邊形架構,還有一個問題

問題2. 從六邊形圖來看,有領域層的概念么?如果有的話,用戶接口層能否直接訪問領域層?

  • 在《實現領域驅動設計》【文獻6】中,六邊形圖中,中間的那個六邊形“應用程序”就是應用層。其內的領域模型,就是領域層。
  • 分層架構也有嚴格分層架構和松散型分層架構。在嚴格分層架構中,是不允許跨層調用的,需要跨層時,還需要一層包裝,轉接一下。在松散型分層架構中,由上到下可以跨層相調用。
  • 一般的做法還是嚴格型的分層架構。這樣有利於權限和邏輯控制,接口的管理

下面這個圖更加形象的表示了六邊形架構的原理
74e70cd681bf3e6512f43faa31827514_487x365.png@900-0-90-f.png

領域層、領域對象和領域服務

領域層是整個系統的核心,實現核心業務邏輯。領域層中包括

  • 領域對象(Domain Object)

    • 這個領域最核心的實體,稱之為聚合根實體(Aggregate Root Entity)
    • 和這個根實體包含的子實體對象(Entity)和值對象(Value)。
    • 實體對象有唯一標識,判斷是否是同一個實體,需要從ID來判斷。
    • 值對象只是一個值和操作這個值的方法,通常作為實體對象的屬性。判斷是否是同一個值,從值相等性判斷。
    • 操作這些對象的方法,簡單的操作方法或只是本對象操作的原子方法放在實體或值的類里實現。
  • 聚合(Aggregate)

    • 根實體是領域層中最核心的對象,根實體往往比較復雜,包含很多屬性和邏輯,一般會分為一些子實體和值
    • 在操作根實體時(增刪改查等),需要保證這些子實體,值可以一起提交,如果只提交了一部分,會造成數據的不一致
    • 一般通過事務會話Transaction Session機制來保證提交的一致性,如果中間某個操作失敗,會回滾所有同一事務內的操作
    • 這個對數據一致性的封裝就成為聚合。
    • 往往一個領域內有一個或多個聚合包。每一個聚合包中都有一個聚合根實體。
  • 除了這些實體和值這些領域對象之外,還有

    • 領域服務:復雜業務邏輯,組合業務邏輯和跨多個實體或值對象的方法放在領域服務中。

    • 領域事件:當方法執行后,需要通知其他領域做響應動作,這時會發出一個事件(通常通過消息總線,例如kafka),這個事件稱為領域事件

  • 工廠(Factory)

    • 在一個業務邏輯比較復雜的領域中,根實體或子實體,值對象等,本身存在着一些業務和邏輯上的關系,約束,缺省值等,這時,創建一個實體或對象就會變得比較困難,這時,往往需要一個工廠類(Factory)或創建者(Builder)來一次性創建滿足數據一致性的整個聚合中的實體,包括根實體和子實體。
  • 資源庫(Repository)

    • 資源庫是在領域對象和持久化數據對象之間的建立一個映射,封裝數據持久化的接口。使得領域服務不需要考慮與數據庫、消息或存儲之類的數據持久化的連接,數據操作,事務會話等問題。

如下圖,展示了在互聯網開發中各個層的實現方式和各層包含的元素。

733459fb3b30ac7ed63fbc009212c60e_1332x985.png@900-0-90-f.png

四種領域模型

在DDD中,針對領域中的實體對象,有四種實現方式,分別是:

  1. 失血模型:領域對象中只有對屬性Getter和Setter的方法,所有的業務邏輯都在領域服務中實現
  2. 貧血模型:領域對象中除了對屬性Getter和Setter的方法,還包括對本對象的一些原子操作,組合業務邏輯和復雜業務邏輯處理都在領域服務層中實現
  3. 充血模型:絕大多數業務邏輯都放在領域對象中處理,包括數據持久化的邏輯,領域服務層很薄,只封裝事務和少量邏輯。
  4. 脹血模型:沒有領域服務層,所有邏輯都在領域對象中實現。

4671c2546a469b09d029f787c739fbd4_975x543.png@900-0-90-f.png
這里,我們就可以解答問題3了。

問題3. 四種領域模型,應該優選那種模型?
在實際的使用中,一般都在貧血模型和充血模型中間選擇。

  • 當領域實體比較簡單時,只有原子操作,沒有復雜邏輯時,充血模型比較合適。
  • 但是當實體之間存在復雜的,組合邏輯時,選擇貧血模型就會比較好。

集成類型Model Integrity常用的架構模式

在微服務的實施中,有幾個比較流行的架構模式。這里列出了常用的一些架構模板,雖然我們不能在嵌入式開發中直接使用這些模板,但是其中有很多的思想我們是可以借鑒的。

  • 事件驅動架構(EDA) :基於消息總線的發布/訂閱模型或事件流模型,對訂閱的的消息進行捕獲、通信、處理和持久保留。
  • 命令和查詢職責分離(CQRS) :把對象的操作分成命令(不返回結果,會改變對象狀態)和查詢(不改變對象狀態,但返回數據)兩個部分。用不同的方法來實現。
  • 事件溯源(ES) :使用append-only的存儲,把對數據的所有操作存儲在一個事件表中,而不是直接更改數據庫內容。這個事件可以作為領域事件通過消息總線發送出去,其他的服務進行響應,例如,可以更新數據庫中物化視圖(Materialized View)來提供快照數據。這種模式適合需要對過程進行記錄,重放和審計的場景。
  • 響應式架構和Actor模型(RAA) :響應式架構是指業務組件和功能由事件驅動,每個組件異步驅動,可以並行和分布式部署及運行。Actor是一種無鎖的並發代理對象。Actor系統通過代理管理,任務管理和分發的框架(例如Akka)把任務分發給各Actor代理,並接收Actor代理執行的結果。
  • 具象狀態傳輸(REST):通過一個統一的URL定位到資源后,將操作通過動作詞(GET/PUT/POST/DELETE)進行傳遞,從而讓資源與操作相互隔離。
  • 面向服務的架構(SOA):將應用程序的業務功能封裝成“服務”,以接口和契約的形式暴露並提供給外界應用訪問(通過交換消息),達到不同系統可重用的目的。
  • 微服務架構(Microservices) :是指開發應用所用的一種架構形式。 通過微服務,可將大型應用分解成多個獨立的組件,其中每個組件都有各自的責任領域。 在處理一個用戶請求時,基於微服務的應用可能會調用許多內部微服務來共同生成其響應。 容器是微服務架構的絕佳示例,因為它們可讓您專注於開發服務,而無需擔心依賴項。
  • 無服務器架構(Serverless) :一般來說,Serverless架構分為 Backend as a Service(BaaS,后端即服務) 和 Functions as a Service(FaaS,函數即服務) 兩種技術。Baas的應用架構由大量第三方雲服務器和API組成的,應用中關於服務器的邏輯和狀態都由服務提供方來管理。FaaS指開發者可以直接將服務業務邏輯代碼部署,運行在第三方提供的無狀態計算容器中,開發者只需要編寫業務代碼即可,無需關注服務器,並且代碼的執行它是由事件觸發的。典型代表是AWS的Lambda服務
  • 防腐層模式(Anti-corruption Layer) :通過在舊系統和新系統之間使用防腐層來隔離它們。該層轉換兩個系統之間的通信,允許舊系統保持不變,同時可以避免損害新應用程序的設計和技術方法。

微服務架構

下圖所展示的是一個典型的微服務架構。每個業務邏輯都被分解為一個微服務(不同顏色的六邊形塊),微服務之間通過REST API通信。一些微服務也會向終端用戶或客戶端開放API接口。但通常情況下,這些客戶端並不能直接訪問后台微服務,而是通過API Gateway來傳遞請求。API Gateway一般負責服務路由、負載均衡、緩存、訪問控制和鑒權等任務。
c3045bc660bfb6ff8f08b78518c95777_500x291.png@900-0-90-f.png

微服務架構有什么優勢

在2010年開始,掀起了異常從單一式架構到微服務躍遷的熱潮,越來越多的公司花費了大量的人力物力財力來完成此次架構躍遷,那么與單一式架構相比,微服務架構有什么優勢,值得大家花這么大的精力去做遷移呢?
下圖是單一式架構和微服務架構的比較

44e95855e9927a50af124bf123da96d6_1791x941.png@900-0-90-f.png
總結:微服務使得獨立部署,獨立升級和獨立擴展變得十分的容易,新產品上市的速度大大加快,軟件系統的TCO(Total cost of Ownership總體擁有成本)成本也會因此而降低。

DDD和微服務的關系

從上面微服務的體系架構圖中,我們可以看到,微服務的體系架構與DDD中的分領域,分層,領域事件驅動,數據和事務一致性等等原則是完全吻合和一致的。

  • 每一個微服務就是一個領域或子域,圖中核心區域的四個六邊形顏色塊就是核心域,而下面的Management, Service Discovery等都是通用子域。
  • API 網關,包括了暴露出來的RESTful API,負載均衡,鑒權等是接口層和應用層
  • 基礎設施層被包在了微服務中。

二者之間的關系可以理解為:DDD是設計方法,而設計出來的架構就是微服務架構。

3. 在嵌入式系統中使用DDD Lite進行設計

嵌入式系統中使用DDD,有很多地方可以借鑒互聯網架構模式,也有很多不同點。 首先,我們看一下嵌入式系統的一般特征。

b448dceb4c9ad21a26e940ae56f0407a_876x737.png@900-0-90-f.png

  1. Linux Kernel是一個宏內核,內核程序都在一個進程空間內。由內核驅動程序負責驅動硬件,包括SoC芯片和設備。
  2. HAL層包含對硬件的抽象,提供接口給上層的lib和應用框架層調用,對內核程序調用一般是通過設備節點的ioctl socket進行通訊,內核態與用戶態的跨界內存共享可以通過ashmem來處理
  3. 用戶態的Lib層和應用框架層作為HAL層的用戶,調用HAL層的接口
  4. 用戶態可以支持多進程,支持C++,內核態只支持C
  5. 用戶和服務都在一個嵌入式系統中,沒有前端和后端通過互聯網連接的結構。

沒有免費午餐

在最優化理論界,有一個著名的定理,No Free Lunch(NFL)沒有免費午餐定理【9】,意思是沒有任何一種算法或模式可以在所有場景中都能產生准確的結果。
同樣,軟件架構和設計模式也都需要根據場景做出調整,不能1:1照搬。關於DDD,有很多的材料、視頻和賦能宣講,還包括多彩建模等等。也有很多人試圖在嵌入式開發中引入DDD,但是同時也有很多人產生了疑惑,DDD是從互聯網開發和基於微服務的架構中產生和發展的,他是否能夠適合嵌入式開發?

我的回答是,DDD的設計思想、建模方法以及架構模式中的很多內容都可以在嵌入式開發中應用,但是需要一些調整和適應,不能簡單的照搬。

因此,為了適應嵌入式系統的設計,尤其是重構的設計,我們參考了業界的一些優秀實踐,提出了輕量級DDD,即DDD Lite的設計模式。

DDD Lite的設計模式

下面,我們以顯示子系統作為例子,看一下如何使用DDD來進行建模。整個建模分為7步,每一步的第一個英文字母串在一起就是 BUSLANE(公交快速道). 由以下7步組成:

  • B - Boundary of Domain 識別域邊界
  • U - Use Case 用例
  • S - Subdomain 划分子域
  • L - Layered Architecture 分層結構
  • A - Architecture Pattern 架構模式的選擇
  • N - Non-sticky design 非粘性設計
  • E - Entity UML Modeling 實體UML建模

第一步:識別領域邊界(Boundary of Domain)

我們在重構時,一般是針對一個嵌入式系統中的一部分或一個子系統進行重構。在重構時需要遵循一個原則:即無損替換原則。就是只替換這一部分,保持對外的接口不變,整個嵌入式系統在重構之后依然可以工作,不產生功能和性能上的損失。這步工作可以分成以下幾個步驟:

  1. 識別出重構部分的邊界:包括上下左右邊界,這個邊界可以稱之為領域邊界(Boundary of Domain )。(這里沒有用boundary of context,為了避免與bounded context 混淆)這里領域邊界與4+1視圖中的上下文模型(UML Context Diagram)中的邊界(Boundary)是一個概念。
  2. 明確邊界的功能目標和看護:對邊界建立基於接口的測試看護體系,來最終驗證重構是否滿足需求。
  3. 明確邊界的性能目標和看護:對邊界內的子系統,性能方面需要達到什么目標?怎么驗證。

這里,我們把DDD的六邊形或洋蔥形的架構圖改一下,改成一個長方形。

2096a65ef13cdc6ef5ec2a3717046d46_700x672.png@900-0-90-f.png

  • 北向:提供給上層的接口,即向上提供的接口。

  • 南向:調用下層的接口,即向下的調用接口

  • 西向:提供給同層其他子系統的接口,即向左提供的接口

  • 東向:調用同層其他子系統的接口,即向右調用的接口

我們在識別了上下左右的外部用戶和接口之后,這個邊界內的部分,我們就可以作為一個領域來進行建模。進一步划分子域和進行戰術設計。

例如,在顯示子系統中,北向或向上的接口有

  • SurfaceFlinger的接口 - hwc

  • Linux wayland的接口 - drm_client

  • displayEffect的接口 - displayEngine
    南向接口基本上就是輸出給屏驅動

2a9c85cb94e04eb00b81e97829afd185_477x493.png@900-0-90-f.png

第二步:建立用例視圖(Use Case View)

這一步的目標是讓需求清晰化,為后面的識別限界上下文和聚合提供一個完整的輸入列表。在DDD的戰略層面的建模,就是要和領域專家一起,帶領團隊搞清楚需求(從領域事件風暴開始)。而對於一個重構項目來說,需求基本上是清楚的,不需要重新頭腦風暴來建立。而是需要一種好的工具來清晰的表達出來,讓每個參與人都清楚需求。這里,選擇用例視圖來梳理和展示需求是比較好的辦法,而且與我司的4+1視圖,端到端追溯等要求是一致的。

“架構設計4+1視圖實踐分享(2):我們如何設計用例視圖”【10】中所講述的方法,我們通過從需求的特性建立用例視圖。用例視圖的表現方式有多種,我們一般采用三種方式:

  • UML用例圖:UML用例圖往往只需要畫出最關鍵的不超過20個用例。可以分成概要層和用戶目標層兩層來畫。注意:UML用例圖中的用例(即橢圓型)往往是一個簡單的短語,並不能准確的表達系統的需求,而且不能畫太多的用例,因此,我們需要用例表和用例描述(Spec)來補充描述。

    283d077de6e5c8f03512bb5241a4cbf8_704x444.png@900-0-90-f.png

  • 用例表:把系統中所有的用例,按照外部用戶分組,列在一張表里,除了用例名,還有用例的描述。比UML可以更加全面的展示用例。

    ff1cb338a20aed63a3f92533922958c0_739x507.png@900-0-90-f.png

  • 用例描述:是對每一個用例的詳細的描述,包括用例的名稱,前置條件,后置條件,基本流程,備選流程,異常流程。有了用例描述Spec,工程師可以照此開始編碼實現。

    0be97c0ec42cb0986946c09bb458bc8b_680x464.png@900-0-90-f.png

例如,在顯示子系統中,疊加的UML用例圖可以表示為

edfae9aa6ad6918550c4b1c641373f08_455x317.png@900-0-90-f.png

部分用例表如下:

76840f6456d7f0ea69c5128c481b203d_1231x468.png@900-0-90-f.png

第三步:建立限界上下文和划分子域(Subdomain)

建立限界上下文(Bounded Context)

從第二步中,我們得到了系統最關鍵的用例,之后,我們需要分析每個用例,識別出每個用例中,是哪個命令(或接口)觸發此用例,和這個用例中的核心的實體是什么。在這個過程中,我們會發現,有一些用例的命令和實體都是一樣的,這時,這些用例就應該被合並到一個限界上下文中。

例如,在顯示子系統中,各種疊加的用例,其命令都是由送顯這一個動作發起,其根實體都是幀,每一個疊加的動作都是針對於幀來說的,而且不能都丟掉其中的圖層,這時就有了數據一致性的需求,因此幀就可以作為聚合根實體。
此外,還包括了圖層等子實體,以及圖層的格式,大小,顏色等值實體。

5877521b880616a3dcbe0a3c3abbc5e4_1696x735.png@900-0-90-f.png

上下文關系(Context Map)

在《領域驅動設計精要》【7】中,作者給出了9種限界上下文映射關系,這里,我們在嵌入式系統種主要討論其中的5種:

  • 客戶 - 供應商關系( Customer-Supplier): 描述的是兩個限界上下文之間一種調用關系:供應商位於上游(U ),客戶位於下游(D )。

    • 客戶與供應商之間需要指定接口契約,由供應商來根據契約向客戶提供滿足契約的數據和服務

    • 存在這種集成關系的團隊常常會采用一種被稱為消費者驅動契約( Consumer Driven Contract, CDC )的實踐,通過契約測試來保證上游(生產者或供應商) 和下游(消費者或客戶〉之間的協作。而利用一些工具(如Pact ) ,客戶可以在進行測試時,將對供應商接口的期望記錄下來,並將其變成供應商的接口測試,作為供應商持續集成流水線的一部分持續地進行驗證。

    • 在嵌入式系統中的實踐:供應商需要提供一個接口頭文件,其中包含一個接口類,放到公共的include目錄下。在接口類中可以加入接口的約束驗證。而在接口頭文件中暴露的接口需要遵循迪米特原則(最小知識原則),只需要暴露最小的接口集,而實現的細節需要放在實現類中。命名的方式一般是 xxxInterface 類和 xxxImpl 類。例如,在顯示重構中的networkGenerator類,在公共include目錄下,有一個NetworkGenerator.h頭文件,但是實現的細節,都在NetworkGeneratorWrapper類中

      599332a8e55404540db0a91fdf5b8f23_253x116.png@900-0-90-f.png

  • 跟隨者( Conformist )上游的團隊或限界上下文沒有動機和理由滿足下游團隊的具體需求,下游團隊也無法投入資源去翻譯上游模型的通用語言來適應自己的特定需求,只能是順應着上游的模型來開發。

    • 驅動開發團隊與芯片團隊之間的關系就是跟隨着的關系。驅動只能按照芯片的規格和約束來做。
  • 防腐層( Anticorruption Layer )是最具防御性的上下文映射關系, 下游團隊在其通用語言(模型)和位於它上游的通用語言(模型)之間創建了一個翻譯層。

    • 這種防腐層關系在嵌入式系統中經常使用,尤其是在重構時需要保持向前兼容,做接口轉換和翻譯。
  • 已發布語言( Published Language )是一種有着豐富文檔的信息交換語言,可以被許多消費方的限界上下文簡單地使用和翻譯。

    • 需要讀寫信息的消費者們可以把共享語言翻譯成自己的語言, 反之亦然
    • 這種己發布語言可以用XML Schema 、JSON Schema 或更佳的序列化格式定義,比如Protobuf 或Avro 。
  • 消息機制:消息機制由訂閱-發布模式和定向消息發布等兩種模式

    • 發布-訂閱模式(Message Bus or Pub-Sub):通過客戶端限界上下文訂閱由它自己或另一個限界上下文發布的領域事件( Domain Events ) 來完成的。
      • 使用消息機制是最健壯的集成方法之一,因為發布者和訂閱者可以做到充分解耦,而且無阻塞
      • 消息可以觸發多個訂閱者響應,來完成特定的動作。這個也就是EDA 事件驅動的架構模型
      • 例如,Graphics BufferQueue就是一種嵌入式的發布-訂閱模式。通過生產者畫圖形,添加到隊列中,timeline keeper來根據時間線的設定觸發那些綁定在這個圖形buffer的消費者,同時,通過Fence機制來避免資源的沖突。
    • 定向發布模式:也可以叫做P2P模式,一對一發送模式。只有一個消息發送者和一個消息接收者。P2P模式下,發送者無需事先約定傳輸消息的Topic,發送者可以直接按照規范發送消息到目標的接收者。接收者無需事先訂閱即可接收消息,從而簡化接收者的程序邏輯,節省訂閱成本。
      • Actor模式可以理解為是一種定向發布模式,Actor System定向發送任務給每個worker的郵箱,並從worker那里得到運行結果。
      • 定向發布模式,發送端可以控制發送的時序和邏輯。而發布-訂閱模式,則是一種無時序的響應模式。
      • IoT物聯網中經常運用此模式來完成IoT設備的數據上報和指令下發。MQTT是其中一種應用最廣泛的一個消息協議和中間件。

顯示子系統部分上下文關系圖如下:
ca118531dcad8af1d47a1499be12fe8a_1687x1032.png@900-0-90-f.png

注意: 在開始建模時,我們不太可能一下子把整個系統的全貌和各個上下文之間的關系搞得非常清楚和精確。這個過程是一個逐步修正的過程。“Context Map上下文關系圖是一個識別限界上下文邊界的工具,通常是在一塊白板上邊討論邊添加內容,它並不是一個嚴格定義的UML圖。它可以讓我們對各個上下文之間的關系進行一個模糊的映射,而且從某種程度上說,一定的模糊度是必要的”。【5】

划分子域(Sub Domain)

從第2章DDD的要點介紹中,可以知道一般我們會對領域進一步划分為子域,子域又分為三種,核心域,支撐子域,通用子域。我們在實際的建模中,應該如何華為子域呢?

我們還是根據第二步用例視圖中得到的用例,來划分一下限界上下文 BC,然后把BC按照功能組合並成子域。例如,在顯示子系統中,疊加預處理,疊加送顯,送顯的通道選擇等都分別是限界上下文,但是這些BC都屬於疊加子域。

得到了子域的划分之后,如何判斷是核心子域還是其他的呢?主要看這個子域是否是核心的業務。

例如,顯示子系統,核心的業務是疊加,效果,連接,和屏這個業務鏈。在這個業務鏈上的子域都是核心子域,因為這些子域可以體現出這個子系統的競爭力。而配置,日志,DFX維測系統這些公用的功能,都應該屬於通用子域。而屏的驅動程序,MCU小核或用到的二進制包等這些外部依賴或核心業務之外的部分,都屬於支撐子域。

第四步:對子域進行分層設計(Layered Architecture)

在第三步中,我們划分好了子域,接下來,我們按照DDD的分層架構的思想,對子域進行架構。例如,對顯示疊加子域,我們可以用分層的架構來對子域進行划分。左邊是嚴格分層的架構,右邊是六邊形架構(或洋蔥架構)。我們一起看一下:

  1. 接口層:包括了SurfaceFlinger對接的接口hwc和Linux Wayland接口的DRM client. 主要的作用是一個適配器的功能,接收外部客戶發出的命令,然后調用應用層進行處理,並且把結果返回給外部客戶。
  2. 應用層:接收到接口層的命令后,對每個顯示設備,調用領域層的服務進行預處理和送顯,然后,把結果返回給接口層。
  3. 領域層:這一層包含整個子域的核心邏輯
    1. 重構之前很多的業務邏輯被實現在了內核層,這樣做會使得內核過於龐大復雜,而且由於開源責任,需要公開內核源碼,容易暴漏業務關鍵技術。
    2. 重構后,核心業務邏輯被放在了HAL層。這一層主要負責選擇合適的策略,管理算子狀態,生成疊加網絡,進而生成cmdlist。
  4. 基礎設施層:基礎設施層通常理解為把數據和信息進行持久化。即數據傳或信息送給基礎設施層后,就不會發生變化了。
    1. 那么在嵌入式系統中,我們在領域層可以處理業務邏輯,包括選擇疊加策略,生成疊加網絡和生成cmdlist。這些實體在領域層是變量,即可以發生變化,例如,在選擇疊加策略時,我們需要檢查算子可用狀態和場景,來確定每個圖層是怎么疊加的,這里,需要嘗試走在線,不行走離線,再不行走GPU等,這是一個變化和嘗試的過程。一旦這個過程結束了,那么疊加策略和命令已經確定了,就需要驅動芯片來執行了。這時,數據,圖片和命令就已經確定了,不會改變了。
    2. 基礎設施層,按照洋蔥模型,也是另一個適配器,外部的用戶是芯片或連接器(MIPI/DP)。這里,基於依賴倒置原則,我們把它分成了兩個子層,內核接口層,主要是內核驅動的設備節點。內核層是真正的內核驅動的實現

588b097682146e76aabfc796835c9a88_1750x902.png@900-0-90-f.png

第五步:為子域選擇合適架構模式(Architecture Pattern)

這里,架構模式除了我們知道的23種設計模式之外,我們着重討論以下互聯網開發的一些模式,以及如何在嵌入式系統中使用。

事件驅動架構(EDA)

基於消息總線的發布/訂閱模型或事件流模型,對訂閱的的消息進行捕獲、通信、處理和持久保留。

事件驅動編程通常只是用一個執行過程,在處理多任務的時候,事件驅動編程是使用協作式處理任務,而不是多線程的搶占式。事件驅動簡潔易用,只需要注冊感興趣的事件,在回調中設計邏輯就可以了。在調用的過程中,事件循環器(Event Loop)在等待事件的發生,跟着調用處理器。事件處理器不是搶占式的,處理器一般只有很短的生命周期。
Linux操作系統本身其實也是基於事件驅動架構的。

事件驅動編程的優勢
  • 在大部分的應用場景中,事件編程優於多線程編程。
  • 相對於多線程編程來講,事件驅動編程比較容易,復雜度低,開發者樂於接受。
  • 大多數的GUI框架,都是使用事件驅動編程架構的。每一個事件會綁定一個處理器,這些事件通常是點擊按鈕,選擇菜單,等等。處理器來實現具體的行為邏輯。
  • 事件驅動經常使用在I/O框架中,可以很好的實現I/O復用。很多高性能的I/O框架都是使用事件驅動模型的。
  • 易於調試。時間依賴只和事件有關系,而不是內部調度。問題容易暴露。
事件驅動編程的劣勢
  • 如果處理器占用時間較長,那會阻塞應用程序的響應。
  • 無法通過時間來維護本地狀態,因為處理器必須返回。
  • 通常在單CPU環境下,比多線程編程要快,因為沒有鎖的因素,沒有線程切換的損耗。CPU不是並發的,這樣的話就不適合用在一些科學計算的應用中。

那么, 在我們在嵌入式開發中,有沒有一個可以參考的EDA的例子?

【例子1】顯示系統中用的DMA-Fence

70bc0533603f846f8e5f223f4bab3a28_1024x821.png@900-0-90-f.png

這個例子的實現,實際上是一種中斷驅動的架構,從大的架構思想上,也是事件驅動架構的一種。其基本原理是:

  • 類似graphics buffer queue, 一次性申請多塊DMA Buffer或將來自其他模塊的DMA buffer,加入到隊列中,需要使用buffer時到隊列中去申請。不用時釋放回到隊列中

  • 注冊Buffer的消費者和回調:當一塊buffer申請到后,注冊此buffer的消費者,回調函數和回調時間點,並加回到隊列中

  • DMA Buffer隊列管理者負責建立和管理時間線(timeline),每一次中斷,時間線加1,同時比較隊列中各buffer的注冊消費者的時間點和fence狀態是否可以觸發(Signal),如果滿足,則觸發回調函數

  • 消費者的回調函數被觸發,則加fence,開始操作buffer

  • 操作結束,釋放控制權,減fence

  • 沒有fence的buffer可以被分配給下一個生產者

具體可以參見利用fence框架支持生產者消費者模型同步技術

注意:這種基於隊列,回調函數,觸發Signal和Fence的模式,可以很好的解決多個進程對同一資源的操作時的沖突和時序的問題。

【例子2】基於消息總線的軟件框架

另外一個可以參考的例子是基於消息總線的軟件框架。這個框架可以支持進程內、跨進程和基於網絡三種不同的消息通道。三種不同的消息通道使用一個消息總線管理器管理,收發消息使用統一的接口。

這個例子可以說是一個標准的基於消息總線發布-訂閱(pub-sub)模式的事件驅動架構。

具體可以參見“基於ZeroMQ的嵌入式跨平台消息總線軟件框架”

37f50d8c9c0b443ffa7ee66aea343450_900x274.png@900-0-90-f.png

響應式架構和Actor模型(RAA)

響應式架構是指業務組件和功能由事件驅動,每個組件異步驅動,可以並行和分布式部署及運行。Actor是一種無鎖的並發代理對象。Actor系統通過代理管理,任務管理和分發的框架(例如Akka)把任務分發給各Actor代理,並接收Actor代理執行的結果。

  • Camera組的實踐(待補充)

命令和查詢職責分離(CQRS)

把對象的操作分成命令(不返回結果,會改變對象狀態)和查詢(不改變對象狀態,但返回數據)兩個部分。分成不同的類來實現。每個類只負責單一的職責。這和我們所說的SOLID中的單一責任原則的思想是一致的。
深入分析一下,CQRS有2個層面的好處或應用場景:

  • 減少不穩定依賴:每個職責(查詢和命令)的依賴對象不同,所以可能對該類產生的變化需求的事件和方向也不同,分離后,可以把每個職責的不穩定依賴降低到一個方向或維度。
  • 實現冷熱模塊和數據的分離:每個職責的熱度不同,有可能造成負載不同。如果不進行分離,那么這個模塊會處於兩個熱度疊加的狀態。而進行了責任分離后,兩個模塊的熱度都會有所降低,而且熱度大的職責可以給與更多的資源,來優化效率。
【例子1】Vulkan Renderpass 查詢與控制分離

由 付強 提供
在改造前,Renderpass類負責兩個方面的工作:

  • 關聯Vulkan 標准,接收來自Vulkan API的狀態查詢指令,返回FrameBuffer和Pipeline等相關對象的狀態
  • 工作流邏輯控制,通過commandBuffer控制流程和desc的配置

這兩個方面的工作,

  • 一個是負責狀態查詢,並不修改內部各模塊的狀態。這個部分要與vulkan API標准進行關聯,當API標准升級時,這個部分也要而做出相應的改變。
  • 而另一部分工作是命令工作,只負責按照workflow Logic組織命令,發出命令。這部分工作與Vulkan API的標准無關,與內部的模塊的邏輯相關聯。

47a3c544beee147ff4dad076a5f248e8_1662x840.png@900-0-90-f.png
這個模塊的兩個方面的變化不是同步的,任何一個方面發生了變化,這個類就要改動,而這也違反了單一責任原則(SRP)。
在經過了CQRS改造后,把原來的Renderpass類分成了兩個類,一個是Renderpass負責第一個職責,對接Vulkan標准,處理狀態查詢。另一個新分出來的類RenderpassInstance負責第二個職責,控制業務流和desc配置。

【例子2】冷熱分離的例子

(例子待補充)

面向服務的架構(SOA)

將應用程序的業務功能封裝成“服務”,以接口和契約的形式暴露並提供給外界應用訪問(通過交換消息),達到不同系統可重用的目的。

(例子待補充)

防腐層模式(Anti-corruption Layer)

通過在舊系統和新系統之間使用防腐層來隔離它們。該層轉換兩個系統之間的通信,允許舊系統保持不變,同時可以避免損害新應用程序的設計和技術方法。

第六步:子域之間的非粘性連接和非耦合接口(Non-sticky Connection Non-coupled Interface)

非粘性連接設計

粘性會話(Sticky Session)在互聯網領域中指負載均衡器為一個客戶client,分配一個固定的服務器,一直服務該客戶,直到會話結束。粘性會話的好處是用戶體驗會比較好,比較穩定,而且網絡資源的優化也比較容易。

但是,問題也很明顯,就是一個客戶獨占了一個資源不釋放,當這樣的會話越來越多時,服務器負載就會處於不均衡的狀態。

對應於嵌入式,我們也有類似的問題,例如,在重構前的顯示系統中,軟件為每一個顯示設備分配了一個固定的硬件通道,只要是這個設備過來的圖層,都會走這個通道,通道中的算子也是固定的,即便是這個設備沒有過來任何圖層,這個通道也依然保留,不能給其他設備使用。

這個問題,我們可以借鑒互聯網的名詞,稱之為粘性連接設計。即疊加子域和外部客戶之間有一個粘性連接。

非粘性連接設計,就是要打破這種固定通道,固定算子的模式,而采用配置驅動開發(CDD)的模式,預先設定幾種疊加模式,由策略管理器來決定選用那種模式,選用了模式后,我們可以采用靜態的算子通道配置表或動態生成算子網絡的方式,來動態優化算子的負載。

4d1449a90089fbf17ce900917f44c822_581x713.png@900-0-90-f.png

非耦合接口

子域之間的連接,都要遵循依賴反轉原則,即:

  • 高層次的模塊不應該依賴於低層次的模塊,兩者都應該依賴於抽象接口;
  • 抽象接口不應該依賴於具體實現。而具體實現則應該依賴於抽象接口。

這種接口,我們就可以稱為非耦合接口。
非耦合接口還有一個重要的原則,就是契約式設計 Design By Contract:

  • 對於Client-Server方式的調用,把server是現成一個接口類和實現類的方式,把雙方的調用約定(前置條件,動作和后置條件)放置在接口類中。
  • 由接口類來約束client的調用和server動作和返回的行為
  • 契約轉包:當接口類由另一個實現時,新的實現類可以有不同的前置條件,動作和后置條件,但是他的前置條件必須弱於接口類的條件,而后置條件必須強於接口類的后置條件
  • 遵循里氏替換原則:一個類的使用者可以使用這個類的子類,即子類中不應該出現與父類不一致的行為。

在這種契約式編程中,我們對父類進行測試,斷言判斷來約束整個子類的行為模式。

如何來理解這種契約轉包呢?
契約轉包的意思是,接口類中規定了最基本的約束,當一個實現類需要實現自己獨有的約束時,它可以增加約束。但是需要滿足里氏替換原則-也就是子類可以替換父類的作用。子類可以處理的情況需要包含父類的所有情況。那么子類約束的前置條件需要弱於父類。后置條件是相反的。后置條件的意思是,當類處理約束完畢后,需要判斷它的輸出是否實現了所有的后置條件,那么為了保證所有父類可以處理的情況被子類處理后不會產生問題,子類對輸出的要求標准應該包含父類的后置條件。例如,父類的輸出需要滿足2項條件,而子類的輸出在這2個條件的基礎上,需要滿足第3條。那么如果子類替換父類時,輸出的結果標准高於父類的要求,也就是父類的后置約束一定會滿足。

第七步:域實體和域服務的UML建模(Entity UML Modeling)

我們通過前六步,已經把一個系統分解到了子域,子域中已經識別出了根實體,子實體,值實體和領域服務,下一步要做的就是把這些設計進一步細化,達到可以編碼的程度,進而可以使用工具生成代碼。

從設計到開發的銜接

軟件開發時,通常是架構師和領域專家,高級開發人員一起,把整個系統從需求特性分析和設計架構,設計到L2層(L2層是組件層,除了組件,還需要設計到子組件,以及組件中的一些關鍵模塊)。這時,架構師會召開評審會,在這個層次上論證系統架構的可行性。

這個評審過了之后,就會進入到編碼階段,在編碼階段,會把一個子組件或模塊交給一個核心開發人員,接着做編碼層面的設計,完成關鍵的UML建模(類,時序和組件圖),然后再交給架構師評審,評審通過后,才可以開始編碼。
cb9890437ddc848ae4fa094fa8623e45_740x599.png@900-0-90-f.png

為什么還需要用UML建模

我們就要用到部分UML圖作為工具來幫助我們整理清思路,清楚的表達划分為幾個類,每個類之間的關系,以及類之間的調用順序等一些實現層面的問題。

有人可能會有疑問,DDD本身就是一種架構設計的方法,UML也是一種建模方法,兩者之間有什么關系?為什么用了DDD設計,還需要UML建模?
這里,我給出我的看法:

  • 從作用上看:
    • 領域驅動設計是在軟件需求分析和設計架構上提出的一些做法。
    • UML是表達分析和設計的可選表示法。因為一些UML圖在表達類的構成,關系,時序方面可以非常清晰和嚴謹,還有很多的工具可以使用,因此,選用UML來作為編碼層面的工具是十分合適的。
  • 從階段上看:
    • 領域驅動設計是架構設計層面的方法(從需求分析到L2的設計)這個工作往往是架構師完成的。
    • UML是開發設計層面用到的分析和表達工具,是開發人員需要用的。

如何建模

這里,主要用三個UML diagram。例如,顯示子系統中的預處理和送顯的部分組件圖,時序圖和類圖

Component Diagram 組件圖

d0a0d2ca4d4ae287d9c48ea15f16c2b7_1223x572.png@900-0-90-f.png

Sequence Diagram 時序圖

5ae3638349cd7da1c394b312e4b28097_1015x523.png@900-0-90-f.png

Class Diagram 類圖

3ce230f8d3d6998fe66a7f2bdaea0c37_398x477.png@900-0-90-f.png
到這里,我們看一下第4個問題:

問題4:領域和功能模塊有啥區別?

  • 領域是一個更高層面的封裝,里面包括了實體對象,值對象,領域服務,領域事件還有資源庫等多個部分,這每個部分的實現都是一個或多個類,最終,通過服務類來進行跨實體和值進行業務操作,完成領域的業務邏輯。所以可以說領域包含了功能模塊。(在極致的面向對象里,所有的功能都是類來包裝的,功能是包裝在類里的)
  • 如果從需求分析的角度,領域與之前的功能群組(Function Group, 即在單一式架構下,跨層的屬於同一個業務的模塊組成功能群組)的概念類似。實現方式不同,在DDD的設計中,屬於同一個業務的部分是被獨立封裝在一個域中,屬於緊密集成的關系。而在單一式架構的功能群組中,每個功能組不同層的部分與同層的屬於其他功能組的模塊緊密集成,而功能組只是一個邏輯的概念。

4. 總結

本文以顯示重構為例子,提出了將輕量級的領域驅動設計(DDD Lite)應用於嵌入式系統開發的模式,提出了BUSLANE的7步建模方法,這種建模方法是一套比較具體的,具有實際指導意義的方法,希望可以幫助大家提升對DDD軟件設計應用的理解。
希望在本文結束時,大家對問題5和6有了一定的解答。

問題5. DDD看上去與嵌入式開發挺契合的,但是結合業務,怎么理解和運用?
問題6. DDD的格局有點大,思維的方式很好,但是套到我們的驅動開發上,是否能很好的匹配?

  • 本文中所提出的BUSLANE七步建模的方法,應該是可以落地的一套可實施的方法。
  • 但是在運用到其他的子系統過程中,可能會有一些需要適配的地方。我們可以按照DDD的思想,結合業務實際,對DDD進行一定的改造,使之適合我們的開發的實踐。

參考文獻:

【1】https://microservices.io/

【2】https://martinfowler.com/articles/microservices.html

【3】https://www.n-ix.com/microservices-vs-monolith-which-architecture-best-choice-your-business/

【4】https://hjwjw.github.io/posts/156e5ee9/#%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84

【5】《領域驅動設計》,Eric Evans, 2004

【6】《實現領域驅動設計》,Vaughn Vernon

【7】《領域驅動設計精粹》,Vaughn Vernon

【8】cloud design pattern, https://iambowen.gitbooks.io/cloud-design-pattern/content/
【9】 Wolpert, D.H., Macready, W.G. (1997), “No Free Lunch Theorems for Optimization”, IEEE Transactions on Evolutionary Computation 1, 67.

【10】Hongjie Liu, Danny Song, 架構設計4+1視圖實踐分享(2):我們如何設計用例視圖, http://3ms.huawei.com/km/blogs/details/9714139?l=zh-cn

【11】https://c4model.com/
【12】Evans, 領域驅動設計,材料見附件“精簡版(中文)DDD_Domain_Driven_Design.pdf”
【13】 Peter Coad,Eric Lefebvre,Jeff De Luca著,彩色UML建模(四色原型)Object Modeling in Color


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM