[IT] 領域驅動設計 Domain-Driven Design

「領域驅動設計」這個詞是在 Eric Evans 在他的著作《領域驅動設計:解決軟體核心的複雜性(Domain-Driven Design: Tackling complexity in the Heart of Software, 2003)》中所提出的,書中正式地提出了許多軟體開發的概念。 我無法用一篇文章就概括 DDD,與 DDD 相關的重要概念實在太多了。以下列出我認為一些重要的 DDD 概成,包含: Ubiquitous Language Layers Bounded Contexts Anti-Corruption Layer Shared Kernel Generic Subdomain 通用語言 Ubiquitous Language 在軟體開發中,一個常見的問題是怎麼理解程式碼,它是什麼,它能做什麼,它如何做,它為什麼這樣做…如果程式碼使用的術語與領域專家使用的術語不同,理解程式碼便變得更加複雜。例如,如果領域專家談論的是 elder users,而程式碼中提到的是 supervisors,那麼名詞可能就會造成在討論應用程式時造成混淆。然而,大部分的模糊性可以透過適當地命名類別和方法來解決,讓類別的命名明確地表達出物件是什麼,讓方法的命名明確地表達出方法在領域上下文中做了什麼事。 使用通用語言的主要概念是將應用程式與商業邏輯對齊,這是通過在程式碼中採用業務與技術之間的共同語言所實現的。該語言的來源是公司的業務部門,它們擁有需要實施的概念,但術語則與公司的技術部門協商(這意味著業務部門並不總是選擇最佳命名)以創建一種業務與程式開發人員共通且不會發生歧義的共同語言。包含程式碼、類別、方法、屬性和模組的命名都最重與通用語言對齊。 層 Layers 我在之前的文章中已經談過分層的概念,但我認為此刻重提由DDD所識別的各層是很重要的 User Interface 使用者介面 負責繪製用戶與應用程式互動的螢幕,並將用戶的輸入轉換為應用程式命令。值得注意的是,「用戶」可以是人類,但也可以是連接到我們API的其他應用程式,這完全對應於EBI架構中的邊界對象。 Application Layer 應用層 協調領域對象以執行用戶所需的任務:用例。它不包含業務邏輯。這與EBI架構中的互動者相關,只是互動者是與UI或實體無關的任何對象,而在這種情況下,應用層只包含與用例相關的對象。這一層是應用服務所屬的地方,因為它們是用例協調發生的容器,使用存儲庫、領域服務、實體、價值對象或任何其他領域對象。 Domain Layer 領域層 這是包含所有業務邏輯的層,包括領域服務、實體、事件以及任何其他包含業務邏輯的對象類型。顯然,它與EBI的實體對象類型有關。這是系統的核心。領域服務將包含不完全適合於實體的領域邏輯,通常在完成某些領域動作時協調多個實體。 Infrastructure 基礎建設 支援上層的技術能力,即持久性或訊息傳遞。 有界上下文 在企業應用中,模型可能會大幅增長,同時進行程式碼開發的團隊規模也可能會擴大。這帶來了兩個問題: 開發人員必須處理的程式碼庫越大,認知負荷就越大,理解程式碼的難度也就越高,因此可能會引入更多的錯誤和判斷失誤, 越多的開發人員在同一個程式碼庫上工作,就愈難協調對應用程式的共同技術與領域視野。 換句話說,手頭的問題變得過於龐大。 對於大問題的常見解決方案是將其分解成較小的部分,這正是「有界上下文」發揮作用的地方。 Two subsystems commonly serve very different user communities - Eric Evans 2014, Domain-Driven Design Reference 兩個子系統通常服務於截然不同的用戶群體 - Eric Evans 2014,領域驅動設計參考 ...

October 26, 2023 · 1 分鐘 · hgraca

[IT] 套件與命名空間 packaging & namespacing

系統的架構是該系統的高層視圖、大局觀,以粗略的筆觸描繪的系統設計。架構決策是系統中的結構性決策,影響整個程式庫的決策,也是定義其他所有元素將在其上建立的決策。 架構決定系統的許多元素,包含: 組件 Components 組件之間的關係 Relationships between components 指導組件與組件間關係如何設計與如何演化 Principles guiding the design and evolution of components and relationships 換句話說,這些是隨著系統演進更難改變的設計決策,它們是支撐功能開發的基礎。 義大利麵架構 Spaghetti Architecture 有些專案,結構隨機,既不反映架構,也不反映領域。如果我問「我應該把這個 value object 放在哪裡?」結果得到「把它放在 src 資料夾的某個地方」這樣的回答;如果我問「執行這個邏輯的 service 在哪裡?」卻得到「用你的 IDE 進行搜索」這樣的回答。這意味著專案沒有經過組織,這樣鬆散的結構就稱為義大利麵架構(Spaghetti Architecture)。 這是一個大問題,因為這意味著沒有套件模組化,高階的程式碼關係和流程並沒有可以遵循的邏輯結構,導致模組之間高度耦合且低內聚,實際上可能代表根本沒有模組,應該屬於模組的程式碼散佈在整個程式碼庫中。 可維護的程式碼庫 擁有一個可維護的程式庫意味著我們可以變更最少的程式碼來實現最大的概念變更。換句話說,當我們需要對一個程式碼單元進行變更時,我們應該盡可能少地對其他程式碼單元進行變更。 這樣帶來的優點有: 程式碼的修改變得簡單,因為它們對較少的程式碼產生影響。 程式碼的修改會更快,因為需要修改的程式碼較少。 因為修改的程式碼變少,出現錯誤的可能性也更低。 封裝(encapsulation)、**低耦合(low coupling)和高內聚(high cohesion)**是使程式碼隔離的核心原則,使得我們能夠擁有可維護的程式碼基礎。 封裝 Encapsulation 這是隱藏類別的內部訊息與實作的過程。 也就是說,它對外隱藏了實作的方式,使得一個類別的內部結構可以自由變更,而不會影響使用這個特定類別的其他類別。 低耦合 Low coupling 耦合是指一個程式碼單元與另一個程式碼單元的關係。如果對一個模組的更改將導致對另一個模組的更改,則該模組被認為與另一個模組高度耦合。而如果一個模組獨立於任何其他模組,則該模組被認為是低耦合的。這可以通過擁有一個穩定的介面來實現,有效地隱藏了對其他模組的實現。 低耦合的好處 可維護性(maintainability) - 變更僅限於單一模組 可測試性(testability) - 可以將單元測試涉及的模塊限制到最小 可讀性(readability) - 需要分析的類別被保持在最小範圍內 高內聚 High Cohesion 內聚性是指一個模塊的功能之間的緊密相關程度的衡量。低內聚是指模組間具有許多不相關的職責;高內聚是指模組間有類似的概念。 高內聚的好處 可讀性(readability) - 相關的功能都包含在單一模塊中 可維護性(maintainability) - bug 通常會被限制在單一模組中 重用性(reusability) - 專注於類別的功能,不被無用的功能污染 結構上的影響 前述的原則通常與類別相關,然而,它們對於類別群組也同樣適用。類別群組在一般情況下被稱為套件(package),但如果它們具有純粹的功能性目標(例如 ORM),我們可以更具體地稱呼它們為模組(module);如果它們具有領域目標(例如 AccountManagement),我們可以稱呼它們為組件(components)。這與 Bass, Clements 和 Kazman 在他們的書《實踐軟體架構(Software Architecture in Practice)》中解釋的定義是一致的。 ...

October 23, 2023 · 3 分鐘 · hgraca

[IT] EBI 架構

EBI 架構全名是 Entity-Boundary-Interactor Architecture,第一次由 Robert C. Martin 在他乾淨架構(Clean Architecture)中的演講中提到。 然而,EBI 架構正式的發布是來自 Ivar Jacobson 在 1992 年所出版的 《物件導向的軟體工程:用例驅動方法(Object-Oriented Software Engineering: A use case driven approach)》。當時,Jacobson 實際上稱之為 Entity-Interface-Control,爾後才進行更名,為了避免將 Interface 與程式語言中的 Interface 或 User Interface 混淆;也避免將 Control 與 MVC 中的 Controller 混淆。 Entity 實體 Entity objects 持有所有系統使用的數據且持有所有與數據耦合的行為。每個 Entity object 代表一個與問題領域相關的概念,同時具備身份(identity)與永久性(persistence)。Jacobson 告訴我們,Entity object 應該要包含那些會因 entity 自身變化而變化的邏輯,也就是說,如果它持有的數據結構改變,則對該數據的操作也將需要變化,因此它們應該位於 entity中。 值得注意的是,Jacobson在1992年就已經發出了一個警告: Beginners may sometime only use entity object as data carriers and place all dynamic behaviour in control objects […]. This should, however be avoided. […] Instead, quite a lot of behaviour should be placed in the entity objects. - Ivar Jacobson 1992, pp. 134 初學者有時可能只將實體物件用作數據載體,並將所有動態行為放在控制物件中[…]。然而,這樣的做法應該避免[…]。相反,應該將相當多的行為放在實體物件中。 - 伊瓦爾‧雅各布森 1992, pp. 134 ...

October 22, 2023 · 2 分鐘 · hgraca

[IT] Model1 & Model2

Java Server Pages (JSP) 是一種技術,這種腳本語言與 PHP、ASP,甚至 Python 相當,用於創建由 JVM 解釋的伺服器端頁面,並可以使用 Java 物件。 首次由 Sun Microsystems 於 1998 年發布的 JSP 規範,定義了兩種結構化應用程式的方式,使得呈現邏輯能與業務邏輯,甚至是在 HTTP 請求/回應範疇中的使用案例,進行解耦。 有些人認為這些"Model1"和"Model2"是首次嘗試將原本為桌面軟體開發環境而設計的 MVC 模式,適配到網路 HTTP 請求/回應範疇的嘗試。 Model1 JSP 規範 v0.92 的首次提議,是將 JSP 作為唯一的呈現工具,其中包含所有的呈現和用例邏輯。 這種方法對當時的大多數使用情況可能已經足夠好,因為當時的網路大部分是由簡單的動態頁面組成,而不是我們今天所熟知的複雜的網路企業應用程式。 Model2 關於如何使用 JSP 的第二個建議,當時是針對被視為複雜的網路應用程式而設計的。然而,請記住,如今的網路應用程式的規模和複雜度已經更高了。 在“Model2”中,一個 HTTP 請求會到達一個 servlet,該 servlet 會解釋 HTTP 請求,使用 Java 物件和 EJBs(repositories)執行一些用例邏輯,收集結果數據,並將該數據傳遞給一個 JSP,該 JSP 再渲染頁面,並發送回客戶端。在“Model2”中,JSPs 僅作為模板引擎使用。 在1999年,Govind Seshadri 發表了一篇文章,其中他將"Model2"對應到 MVC: servelet 是 Controller,它控制應如何處理用戶的請求。 JSP 是 View,它決定了顯示給使用者的內容。 在 MVC 和"Model2"中,Model 指的都是一整個領域模型(domain model)。 我的看法 這兩種方法至今只能說是堪用,但對今日的網路企業應用程式而言,我們需要更好的東西,因為這兩種模式都不遵守單一職責原則(Single Respoinsibility Pinciple, RSP)。 ...

October 19, 2023 · 1 分鐘 · hgraca

[IT] Action-Domain-Responder(ADR) 架構

這篇文章將討論 MVC 的另一種變體:由 Paul M. Jones 創建的 Action-Domain-Responder。 2014 – Action-Domain-Responder(ADR) ADR 模式是由 Paul M. Jones 於 2014 年創建的,其想法就像 RMR 一樣,是為了將 MVC 調整到網路 REST APIs的 情境中。ADR 的原始解釋非常簡單明瞭,我實在無法更好地改述它,所以我將在這裡複製/貼上部分內容,並只添加一些更多的評論。 Action 行動 Is the logic to connect the Domain and Responder. It invokes the Domain with inputs collected from the HTTP request, then invokes the Responder with the data it needs to build an HTTP response. 這是連接 domain 和 responder 的邏輯,它會用從 HTTP 收集來的請求來觸發 domain,接著使用需要構建 HTTP 響應的數據來調用 responder。 ...

October 19, 2023 · 4 分鐘 · hgraca

[IT] Resource-Method-Representation(RMR) 架構

MVC 於 1979 年在桌面應用程式與 CLI 使用者介面的背景下出現,這意味著如果資料庫因使用者以外的某些因素發生變化,則使用者介面將自動更改。同樣的模式後來在具有 GUI 的桌面應用程式上也完全可用。 然而,其在網路應用程式中的使用一直都是一種調適,因為大多數的網路應用程式並不會因為伺服器端的變更而改變使用者介面,使用者介面總是會呼叫伺服器端要求更新畫面。 我之前已經談過 MVC 模式的變體,這篇文章將討論另一種變體:Resource-Method-Representation。 我覺得有必要談論這個議題,是因為我曾對它產生誤解,認為它與 ADR 模式一樣,而我很快就會寫到這一點。 2008 – Resource-Method-Representation(RMR) RMR 模式是由 Paul James 在 2008 年創建的,它將 MVC 模式適配到 REST APIs 的情境中。 Resource 資源 The idea is that the Entities are modelled as REST resources (the first R in the pattern name), with its only public methods mapping to an HTTP method: 這個概念是將實體模型化為 REST resources 資源(RMR中的第一個R),與其唯一的公開方法映射到一個 HTTP 方法: <?php // taken from http://www.peej.co.uk/articles/rmr-architecture.html class Resource { private resourceData = []; method constructor(request, dataSource) { // load data from data source } method get(request) { return new Response(200, getRepresentation(request.url, resourceData)); } method put(request) { return new Response(405); } method post(request) { return new Response(405); } method delete(request) { return new Response(405); } } Method 方法 當向 API 發出請求時,該請求會被路由到這些業務物件之一,即資源,並且在此資源上被調用的方法對應於請求的 HTTP 方法。然後,這個業務物件上的方法負責返回一個完整的 http 響應,包括其狀態碼和 headers 信息。 ...

October 19, 2023 · 1 分鐘 · hgraca

[IT] MVC 及其變形

創立一個可維護的應用程式一直是程式設計的一項長期挑戰。 不久前,我在一家公司工作,其核心業務應用是一個 SaaS 平台,被幾千個客戶公司使用,這項應用程式已經運營了三年,其中的程式碼混雜了 HTML, CSS, 業務邏輯及 SQL,當然,在應用程式推出後的兩年,公司決定開始重構。儘管我們知道這樣的做法是不好的,且我們也知道如何避免,但是這樣的情況還是時常發生。 然後,回溯到 1970 年代,混合職責是很常見的做法,且人們仍在努力尋找如何改進。隨著應用程式的複雜性提升,對 UI 的更動必然會導致業務邏輯的更改,從而增加了修改的複雜度、執行的時間與 bug 出現的可能性。(因為會有更多的程式碼被更改)。 1979 - Model-View-Controller 為了解決上述問題,Trygve Reenskaug 於 1979 年提出了 MVC 架構,以此來將關注點分離,將 UI 與業務邏輯分離。該模式被應用於 1973年出現的桌面 GUI。 MVC 架構將程式分為三個部分: Model: 模型,代表了商業邏輯。 View: 視圖,代表了 UI 中的組件,如 button, text box 等。 Controller: 控制器,負責協調視圖和模型之間的配合,這意味著它: 決定要顯示哪些視圖,以及使用什麼數據。 將使用者行為轉化為業務邏輯。 A model could be a single object (rather uninteresting), or it could be some structure of objects. - Trygve Reenskaug 1979, MVC 一個模型可以是單一物件(相對無趣),或可以是一些物件的結構。 - 特里格維‧倫斯考 1979, MVC ...

October 19, 2023 · 4 分鐘 · hgraca

[IT] 分層架構 Layered Architecture

分層是一種常見於系統中做法,用於分隔或組織程式碼,根據程式碼在系統中的角色或職責。 In an object-oriented program, UI, database, and other support code often gets written directly into the business objects. Additional business logic is embedded in the behaviour of UI widgets and database scripts. This happens because it is the easiest way to make things work, in the short run. When the domain-related code is diffused through such a large amount of other code, it becomes extremely difficult to see and to reason about. Superficial changes to the UI can actually change business logic. To change a business rule may require meticulous tracing of UI code, database code, or other program elements. Implementing coherent, model-driven objects becomes impractical. Automated testing is awkward. With all the technologies and logic involved in each activity, a program must be kept very simple or it becomes impossible to understand. - Eric Evans 2014, Domain-Driven-Design 在物件導向的程式中,使用者介面、資料庫和其他輔助程式碼常常直接被寫入商業物件中,額外的商業邏輯被嵌入在使用者介面和資料庫腳本,因為這是最簡單且最快速使事情可以運作的方式,導致這種情況時常發生。 當與領域相關的程式碼在更大量的程式碼中擴散,程式碼便開始變得困難且難以理解,UI 的變更可能導致業務邏輯的改變,同樣地,業務邏輯的更動需要密切地追縱 UI、資料庫與組其它組件的程式碼。實作內聚且以模型驅動的物件變得不可行、自動化測試變得笨拙,由於每個動作都涉及到所有的技術和邏輯,程式必須保持簡單,否則就會難以理解。 - 艾瑞克‧埃文斯 2014, 領域驅動設計 ...

October 17, 2023 · 2 分鐘 · hgraca

[IT] 單體架構 Monolithic Architecture

建立一個單體系統一直以來都是預設的架構風格,最初軟體開發剛萌芽時,每個應用程式只有一個檔案,然後才出現了包含多個檔案的應用程式,並且直到1990年代我們才開始看到由其他應用程式組成的應用程式(儘管第一次的實驗是在1980年代進行的)。 單體結構自身也在演進,當應用程式開始使用多個檔案進行建構時,因為這些應用程式相對簡單,所以對每個檔案的職責與檔案之間的關係並沒有太多的思考。但隨著應用程式變得越來越大且越來越複雜,我們便開始需要思考要創建哪些檔案以及如何關聯它。 模組化軟體開發 Modular Software Development 模組化程式設計是在 1960 年代晚期和 1970年代 所提出的解決方案。它是從類別演變到對粒度更大程式碼單元進行明確定義(explicit definition),程式語言以不同程度的明確性(explicitness)實現了模組化。 例如,JAVA 具有 default 和 public 的類別級別可見性,其中 default 級別意味著一個類別只在其套件(模組)中可見,而 public 則意味著該類別在其套件(模組)內外都可見,這讓我們可以定義哪些類別可以當作套件被客戶端使用。 組件化軟體開發 Componentized Software Development 另一種模組化的風格是組件。如我在之前的文章中所解釋的,組件是以領域概念為基礎創建的模塊。理想情況下,它們是可以用來創建複合應用的獨立「應用程式」。這種風格的一個常見例子是 pipes 和 filters 架構,這在 Unix 系統中被廣泛使用,並允許我們做像 ps -ef | grep php 這樣的操作。另一個例子是使用微服務作為複合應用的組件,如 Netflix。 這種程式碼組織方式也已經存在很長時間了,可以追溯到1960年代末,就像模組化軟體開發一樣。 現代的單體架構 現今,擁有單體架構風格簡單來說就是所有的應用程式碼都被部署(deployed) 並在單一節點(node) 上作為單一進程運行。我們假設它正在使用模組和組件,儘管事實上往往並非如此。 理解這裡的關鍵詞「部署」和「節點」至關重要。關於第一個詞,部署,這意味著無論程式碼在物理上儲存在一個或多個儲存庫的任何地方,重要的是它在運行時是如何組織的。關於第二個關鍵詞,節點,這意味著即使我們將應用程式部署到多個服務器,就像在水平擴展的情況下,它仍然是一個單體。 在單一節點伺服器中,單體中的所有模組都被組裝到同一記憶體 image 中,並在單一節點上作為單一進程運行。通訊是通過同一 heap 和 stack 進行標準程式調用。正是這種單一記憶體 image 使得應用程序變得單體化。如果你在不同的進程中運行模組,那麼你正在進行 IPC。因為模組落入不同的進程邊界,你將開始面臨分散式計算的挑戰,這就進入了微服務領域。 這種風格,儘管聲名狼藉,但即使對於大型應用程式也能運作得相當好。只有當我們需要以下情況時,它才不再足夠好: 不同領域組件的獨立可擴展性(Independent scalability)。 需要用不同的程式語言撰寫不同的組件或模組; 獨立部署能力(Independent deployability),或許是因為我們的釋出速率超過了單一程式碼庫的部署管道所能處理的範圍,導致一個版本的部署變慢,因為它需要等待其他版本的部署,甚至導致部署佇列的增長速度超過了它的消耗速度。 在那個時候,我們需要將我們的單體系統分離成不同的應用程式,並採用SOA架構風格(關於這點將在後續的文章中詳述)。 反模式:大泥球/義大利麵條式架構 Anti-pattern: Big Ball of Mud / Spaghetti Architecture 所謂的「泥球」,又稱為「義大利麵條式架構」,就是這種風格的反模式,其中套件的結構和關係並不明確,結構的內聚力和封裝性幾乎不存在,依賴性沒有遵循任何規則,並且很難理解子系統,以進行變更和重構。該系統是不透明的,黏稠的,脆弱的,且僵硬的:一個大泥球! ...

October 15, 2023 · 1 分鐘 · hgraca

[IT] 架構風格 vs. 架構模式 vs. 設計模式 Architectural Styles vs. Architectural Patterns vs. Design Patterns

在上一篇文章中,我介紹了程式語言的演進,以及它告訴我們的事情:軟體發展的驅勢是走向更多的模組化(modularity)與封裝(encapsulation)。 在接下來的文章,我會開始介紹架構風格與架構模式的演進。首先,我們要先知道什麼是架構風格,什麼是架構模式。 在軟體開發中的眾多術語中,定義往往不夠明確,卻不同人都有自己不同的解釋。MSDN 認為架構風格(architectural styles)與架構模式(architectural patterns)是同一碼子事,但我個人更傾向於 George Fairbanks 與 Michael Keeling 在 stack overflow 中與維基百科中的解釋。其關鍵的差異在於範疇(scope)。 同時,我們也需要強調這個觀念:架構風格、架構模式和設計模式並非互斥,它們相輔相成,每一種都能帶給我們一些啟示。然而,如同常情,我們只應在需要時才使用它們。 Architectural Styles 架構風格 架構風格非常大方向的告訴我們該如何組織我們的程式碼,它有著高層次的粒度。它定義了層的概念,尤其應用程式的高階模組。它告訴我們模組與層之間是怎麼交互作用的、它們的關係等等。架構風格的例子: Component-based Monolithic application Layered Pipes and filters Event-driven Publish-subscribe Plug-ins Client-server Service-oriented 一種建架構格可以透過各種方式實現,包括特定的技術環境、特定的政策、框架或實踐方法。 Architectural Patterns 架構模式 模式是一種對反覆出現的問題的反覆解決方案。在架構模式的情況下,他們解決與架構風格相關的問題。例如,「我們該有哪些類別,它們該如何互動,以實現具有特定層次的系統」,又或者「我們的服務導向架構將擁有哪些高級模組,以及他們將如何溝通」,或者「我們的客戶端-伺服器架構將擁有多少層」。 架構模式對程式碼庫有著廣泛的影響,通常會橫向(即如何在一層內結構化程式碼)或縱向(即如何從外層處理請求到內層並返回)影響整個應用程式。架構模式的例子: Three-tier 三層架構 Microkernel 微核心 Model-View-Controller 模型-視圖-控制器 Model-View-ViewModel 模型-視圖-視圖模型 Design Patterns 設計模式 設計模式與架構模式在範疇上有所不同,它們更為局部化,對程式碼庫的影響較小,它們只影響程式碼的特定部分,例如: 如何在我們只知道在運行時需要實例化什麼類型的情況下實例化一個對象(可能是一個工廠類別?) 如何讓一個物件根據其狀態(可能是一個狀態機,或者是策略模式?)表現出不同的行為? Conclusion 結論 如我在這篇文章的開頭所提到的,一切都關於範疇(scope): 架構風格是在最高抽象層次上的應用設計。 架構模式是實現架構風格的一種方式。 設計模式是解決局部問題的一種方式。 此外,一種模式可能既可以作為架構模式,也可以作為設計模式使用,這再次取決於我們在特定項目中使用它的範疇。

October 14, 2023 · 1 分鐘 · hgraca