領域驅動設計:分離領域

来源:http://www.cnblogs.com/haifeng1990/archive/2016/03/23/5313274.html
-Advertisement-
Play Games

本章大部分內容摘自:《領域驅動設計:軟體核心複雜性應對之道》一書中的第四章,分離領域,純屬原創。如有錯誤請指正,相互學習。 在軟體中,專門用於解決問題的那部分通常之占整個軟體的系統的很小一部分,這與其重要性遠遠不成比例。要想實現最佳的設計構思,就得去研究模型中的元素並它們視為一個系統 模式:LAYE... ...


本章大部分內容摘自:《領域驅動設計:軟體核心複雜性應對之道》一書中的第四章,分離領域,純屬原創。如有錯誤請指正,相互學習。

    在軟體中,專門用於解決問題的那部分通常之占整個軟體的系統的很小一部分,這與其重要性遠遠不成比例。要想實現最佳的設計構思,就得去研究模型中的元素並它們視為一個系統

模式:LAYERED ARCHITECTURE (分層結構)

    在面向對象的程式中,常常會在業務對象中直接寫入用戶界面、資料庫訪問等支持代碼。而一些額外的業務邏輯則會被嵌入到用戶界面組件和資料庫腳本的行為中。這麼做是為了以最簡單的方式在短期內完成開發工作。

    如果與領域有關的代碼大量分散在大量的其他代碼之中,那麼查看和分析領域代碼就會變得相當困難。對用戶界面的簡單修改實際上很可能會改變業務邏輯,而要想調整業務規則也很可能需要對用戶界面代碼、資料庫操作代碼或者其他的程式元素進行仔細的篩查。這樣就不太可能實現一致的、模型驅動的對象了,同時也會給自動化測試帶來困難。程式中每一個活動都有其自身的邏輯和使用的技術,因此程式本身必須簡單明瞭,否則就會讓人無法理解。

    要想創建能夠處理複雜任務的程式,需要把不同的關註點分開考慮,是設計中的每個部分都得到單獨的關註,在分離的同事,也需要維持系統內部複雜的交互關係。

    分割軟體系統普遍採用LAYERED ARCHITECTURE(分層架構),有些層已成標準。基本原則是層中的任何元素都僅依賴於本層的其他元素或其下層的元素。向上通信必須通過間接的傳遞機制進行。分層的價值在於每一層只代表程式中的某一特定方面,這種限制使每個方面的設計都更具內聚性,更容易解釋。

    大多數成功的架構使用都包括下麵四個概念層的某個版本:

clipboard

  • 用戶界面層(或表示層):負責用戶顯示信息和解釋用戶指令,這裡指的用戶是另一個電腦系統,不一定是使用用戶界面的人。
  • 應用層:定義軟體要完成的任務,並指揮表達領域概念的對象來解決問題。這一層所負責的工作對業務來說意義重大,也是與其他系統的應用層進行交互的必要渠道。應用層要儘量簡單,不包含業務規則或者知識,而只為下一層中的領域對象協調任務,分配工作,使他們互相協作。它沒有反應業務情況的狀態,但是卻可以具有另外一種狀態,為用戶或程式顯示某個任務的進度。
  • 領域層(或模型層):負責表達業務概念,業務狀態信息以及業務規則。儘管保存業務狀態的技術細節是有基礎設施層實現的,但是反應業務情況的狀態是有本層控制並且使用的,領域層是業務軟體的核心。
  • 基礎設施層:為上面各層提供通用的技術能力:為應用層傳遞消息,為領域層提供持久化機制,為用戶界面層回執屏幕組件,等等。基礎設施層還能夠通過架構框架來支持四個層次間的交互模式。

    給複雜的應用程式劃分層次,在每一層內分別進行設計,使其具有內聚性並且只依賴於它的下層。採用標準的架構模式,只與上層進行鬆散連接。將所有與領域相關的代碼放在一個層中,並把它與用戶界面層、應用層以及基礎設施層的代碼分開。領域對象應該將重點放在如何表達領域模型上,而不需要考慮自己的顯示和存儲問題,也無需管理應用任務等內容。這使得模型的含義足夠豐富,結構足夠清晰。

    各層之間可以生成一個應用程式,也可以每層部署為一個應用,在分散式系統中每層可以靈活的放在不同的伺服器或者客戶端中。

將各層關聯起來

    程式需要劃分層次,但是各層之間也需要互相連接,在連接各層的同事需要不影響分離帶來的好處。各層之間是鬆散連接的,層與層之間的以來關係只能是單向的,上層可以直接使用或操作下層元素。方法是保持對下層元素的引用,採用常規的交互手段調用下層元素的公共介面。如果下層需要與上層元素通信,則需要採用另一種通信機制,如使用架構模式來鏈接上下層,比如回調模式或觀察者模式。

    鏈接用戶界面層與應用層有很多種方式,只要連接方式能夠維持領域層的獨立性,保證設計領域對象時不需要同時考慮可能與其交互的界面,那麼連接方式就可用。

    一般基礎設施層只提供服務,如:發送郵件的消息發送功能,持久化操作功能、導出excel功能等,但有時有些技術組件被設計成直接支持其它層的基本功能,比如:為所有領域對象提供抽象基類等.

架構框架

    整合了大量基礎設施需求的框架常會需要其他層以某種特定的方式實現,比如以框架類的子類形式或者帶有結構化的方法簽名。最好的框架既能解決複雜技術問題,也能讓領域開發人員集中精力去表達模型而不考慮其他問題。然而使用框架很容易為項目製造障礙,要麼設定了太多假設,減少領域設計的可選範圍;要麼是需要實現太多的東西,影響開發進度。

    項目中一般都需要某種形式的架構框架(儘管有時項目團隊選擇了不太合適的框架)。當使用框架時,應該明確其使用目的,建立一種可以表達領域模型的實現並且用它來解決重要問題。項目團隊必須想方設法讓框架滿足這些需求,即使這意味著要拋棄框架中的一些功能。

模型屬於領域層

    領域模型是一系列概念的幾核。“領域層”則是領域模型以及所有與其直接相關的設計元素的表現,它由業務邏輯的設計和實現組成。在MODEL-DRIVEN DESIGN中,領域層的軟體構造反應出了模型概念。

模式:THE SMART UI "ANTI-PATTERN"

    如果一個經驗並不豐富的項目團隊要完成一個簡單的項目,要使用MODEL-DRIVEN DESIGN 以及 LAYERED ARCHITECTURE,那麼項目組將會經歷艱難的學習過程。掌握新技術、學習對象建模、對基礎設施及各層的管理工作使原本簡單的任務卻需要花費很長的時間來完成。簡單項目開發周期較短、期望值也不高,所以可能任務完成之前項目木就會被取消。即使項目有時間,沒有專家幫助,團隊成員也不太可能掌握這些技術。最後假如剋服了這些困難,恐怕也只會開發出一套簡單的系統,因為項目本來就不需要豐富的功能。

    因此,當情況需要時:在用戶界面中實現所有業務邏輯。將應用程式分成小的功能模塊,分別將它們實現成用戶界面,併在其中嵌入業務規則。用關係資料庫作為共用的數據存儲庫。使用自動化程度最高的用戶界面創建工具和可用的可視化編程工具。

    這也是一個有爭議的觀點,但是SMART UI有著自身的優勢,效率高、學習成本低等,但是一旦使用了SMART UI如果補充些代碼將無法在使用領域驅動設計。

    項目團隊也經常犯一個錯誤就是採用了一種複雜的設計方法,卻無法保證項目從頭到尾始終使用它。另一種常見的也是代價高昂的錯誤則是為項目構建一種複雜的基礎設施,並使用頂級的工具,而這樣的項目根本不需要它們。

   將領域設計與其他部分混在一起會產生災難性的後果,所以不要隨意使用多種模式。

 

Example:

項目分層結構

將應用程式分層,並對指定層隱藏類的可見度,使用maven的多模塊項目是個不錯的選擇,由於接觸尚少,尚不知還有沒有其他的辦法,如有請指點相互學習,下麵參考下koala項目的項目結構及編寫一個小demo。關於maven的知識我就不多說了,請大家自行搜索吧。首先看下目錄結構:

image

demo:為父項目,進行一些公共配置

demo-facade: 外觀層介面

demo-facade-impl:外觀層實現類,實現demo-facade層的介面

demo-application:應用層

demo-infra:基礎設施層

demo-conf:基礎設施層,主要是一些配置文件信息

demo-web:用戶界面層

demo-core:領域層

由於koala項目分層較多,其中外觀層在一些相對簡單一些的項目中可以去掉,因為外觀層主要是封裝各個子系統的細節來提供統一的介面對外發佈,可以繞過外觀層直接讓用戶界面層訪問應用層。

根據分層基本原則,本層依賴或操作本層及下層的元素,依賴是單向性的,koala項目的依賴方向如下:

image

demo-web項目為界面層,主要顯示用戶信息與解釋用戶指令,即封裝好參數然後傳遞給外觀層。引用其他項目的配置信息為:

        <dependency> 
            <groupId>com.yunzaipiao</groupId>  
            <artifactId>demo-facade</artifactId>  
            <version>${project.parent.version}</version> 
        </dependency>  
        <dependency> 
            <groupId>com.yunzaipiao</groupId>  
            <artifactId>demo-conf</artifactId>  
            <version>1.0.0-SNAPSHOT</version> 
        </dependency> 
        <dependency> 
            <groupId>com.yunzaipiao</groupId>  
            <artifactId>demo-infra</artifactId>  
            <version>1.0.0-SNAPSHOT</version> 
        </dependency> 

 

demo-facade與demo-facade-impl項目為外觀層,這一層為應用層上面的一層,對應用層進行了進一步的抽象,如果項目不是太複雜,沒有太多的子系統,可以沒有這一層。demo-facade為介面定義項目,一般不需要引用其他的項目,demo-facade-impl是外觀層的介面實現項目,需要引用下層項目,不過上面也說過,同層元素也是可以相互引用的,所以這裡也可以引用demo-facade項目,因為他們在同一層,只不過不是一個項目而已,下麵為demo-facade-impl引用其他兩個項目的配置信息:

        <dependency> 
            <groupId>com.yunzaipiao</groupId>  
            <artifactId>demo-application</artifactId>  
            <version>${project.parent.version}</version> 
        </dependency>  
        <dependency> 
            <groupId>com.yunzaipiao</groupId>  
            <artifactId>demo-facade</artifactId>  
            <version>${project.parent.version}</version> 
        </dependency>  

 

demo-application 為應用層,應用層定義要完成的任務和指揮領域層解決問題,不存儲業務規則,應用層可以引用下層項目及基礎設施層,配置信息如下:

        <dependency> 
            <groupId>com.yunzaipiao</groupId>  
            <artifactId>demo-core</artifactId>  
            <version>${project.parent.version}</version> 
        </dependency>  
        <dependency> 
            <groupId>com.yunzaipiao</groupId>  
            <artifactId>demo-infra</artifactId>  
            <version>${project.parent.version}</version> 
        </dependency>  

 

demo-core 為領域層項目,處理業務規則,可以引用基礎設施層,主要通過基礎設施層來進行持久化操作。由於一個基礎設施層可以有多個項目組成,這裡把持久化操作的相關內容都定義在了其他的項目中,引用的配置如下:

        <dependency> 
            <groupId>org.dayatang.dddlib</groupId>  
            <artifactId>dddlib-query-channel</artifactId>  
            <exclusions> 
                <exclusion> 
                    <groupId>org.hibernate</groupId>  
                    <artifactId>hibernate-core</artifactId> 
                </exclusion>  
                <exclusion> 
                    <groupId>org.javassist</groupId>  
                    <artifactId>javassist</artifactId> 
                </exclusion> 
            </exclusions> 
        </dependency>  

demo-infra 是基礎設施層項目,由於基礎設施層為項目的最層,所以一般不會對其它層進行直接操作,這裡也就沒有配置引用信息。

編寫應用層及領域層實例

現在模仿一個非常簡單的用戶註冊的例子,其中領域模型的getter setter方法都不再顯示了,還有一些其他的在此不重要的代碼都不寫了。在用戶註冊時,登錄用戶名與昵稱是不能重覆的,所以在註冊的時候需要驗證用戶的登錄用戶名與昵稱是否已經存在,驗證時的查詢資料庫等代碼也沒有實現,請簡單看下吧。

首先是領域模型,UserModel的代碼

package com.yunzaipiao.demo.core.domain;
import org.openkoala.koala.commons.domain.KoalaAbstractEntity;
import com.yunzaipiao.demo.core.exception.LoginNameExistsException;
import com.yunzaipiao.demo.core.exception.NickNameExistsException;
/**
 * 用戶 Model
 * @created 2016年3月23日 下午9:13:35
 */
public class UserModel extends KoalaAbstractEntity {
    /**
     * 登錄用戶名
     */
    private String loginName;
    /**
     * 密碼
     */
    private String password;
    /**
     * 昵稱
     */
    private String nickName;
    /**
     * 真實姓名
     */
    private String realName;
    
    
    /**
     * 判斷登錄用戶名是否存在
     * 
     * @created 2016年3月23日 下午9:12:04
     */
    private void isExistsLoginName() {
        if (true) {
            throw new LoginNameExistsException("loginName is exists!");
        }
    }
    /**
     * 判斷昵稱是否存在
     * 
     * @auth 周志君
     * @created 2016年3月23日 下午9:12:25
     */
    private void isExistsNickName() {
        if (true) {
            throw new NickNameExistsException("nickName is exists!");
        }
    }
    
    public UserModel(String loginName, String password, String nickName,
            String realName) {
        isExistsLoginName();
        isExistsNickName();
        this.loginName = loginName;
        this.password = password;
        this.nickName = nickName;
        this.realName = realName;
    }
    
    
    // getter setter ....
}

在領域類中包含四個成員變數,以及三個方法,分別是:

isExistsNickName:這個方法用來判斷用戶的昵稱是否已存在,如果已存在則拋出異常

isExistsLoginName:這個方法用來判斷用戶登錄用戶名是否已存在,如果已存在則拋出異常

UserModel(String loginName, String password, String nickName, String realName):構造方法,該構造方法,首先調用上述的兩個方法判斷用戶昵稱及用戶登錄名是否已存在,如果已存在則繼續向上拋出異常有應用層進行處理。

 

下麵看看應用層的代碼,首先是介面只定義一個註冊的介面,如下;

package com.yunzaipiao.demo.application;
public interface UserApplication {
    
    /**
     * 註冊介面
     * @param loginName
     * @param password
     * @param nickName
     * @param realName
     * @created 2016年3月23日 下午9:24:20
     */
    public void reg(String loginName, String password, String nickName, String realName);
}

下麵是介面實現類,實現類的方法中,只需要new一個UserModel,然後調用UserModel的save方法即可,save方法是UserModel繼承的KoalaAbstractEntity中的方法,可以將領域類持久化到資料庫中,代碼如下:

package com.yunzaipiao.demo.application.impl;
import com.yunzaipiao.demo.application.UserApplication;
import com.yunzaipiao.demo.core.domain.UserModel;
public class UserApplicationImpl implements UserApplication {
    @Override
    public void reg(String loginName, String password, String nickName,
            String realName) {
        UserModel userModel = new UserModel(loginName, password, nickName, realName);
        userModel.save();
    }
}

外觀類與界面類就不寫實例了,因為都是簡單的將參數封裝一下傳遞過來就可以了,一般沒有什麼業務可以處理的。這裡的介面類使用的傳遞多個參數的方式,也可以將參數封裝到一個DTO中,直接傳遞一個類過來,不顧這裡為了方便也沒有在生成其他的類。

不過有這裡簡單的代碼可以看出,應用層只是定義要完成的任務如用戶註冊。而具體如何註冊,有哪些業務規則,如登錄用戶名不能重覆,昵稱不能重覆等,則有領域類來進行處理。應用層定義任務,然後指揮者領域層進行完成任務,基礎設施層協助應用層或領域層。界面層則是處理用戶下達的指令並解釋成應用層可以理解的參數。

本章大部分內容摘自:《領域驅動設計:軟體核心複雜性應對之道》一書中的第四章,分離領域,純屬原創。如有錯誤請指正,相互學習。


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

-Advertisement-
Play Games
更多相關文章
  • 工廠方法模式(Factory Method) 類圖 描述 工廠方法: 一個抽象產品類,可以派生多個具體產品類; 一個抽象工廠類,可以派生多個具體工廠類; 每個具體工廠只能創建一個具體產品。 應用場景 汽車介面 汽車類 汽車工廠介面 汽車工廠類 調用 ...
  • 簡單工廠模式(Simple Factory) 類圖 描述 簡單工廠: 一個抽象產品類,可以派生多個具體產品類; 一個具體工廠類; 工廠只能創建一個具體產品。 應用場景 汽車介面 汽車類 汽車工廠類 調用,從配置文件中讀取操作符 ...
  • 無論做什麼事情呢,都要善始善終呢。前邊連續發表了5篇關於重構的博客,其中分門別類的介紹了一些重構手法。今天的這篇博客就使用一個完整的示例來總結一下之前的重構規則,也算給之前的關於重構的博客畫一個句號。今天的示例借鑒於《重構,改善既有代碼的設計》這本書中的第一章的示例,在其基礎上做了一些修改。今天博客 ...
  • 對第三方介面的調用我們需要對GET和POST進行監控,看一些請求的執行是否成功,如A調用B,B調用C,C調用D,這一連串的東西需要我們使用cat進行記錄,進行記錄之後,我們可以很容易的發現請求響應的時間及是否出錯,下麵是我對這兩種請求的封裝。 在程式中使用非常方便,如下代碼,一看便知 而它產生的消息 ...
  • 本系統旨在提供業務監控實時數據和歷史數據以及報表、閾值報警、同比增長分析等一體化的歷史業務數據解決方案。 技術選型 sdk部門有C#版和java版,api和website採用golang語音開發,資料庫採用mysql,數據傳輸採用socket+http 架構設計 本系統主要分3個部分:即sdk(@2 ...
  • 8. 告別Lock 不是一直說Lock比較麻煩危險嗎,那就不要好了。其實有一個Lock free的方法。 首先引入一個概念——原子變數。在這種變數上的操作是原子操作(atomic operation)。原子操作就是說這個操作要麼都完成,要麼都不完成,部分完成是不行的。就像物理化學中的原子一樣,借用不 ...
  • 一、現狀說明: 就在這金三銀四的求職黃金時期,我有幸作為公司的獨立技術面試官,擁有最終決定錄用權,在倍受上級領導的充分信任下,我也向上級保證,一定要為公司找到合適的人才,就在我滿懷信心的情況下麵試了一個又一個的求職者,發現了大多數求職者共同的問題,一是:眼高手低,即工作年限雖長,但受工作內容及個人原 ...
  • 多租戶(Multi Tenancy/Tenant)是一種軟體架構,其定義是:在一臺伺服器上運行單個應用實例,它為多個租戶提供服務。 本框架使用的是共用資料庫、共用 Schema、共用數據表的數據設計架構。 進入系統管理員界面,打開租戶管理界面,如下圖所示: 下麵是租戶管理界面: 這裡可以管理租戶成員 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...