0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何在SpringBoot中應(yīng)用DDD

科技綠洲 ? 來源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-10-08 14:49 ? 次閱讀

在日常工作中,接手或維護(hù)的工程,大多數(shù)使用的是三層架構(gòu),即controller、service、dao三層,在使用的過程中,會(huì)遇到很多問題:

  • 面向數(shù)據(jù)建模,面向過程編程,沒有真正“面向?qū)ο蟆?/li>
  • 只注重結(jié)果,不注重過程,service層動(dòng)輒數(shù)百上千行,充斥著過程代碼、膠水代碼,要么臃腫、要么流水賬、要不重復(fù)、要么邏輯分散,后期極難維護(hù)
  • 代碼耦合嚴(yán)重,層與層之間互相調(diào)用、逆向調(diào)用,牽一發(fā)而動(dòng)全身
  • 代碼無法體現(xiàn)業(yè)務(wù),在大家都不愛寫注釋的情況下,隨著時(shí)間的推移,代碼業(yè)務(wù)邏輯將無人理解,不敢改也改不動(dòng)。

那么有沒有一個(gè)好的解決方案呢?今天要講的DDD就是一個(gè)不錯(cuò)的選擇。

DDD

DDD,即 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì) ,完美的解決了以上問題:

  • 面向領(lǐng)域建模,面向?qū)ο缶幊?,代碼直接映射現(xiàn)實(shí)世界概念,貼近業(yè)務(wù),離客戶更近
  • 領(lǐng)域邏輯高內(nèi)聚,符合Java開發(fā)原則
  • 技術(shù)細(xì)節(jié)變更如數(shù)據(jù)庫(kù)、緩存、定時(shí)器等的變更對(duì)業(yè)務(wù)邏輯影響比較小,非常適合插件式架構(gòu)
  • 代碼可讀性、可維護(hù)性更強(qiáng),對(duì)后續(xù)擴(kuò)展、移植等支持更好,分層更加科學(xué)

DDD的概念,在網(wǎng)上很容易找到,這里就不贅述了。

然而網(wǎng)上DDD的文章雖然很多,但大多數(shù)是理論知識(shí),介紹的無非就是一些名詞:戰(zhàn)略設(shè)計(jì)、戰(zhàn)術(shù)設(shè)計(jì)、核心域、支撐域、值對(duì)象、實(shí)體、聚合... 對(duì)我們實(shí)際落地卻沒有太多的幫助,下面介紹下我在SpringBoot中應(yīng)用DDD的落地方案。

落地方案

1、代碼分層

圖片

代碼分層

  • 用戶接口 :圖中的api包(即controller層,我嫌controller后綴太長(zhǎng)...)
  • 應(yīng)用層 :這里使用了命令模式,并且讀寫分離成了兩個(gè)包(command、query),如果不使用命令模式可以合并成一個(gè)service包
  • 領(lǐng)域?qū)?/strong> :domain包,使用JPA(對(duì)DDD有良好的支持)
  • 基礎(chǔ)設(shè)施層 :infra包,其他所有的公用組件都放在這里,如果使用DIP依賴倒置,那么實(shí)現(xiàn)類也放在這里。
  • model模型 :model包,用于存放不同層間傳遞的對(duì)象,這些對(duì)象我試過放到好些地方,最后發(fā)現(xiàn)還是提出來統(tǒng)一放在一個(gè)包下比較好(便于服務(wù)間調(diào)用時(shí)共用對(duì)象)

2、層級(jí)關(guān)系及模型傳遞

圖片

分層及調(diào)用關(guān)系

3、分層詳細(xì)說明

  • api包(controller)
@Tag(name = "用戶", description = "用戶")
@RestController
@RequestMapping(value = "/api/sys-user")
public class SysUserApi extends BaseApi {


    @ApiResult
    @Operation(summary = "根據(jù)ID查詢用戶")
    @GetMapping("/{id}")
    public SysUserVo get(@PathVariable Long id) {
        return queryExecutor.execute(new SysUserByIdQry(id));
    }


    @Pagination(total = true)
    @ApiResult
    @Operation(summary = "分頁(yè)查詢用戶")
    @GetMapping
    public List< SysUserVo > getList(SysUserQo sysUserQo) {
        return queryExecutor.execute(new SysUserListQry(sysUserQo));
    }


    @ApiResult
    @Operation(summary = "新增用戶")
    @PostMapping
    public void save(@Valid @RequestBody SysUserDto sysUserDto) {
        commandExecutor.execute(new SysUserCommonCmd(sysUserDto));
    }
}
  1. 在BaseApi中封裝了兩個(gè)命令執(zhí)行類queryExecutor和commandExecutor,調(diào)用應(yīng)用層時(shí)執(zhí)行不同的命令即可,無需@Autowired引入不同的服務(wù)
  2. @ApiResult加上這個(gè)自定義注解后,對(duì)返回結(jié)果統(tǒng)一封裝
  3. @Pagination加上這個(gè)自定義注解后,會(huì)自動(dòng)將分頁(yè)參數(shù)存入線程變量,后面查詢時(shí)也會(huì)自動(dòng)獲取分頁(yè)參數(shù),返回結(jié)果統(tǒng)一封裝時(shí)也會(huì)加上分頁(yè)信息
  4. Qo是查詢參數(shù)對(duì)象,Dto是增刪改等命令參數(shù)對(duì)象,返回對(duì)象為Vo,這里要注意,Entity絕對(duì)不能暴露到這一層,需要轉(zhuǎn)換為Vo再返回
  5. 在這一層中,每個(gè)方法幾乎就是一行執(zhí)行命令的語句,一般情況不進(jìn)行業(yè)務(wù)邏輯(當(dāng)然也有特殊情況咯)
  • command包
@AllArgsConstructor
public class SysDeptAddCmd implements Command Void > {


    private SysDeptDto sysDeptDto;


    @Override
    public Void execute(Executor executor) {
        // 獲取命令的接收者:領(lǐng)域服務(wù)
        SysDeptManager receiver = executor.getReceiver(SysDeptManager.class);
        // 對(duì)象模型轉(zhuǎn)換,由DTO轉(zhuǎn)為Entity,使用了MapStruct
        SysDept sysDept = SysDeptMapper.INSTANCE.toSysDept(sysDeptDto);
        // 使用JPA保存
        receiver.save(sysDept);
        return null;
    }
}
  1. 增刪改命令,很薄的一層,作為一項(xiàng)工作的組織者,幾乎沒有業(yè)務(wù)邏輯,調(diào)用領(lǐng)域服務(wù)和充血對(duì)象方法
  2. 命令模式,實(shí)現(xiàn)自定義Command接口,泛型為返回值
  3. 通過屬性和構(gòu)造方法(使用lombok注解)接收參數(shù)
  4. 一個(gè)命令里只有一個(gè)execute方法,缺點(diǎn)是會(huì)產(chǎn)生大量的命令類,一個(gè)類相當(dāng)于之前service類中的一個(gè)方法,但是這樣符合了單一職責(zé)原則
  5. 通過executor.getRecerver方法獲取到領(lǐng)域服務(wù)(manager)
  6. DTO絕對(duì)不下探到領(lǐng)域?qū)又?,需要先由DTO轉(zhuǎn)換為Entity(轉(zhuǎn)換方法這里使用的MapStruct,以后再單獨(dú)細(xì)講)
  • query包
@AllArgsConstructor
public class SysDeptByIdQry extends CommonQry< SysDeptVo > {


    private Long id;


    @Override
    public SysDeptVo execute(Executor executor) {
        if (id == null) {
            throw new BusinessException("部門ID不能為空");
        }
        QSysDept sysDept = QSysDept.sysDept;
        return queryFactory.select(this.fields())
                .from(sysDept)
                .where(sysDept.deleted.eq(false), sysDept.id.eq(id))
                .fetchOne();
    }


    /**
     * 部門VO映射
     *
     * @return QBean< SysDeptVo >
     */
    public static QBean< SysDeptVo > fields() {
        QSysDept sysDept = QSysDept.sysDept;
        return Projections.fields(
            SysDeptVo.class,
            sysDept.deptName,
            sysDept.orderNum,
            sysDept.id
        );
    }
}
  1. 命令模式,繼承自定義CommonQry基類(此類也實(shí)現(xiàn)了自定義的Command接口,其中引用了QueryDSL的queryFactory類,且封裝了分頁(yè)方法),泛型為返回值
  2. query包中的查詢命令與command包中的命令大體相同,唯一區(qū)別是query命令理論上直接查詢數(shù)據(jù)庫(kù),不調(diào)用領(lǐng)域?qū)?/li>
  3. 由于JPA對(duì)于復(fù)雜查詢不太好用,這里強(qiáng)烈推薦使用QueryDSL(以后再單獨(dú)細(xì)講),圖中是一個(gè)簡(jiǎn)單的使用例子
  • domain包

類較多,代碼部分不一一羅列:

  1. 由Entity類自動(dòng)生成數(shù)據(jù)庫(kù)表,僅維護(hù)Entity類(屏蔽數(shù)據(jù)庫(kù))
  2. 設(shè)計(jì)Entity時(shí)根據(jù)實(shí)際業(yè)務(wù)靈活使用@OneToMany、@OneToOne等注解(聚合根的概念)
  3. 聚合根不要太大,80%的情況一個(gè)聚合根中只包含一個(gè)實(shí)體(不要過度設(shè)計(jì)成大聚合根)
  4. 不要使用貧血模型,而是要面向?qū)ο?,屬于?duì)象的方法要放到對(duì)象中,但是對(duì)象中不建議引入倉(cāng)庫(kù)repository類,需要操作數(shù)據(jù)庫(kù)的方法寫在領(lǐng)域服務(wù)manager里
  5. 業(yè)務(wù)邏輯盡量寫在領(lǐng)域服務(wù)(manager)中,不斷提取、抽象不同的方法供應(yīng)用層調(diào)用
  6. 適當(dāng)?shù)氖褂妙I(lǐng)域事件,JPA可以在Entity中使用@DomainEvents注解來發(fā)送領(lǐng)域事件

心得

  1. 通過DDD對(duì)業(yè)務(wù)理解更加透徹,寫的代碼可以更好的傳達(dá)客戶的業(yè)務(wù)訴求
  2. 能夠盡情的編寫低耦合的、符合單一職責(zé)、開閉等原則、封裝、繼承、多態(tài)的代碼,是很身心愉悅的
  3. 前期相比傳統(tǒng)架構(gòu)代碼量更多,開發(fā)人員前期投入更多:
    • 領(lǐng)域的合理劃分、實(shí)體的合理設(shè)計(jì)
    • 大量的DTO、VO等數(shù)據(jù)對(duì)象
    • 大量的數(shù)據(jù)對(duì)象轉(zhuǎn)換方法
    • 大量的命令類
    • ...
  4. 但是,除非是特別簡(jiǎn)單的功能,對(duì)于一個(gè)中等復(fù)雜的系統(tǒng),這些前期的付出還是值得的,一張圖說明:
    圖片

小結(jié)

以上簡(jiǎn)單介紹了下我對(duì)DDD的理解和實(shí)踐,并通過實(shí)際的代碼展現(xiàn)了如何在SpringBoot中應(yīng)用DDD,希望能為大家提供一個(gè)思路。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3675

    瀏覽量

    94766
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4883

    瀏覽量

    70127
  • ddd
    ddd
    +關(guān)注

    關(guān)注

    0

    文章

    23

    瀏覽量

    3009
  • SpringBoot
    +關(guān)注

    關(guān)注

    0

    文章

    175

    瀏覽量

    296
收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    SpringBoot的Druid介紹

    SpringBootDruid數(shù)據(jù)源配置
    發(fā)表于 05-07 09:21

    SpringBoot知識(shí)總結(jié)

    SpringBoot干貨學(xué)習(xí)總結(jié)
    發(fā)表于 08-01 10:40

    springboot spring data jpa使用總結(jié)

    【本人禿頂程序員】springboot專輯:spring data jpa的使用
    發(fā)表于 04-15 11:38

    怎么學(xué)習(xí)SpringBoot

    SpringBoot學(xué)習(xí)之路(X5)- 整合JPA
    發(fā)表于 06-10 14:52

    怎樣去使用springboot

    怎樣去使用springboot呢?學(xué)習(xí)springboot需要懂得哪些?
    發(fā)表于 10-25 07:13

    SpringBoot應(yīng)用啟動(dòng)運(yùn)行run方法

    )、refreshContext(context);SpringBoot刷新IOC容器【創(chuàng)建IOC容器對(duì)象,并初始化容器,創(chuàng)建容器的每一個(gè)組件】;如果是web應(yīng)用創(chuàng)建**AnnotationConfigEmbeddedWebApplicationContext**,否則
    發(fā)表于 12-20 06:16

    黑客攻防入門與進(jìn)階ddd

    黑客攻防入門與進(jìn)階ddd黑客攻防入門與進(jìn)階ddd
    發(fā)表于 02-23 15:45 ?9次下載

    為什么建議你替換掉SpringBoot框架的Tomcat?

    SpringBoot框架,我們使用最多的是Tomcat,這是SpringBoot默認(rèn)的容器技術(shù),而且是內(nèi)嵌式的Tomcat。
    的頭像 發(fā)表于 01-07 10:26 ?1276次閱讀

    用好DDD必須先過Spring Data這關(guān)

    DDD 是一種領(lǐng)域驅(qū)動(dòng)的設(shè)計(jì)方法,旨在通過建立對(duì)領(lǐng)域模型的清晰理解來解決業(yè)務(wù)問題。和事務(wù)腳本不同,DDD 使用面向?qū)ο笤O(shè)計(jì)來應(yīng)對(duì)復(fù)雜的業(yè)務(wù)場(chǎng)景。
    的頭像 發(fā)表于 03-07 09:38 ?2280次閱讀

    什么是 SpringBoot?

    本文從為什么要有 `SpringBoot`,以及 `SpringBoot` 到底方便在哪里開始入手,逐步分析了 `SpringBoot` 自動(dòng)裝配的原理,最后手寫了一個(gè)簡(jiǎn)單的 `start` 組件,通過實(shí)戰(zhàn)來體會(huì)了 `
    的頭像 發(fā)表于 04-07 11:28 ?1587次閱讀
    什么是 <b class='flag-5'>SpringBoot</b>?

    SpringBoot的核心注解1

    今天跟大家來探討下SpringBoot的核心注解@SpringBootApplication以及run方法,理解下springBoot為什么不需要XML,達(dá)到零配置
    的頭像 發(fā)表于 04-07 14:34 ?866次閱讀
    <b class='flag-5'>SpringBoot</b>的核心注解1

    SpringBoot的核心注解2

    今天跟大家來探討下SpringBoot的核心注解@SpringBootApplication以及run方法,理解下springBoot為什么不需要XML,達(dá)到零配置
    的頭像 發(fā)表于 04-07 14:34 ?2110次閱讀
    <b class='flag-5'>SpringBoot</b>的核心注解2

    何在SpringBoot解決Redis的緩存穿透等問題

    今天給大家介紹一下如何在SpringBoot解決Redis的緩存穿透、緩存擊穿、緩存雪崩的問題。
    的頭像 發(fā)表于 04-28 11:35 ?869次閱讀

    DDD是什么?DDD核心概念梳理

    DDD 是什么,DDD 的英文全稱是 Domain-Driven Design,翻譯過來就是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。
    的頭像 發(fā)表于 09-07 11:12 ?9967次閱讀
    <b class='flag-5'>DDD</b>是什么?<b class='flag-5'>DDD</b>核心概念梳理

    DDD學(xué)習(xí)與感悟——向屎山?jīng)_鋒

    軟件系統(tǒng)是通過軟件開發(fā)來解決某一個(gè)業(yè)務(wù)領(lǐng)域或問題單元而產(chǎn)生的一個(gè)交付物。而通過軟件設(shè)計(jì)可以幫助我們開發(fā)出更加健壯的軟件系統(tǒng)。因此,軟件設(shè)計(jì)是從業(yè)務(wù)領(lǐng)域到軟件開發(fā)之間的橋梁。而DDD是軟件設(shè)計(jì)的其中
    的頭像 發(fā)表于 09-24 13:31 ?417次閱讀
    <b class='flag-5'>DDD</b>學(xué)習(xí)與感悟——向屎山?jīng)_鋒

    電子發(fā)燒友

    中國(guó)電子工程師最喜歡的網(wǎng)站

    • 2931785位工程師會(huì)員交流學(xué)習(xí)
    • 獲取您個(gè)性化的科技前沿技術(shù)信息
    • 參加活動(dòng)獲取豐厚的禮品