1. 程式人生 > >領域驅動設計思考(持久層)

領域驅動設計思考(持久層)

如果不考慮資料庫的具體實現,只考慮程式碼中執行的儲存操作,大致可以分為以下幾種情況

1 有Repository 由repository.save(entity)來完成持久化操作

2 無Repository,由entity.save()來完成儲存

3 無repository,無entity.save(),在呼叫業務方法後自動儲存到資料庫

按照DDD書上的介紹推薦的是第一種,理由是把持久化的責任歸到entity會到導致混亂,有損entity專注於業務邏輯,也導致開發者的關注力收到影響(那就是為了保護你所以故意這樣做)

但是採用第一種也會導致一個問題,那就是繼承:

        如果有一個父類訂單Contract和兩個子類CommonContract和SpecialContract都有作廢操作並且含義一致.對於Application層有兩種方案,A方案是:

public ContractService{
    private ContractRepository repository;
    public Contract cancel(String id){
        Contract contract=repository.retrieve(id);
        contract.cancel();
        repository.save(contract);
        return contract;
    }
}

這樣就給ContractRepository出了一個難題,那就是我save到哪兒呢

B方案是提供兩個service

public CommonContractService{
    private CommonContractRepository repository;
    public CommonContract cancel(Stringid){
        CommonContract contract=repository.retrieve(id);
        contract.cancel();
        repository.save(contract);
        return contract;
    }
}

public SpecialContractService{
    private SpecialContractRepository repository;
    public SpecialContract cancel(Stringid){
        SpecialContract contract=repository.retrieve(id);
        contract.cancel();
        repository.save(contract);
        return contract;
    }
}

這樣做實現方雖然簡單了但毫無疑問增加了呼叫方的難度.一種緩解呼叫方難度的方案是採用Restful風格的介面,這個我後面會在restful風格api設計中介紹

B方案的另一個缺點是感覺沒有複用(這個問題也可以歸結於service這種東西本身就不夠OO)

也許有人會問第一種方案裡repository.retrieve()如何實現呢,這裡可以委託子類的repository(但子類太多而導致頻繁呼叫也是問題);但是在save的時候總不能把每個子類的repository都呼叫一遍吧.就算可以也會遇到方法不相容問題.原因如下

interface ContractRepository{
    void save(Contract contract);
}
interface CommonContractRepository{
    void save(CommonContract contract);
}
interface SpecialContractRepository{
    void save(SpecialContract contract);
}

從方法入參可以看出如果在ContractRepository裡呼叫CommonContractRepository裡save方法是要強制型別轉換的.

下面再說第二個方案:

public ContractService{
    private ContractRepository repository;
    public Contract cancel(String id){
        Contract contract=repository.retrieve(id);
        contract.cancel();
        contract.save();
        return contract;
    }
}

abstract class Contract{
     abstract void save();
     private boolean isCanceled;
    //這裡是個示例,實際操作與操作前的判斷及操作後的訊息通知可能更加複雜
     public void cancel(){isCenceled=true;}
}

這樣調至的一個明顯好處是體現了OO中的繼承,繼承在呼叫父類擁有的方法時很好用,不用care是哪個子類,但是作為入參並且要求方法對於不同子類採用不同操作時就比較麻煩了.

方案三可以避免方案一可能遇到的另一個問題,場景如下

        client發起了一個退費申請,然後本地服務受到請求後呼叫一個遠端第三方服務,在受到請求後返給client.這時會遇到一個問題,那就是由於網路原因或者程式碼bug導致遠端服務成功後本地服務沒有收到或者沒有寫庫成功,那麼當第三方提供對賬單時將無法在本地找到相應訂單記錄.所有一個解決方案就是在呼叫第三方前先寫一次本地資料庫,在得到呼叫結果後在寫一次本地資料庫.如果出了問題就等到對賬單到了找到本地的訂單記錄並把狀態改對就可以了.

        這個場景的解決也有兩種方案,一種是DDD推薦的使用Service來處理

class RefundService{
    private RemoteService remoteService;
    public Response refund(String accountId,Request request){
        Account account=repository.retrieve(accountId);
        account.refund(request);
        repository.save(transfer);//第一次
        Response repose=remoteService.refund(request);
        account.update(response);
        repository.save(account);//第二次
        return response; 
    }
}

這個在我看來讓Entity和Service責任混為一體,這樣導致只要有遠端呼叫就需要service的介入,這對今後一個服務拆分為多個服務是不利的.而採用第三種方案可以這樣處理

class RefundService{
    private RemoteService remoteService;
    public Response refund(String accountId,Request request){
        Account account=repository.retrieve(accountId);
        account.refund(request);
        repository.save(transfer);
        return response; 
    }
}
class Account{
    private RemoteService remoteService;
    void refund(Request request){
        //進行規制判斷
        if(getRemainAmount()<request.getAmount()){
            throw new InsufficientAmountException();
        }
        //儲存請求資料
        this.request=request;
        save();
        //更新執行結果
        Response response=remoteService.refund(request);
        this.response=response;
        save();
    }
    void save(){
        repository.save(this);
    }
}

這樣的好處是Entity處理了所有的業務操作,外邊的Application層只是負責把遠端呼叫(可能是json或xml格式,可能有加解密等等)轉換為一次對entity的本地方法呼叫.然後entity搞定其他的,甚至和其他微服務之間的互動也可以轉化為了一次對另一個Entity的呼叫.每次entity的寫方法結束後就會完成持久化和傳送訊息(而按照DDD書上介紹這是Application的責任)

上面講到的第三個方案有一個缺點,就是有時候我們接收到一次前端呼叫的時候也許不需要持久化.

比如一些帶確認動作的操作.就是使用者輸入了一些(例如使用了一個滿減紅包和一個折扣紅包),那麼頁面的顯示會隨之變動(例如訂單金額),有時是前端的資訊不足(例如該使用者是會員可以打折),如果這個計算操作後端是肯定要進行了,總不能拿著前端金額作為訂單金額,另外是紅包有可能被併發使用.總而言之就是後端不能信任前端的結果需要自己根據原始資料重新計算.這樣就導致了兩個問題,一個就是前後端計算結果可能不一致,就算一致也加重了前端的開發量.所以一個方案就是使用者在輸入時前端實時把資料拋給後端,由後端計算後返給前端呈現,這樣前端就只負責呈現而不用考慮業務邏輯.

這個時候後端需要提供兩個介面,一個是根據前端原始資料計算並返回,另一個是計算並儲存.可以看出來第二個方法是第一個方法+持久化.如果如果持久化本身就在entity的方法裡執行了豈不是entity還要專門提供一個計算但不持久化的方法.而我認為這是不必要的,這個可以通過service來控制

class ContractService{
    public Contract doRefund(String contractId,Request request){
        Lock lock=lockFactory.create(contractId);
        if(lock.tryLock()){//為防止併發而加鎖
            try{
                Contract contract=repository.retrieve(contractId);
                contract.refund(request);
                contract.save();
                return contract;
            }finally{
                lock.unLock();
            }
        }else{
            return null;
        }
    }
    public Contract calculate(String contractId,Request request){
        Contract contract=repository.retrieve(contractId);
        contract.refund(request);
        return contract;
    }
}

可以看出來entity專注於處理業務邏輯,而一些技術上的問題,例如持久化和加鎖交給了Service來處理.

也許你也注意到了,這樣會導致refund()內部無法save()的問題.

相關推薦

領域驅動設計思考(持久)

如果不考慮資料庫的具體實現,只考慮程式碼中執行的儲存操作,大致可以分為以下幾種情況 1 有Repository 由repository.save(entity)來完成持久化操作 2 無Repository,由entity.save()來完成儲存 3 無repositor

領域驅動設計實踐 —— UI實現

mcg ndk don xiv llc clu dji can vdc http://www.fjrcw.cn/zhiwei/company-1481.htmlhttp://2shou.guilinlife.com/product-386-816469.htmlhttp:/

如何使用ABP進行軟體開發(2) 領域驅動設計和三架構的對比

# 簡述 上一篇簡述了ABP框架中的一些基礎理論,包括ABP前後端專案的分層結構,以及後端專案中涉及到的知識點,例如DTO,應用服務層,整潔架構,領域物件(如實體,聚合,值物件)等。 筆者也曾經提到,ABP依賴於領域驅動設計這門方法論,由於其門檻較高,給使用者帶來了不少理解上的難度。尤其是三層架構對.NE

架構師根本不會被語言限制住,php照樣可以用領域驅動設計DDD四架構!

![](https://img-blog.csdnimg.cn/20201011211214713.jpg) 作者:小傅哥 部落格:[https://bugstack.cn](https://bugstack.cn) > 沉澱、分享、成長,讓自己和他人都能有所收穫!

.NET應用架構設計—面向查詢的領域驅動設計實踐(調整傳統三架構,外加維護型的業務開關)

閱讀目錄: 1.背景介紹 2.在業務層中加入核心領域模型(引入DomainModel,讓邏輯、資料有家可歸,變成一個完整的業務物件) 3.統一協調層Application Layer(加入協調層來轉換DomianModel) 4.從資料扁平結構轉換成OO體系結構(使用OO豐富程式碼結構) 5.D

關於領域驅動設計(DDD)中聚合設計的一些思考

關於DDD的理論知識總結,可參考這篇文章。 DDD社群官網上一篇關於聚合設計的幾個原則的簡單討論: 聚合是用來封裝真正的不變性,而不是簡單的將物件組合在一起; 聚合應儘量設計的小; 聚合之間的關聯通過ID,而不是物件引用; 聚合內強一致性,聚合之間最終一致性; 上面這幾條原則,作者通過

C#進階系列——DDD領域驅動設計初探(七):Web的搭建

前言:好久沒更新部落格了,每天被該死的業務纏身,今天正好一個模組完成了,繼續來完善我們的程式碼。之前的六篇完成了領域層、應用層、以及基礎結構層的部分程式碼,這篇打算搭建下UI層的程式碼。 DDD領域驅動設計初探系列文章: 一、UI層介紹 在DDD裡面,UI層的設計也分為BS和CS,本篇還是以Web為

領域驅動設計架構風格

des 設計 表達 對象 切入點 解決 基於 1.5 pattern 領域驅動設計 (DDD) 是面向對象的軟件設計方法,基於業務領域、元素和行為,以及它們之間的關系。其目標是將潛在業務領域的實現用業務領域專家語言定義的領域模型來表達出來。領域模型可以看一個框架,讓業務變得

EF Code first 和 DDD (領域驅動設計研究)系列一

發的 tex bsp cti 設計 ron 映射 developer devel 在上個公司工作時,開發公司產品的過程中,接觸到了EF Code first. 當時,整個產品的架構都是Lead developer設計建立的,自己也不是特別理解,就趕鴨子上架跟著一起開發了。

領域驅動設計(DDD)- 請先搞清楚一些概念

責任 可能 升級 是你 ora ext 計數 方法 避免 開發一個新系統   一般我們開始開發一個商業系統都需要做什麽?讀需求文檔去查找功能點,拆解任務。多數情況下,拆解項目是為了評估工作,做評估、分配任務到個人、設計數據庫結構,然後就開始了Coding。 所以,這種方

【DDD】領域驅動設計實踐 —— 架構風格及架構實例

讀取 bili 邏輯 stat orcal ransac 應用服務 業務場景 解讀 概述 DDD為復雜軟件的設計提供了指導思想,其將易發生變化的業務核心域放置在限定上下文中,在確保核心域一致性和內聚性的基礎上,DDD可以被多種語言和多種技術框架實現,具體的框架實現需要根據

【DDD】領域驅動設計實踐 —— 限界上下文識別

團隊協作 協作 tin 組織 領域 ges 承擔 產品 進行 本文從戰略層面街上DDD中關於限界上下文的相關知識,並以ECO系統為例子,介紹如何識別上下文。限界上下文(Bounded Context)定義了每個模型的應用範圍,在每個Bounded Context中確保領域模

.NET領域驅動設計—初嘗(原則、工具、過程、框架)

事物 只需要 pos eight 封裝 bili 建模 成就 一個 閱讀目錄: 1.原則 1.1.精簡聚合 1.2.分離用例與接口功能(設計模式的用武之地) 2.工具、框架、組件 3.過程 1】原則 原則對於任何一項技術實現來說都是至關重要的,在設計某一個系統功能的

領域驅動設計系列(2)淺析VO、DTO、DO、PO的概念、區別和用處

服務 完全 session 並且 main 解決 業務 導致 teacher   上一篇文章作為一個引子,說明了領域驅動設計的優勢,從本篇文章開始,筆者將會結合自己的實際經驗,談及領域驅動設計的應用。本篇文章主要討論一下我們經常會用到的一些對象:VO、DTO、DO和PO。

領域驅動設計:軟件核心復雜性應對之道》讀書筆記

風暴 基於模型 自動 知識 有效 嚴格 就是 專家 body 1.Eric Evans強調要聚焦於軟件的核心領域,以它來驅動開發。軟件能夠在市場上賣出去。是因為它封裝了別的軟件所滅有的一些核心領域知識,這就是核心競爭力,是利潤所在的地方,也是最值得下功夫的地方,再難也不能逃

領域驅動設計:軟件核心復雜性應對之道pdf

核心 案例 項目案例 ans weight line 作者 tle 方法 下載地址:網盤下載 內容簡介《領域驅動設計:軟件核心復雜性應對之道》是領域驅動設計方面的經典之作。全書圍繞著設計和開發實踐,結合若幹真實的項目案例,向讀者闡述如何在真實的軟件開發中應用領域驅動設計

領域驅動設計

代碼 包括 行為 data ech 不同的 好處 區別 權限 1.什麽是領域驅動設計(DDD:Domain Driven Design) 領域驅動設計(DDD)是一種基於模型驅動的軟件設計方式。它以領域為核心,分析領域中的問題,通過建立一個領域模型來有效的解決領域

領域驅動設計-分享

技術問題 詳細分析 上下文 mage oot class 頁面 val 約束 概述 領域驅動不是純粹的技術問題,領域建模(建立數據表只是一部分)是領域專家(客戶/產品團隊)和開發人員溝通努力、抽象的的結果。 領域建模的目的是,經過有效的溝通、詳細分析、 良好設計可以更好的適

領域驅動設計的必要性和模型標準——《領域驅動設計-精簡版》

叠代 思考 tro 開發人員 實例 動軟 需求 com 專家 一、領域驅動設計 領域驅動設計早在30年前就已經為人所知,一些設計人員開始開始領域建模,領域通用語言的思維構造,以便能夠在領域專家和開發專家形成高效的溝通,Eric Evans將這種思維(思潮)定義為Domain

領域驅動設計_01_基本概念

一、前言 二、領域、子域、限界上下文 1.領域 2.子域 核心域、支撐子域、通用子域 3.限界上下文 (1)邊界 限界上下文是一個顯示的邊界,領域模型邊存在於這個邊界之內。 在邊界內,每一個概念模型,包括其屬性和操作,都具有特定的含義。 (2)概