本項目是基于小編的開發(fā)經(jīng)驗與心得,分享小編關(guān)于領(lǐng)域模型的理解, 個人愚見僅供參考,希望能為渴望進步的你提供幫助。如果你感到有用對你有幫助,請不要吝嗇你的關(guān)注,求關(guān)注,求轉(zhuǎn)發(fā)。
文章有三個議題,什么是領(lǐng)域模型,為什么需要領(lǐng)域模型設(shè)計,以及領(lǐng)域驅(qū)動的項目結(jié)構(gòu)是什么樣的?
一、領(lǐng)域驅(qū)動模型是什么、領(lǐng)域驅(qū)動模型是什么?
一、
一、領(lǐng)域驅(qū)動模型是什么?
如果你是第一次聽到這個詞,嗯,多么恐怖的一件事情呀! 什么是領(lǐng)域模型,一種新的技術(shù)嗎? 領(lǐng)域模型到底有什么用呢?
為什么那么多大佬都在講領(lǐng)域模型。網(wǎng)絡(luò)上充斥著著各種高端的解釋,各種高大上的名字,各種復(fù)雜的系統(tǒng)設(shè)計圖。
fuck !身邊總是有這樣一群人的出現(xiàn)。總喜歡中文里夾雜英文,英文中夾雜著中文,仿佛這樣能使他們更加自信一樣。把你講懵了,他就自信了。 very fuck !
身為技術(shù)人,盡量想把一種事情給講清楚,說明白。而不是用各種抽象的晦澀難懂但看上去高大上的名詞給解釋。千萬不要怕,下面我們通過先做一點小小的鋪墊。最后在總結(jié)領(lǐng)域模型的理解。
1. 貧血模型
1. 貧血模型
1. 貧血模型
在講清楚領(lǐng)域模型之前我們先來看引入一個詞匯 “貧血模型” ,讀到這里不要怕。只是一個詞匯而已。是對我們平時的項目代碼結(jié)構(gòu)的一個形容詞。相信無論面前的你
是一個大牛,還是一個剛?cè)胄械男〔锁B。你都一定寫過這樣的代碼:
- dao層: 負(fù)責(zé)持久化
- model層: 數(shù)據(jù)模型
- service層: 服務(wù)層
- web層: 提供對UI層的訪問
嗯。這就是一個典型的貧血模型, 哇,真的好形象,這是誰想出來的詞匯,真想給他說一句 fuck you! 但是,但是,你還有更好的詞匯來形容這種項目結(jié)構(gòu)嗎?
所謂貧血模型是指使用的領(lǐng)域?qū)ο笾兄挥?`setter` 和 `getter` 方法(POJO),所有的業(yè)務(wù)邏輯都不包含在領(lǐng)域?qū)ο笾卸欠旁跇I(yè)務(wù)邏輯層。
往往我們?nèi)胄械某跗谖覀兌际窃谶@樣的項目結(jié)構(gòu)中進行編程的,那個時候我們的業(yè)務(wù)往往都是簡單的,對于那個時候的我們來說,這樣的代碼結(jié)構(gòu)真是太好用了。清晰易懂。甚至想說一聲 i love code !!!
這個時期,我們的關(guān)注點往往不是業(yè)務(wù)的復(fù)雜度,而是技術(shù)的使用,語法的使用。以及代碼是否能編譯通過。所以下面我們來總結(jié)一下貧血模型的優(yōu)點。
2. 貧血模型優(yōu)點
1. 被許多程序員所掌握,對于剛?cè)胄械耐瑢W(xué)來說,這種模型很自然很舒服,典型的MVC結(jié)構(gòu)
2. 它非常簡單,對于并不復(fù)雜的業(yè)務(wù),它工作得很好,開發(fā)起來非常迅速。它似乎也不需要對領(lǐng)域的充分了解,只要給出要實現(xiàn)功能的每一個步驟,就能實現(xiàn)它。
3. 事務(wù)邊界相當(dāng)清楚,一般來說service的每個方法都可以看成一個事務(wù)。
3. 貧血模型缺點
隨著發(fā)際線推移,隨著歷史的變遷,隨著候鳥的遷徙。不知不覺我們的業(yè)務(wù)越來越復(fù)雜了。萬惡的資本家,總想讓我們一夜之間開發(fā)一個淘寶,一夜之間開發(fā)一個百度,一夜之間開發(fā)一個QQ。于是我們的service層,不斷的
不斷的增加。代碼量從100行,200行,300行,10000行剎不住車了。終于小張忍不住了,辭職走了。留下了孤獨的你獨自承受這憂傷。
這樣代碼是什么意思? 這樣代碼能不能刪?這行代碼怎么沒有走?這樣代碼能不能拆出去? 這樣改萬一項目上線崩潰了怎么辦? 想一想老婆,望一望孩子。哎,算了吧。于是乎service復(fù)雜度指數(shù)般的遞增。這就是貧血模型的缺點。
缺點
- 所有的業(yè)務(wù)都在service中處理,當(dāng)業(yè)越來越復(fù)雜時,service會變得越來越龐大,最終難以理解和維護,輕則項目組解散,重則妻離子散。
- 將所有的業(yè)務(wù)放在無狀態(tài)的service中實際上是一個過程化的設(shè)計,這與面向?qū)ο蟮木幊田L(fēng)格,相向而行。(你轉(zhuǎn)身離開分手說不出來,海鳥跟魚相愛只是一場意外)
- 項目代碼寫的不少,重用的不多。(fuck and fuck = double kill)
4. 充血模
4. 充血模型
前面說我說了貧血模型,這里順便提一下充血模型,也不要怕,也只是一個嚇人的詞匯。前面我們理解了貧血模型,那么充血模型,很容易就能理解。
前面我們說貧血模型實體類只有SET GET方法,邏輯基本在服務(wù)層實現(xiàn)。而充血模型它的實體類里不但有狀態(tài),還有行為,即屬性和方法都有。它的Service層很薄。顯然這不符合MVC的思想,因為充血模型中model中不僅有數(shù)據(jù),還有狀態(tài)。維護起來非常麻煩。
5. 領(lǐng)域驅(qū)動總結(jié)
針對貧血模型的service層非常復(fù)雜臃腫的缺點,領(lǐng)域模型的概念越來越流行起來,至少在一些很多的大公司中,非常盛行。領(lǐng)域模型的概念不僅可以重新去設(shè)計service,同時也在微服務(wù)設(shè)計中有重要的意義。所以說領(lǐng)域模型其實就是要解決service越來越臃腫的一種設(shè)計思想。主要就是對service中的復(fù)雜的業(yè)務(wù)邏輯進行拆分,根據(jù)領(lǐng)域來進行拆分。用面向?qū)ο蟮乃枷肴ブ匦略O(shè)計service。有人給他起了一個高大上的詞匯: 領(lǐng)域模型。
所以最后小編想用一大白話來總結(jié)一下領(lǐng)域模型。
領(lǐng)域模型就是要用面向?qū)ο蟮乃枷肴ブ匦略O(shè)計充斥著復(fù)雜業(yè)務(wù)邏輯的service層。
二、為什么要進行領(lǐng)域模型設(shè)計?
相信看到這里的你,一定對領(lǐng)域模型有一個自己的認(rèn)識。為什么要進行領(lǐng)域模型設(shè)計? 相信自己心里一定有一個自己的判斷了。貧血模型的項目結(jié)構(gòu), service層無可避免的非常的臃腫,臃腫到一個方法可能深不見底。對于業(yè)務(wù)老油條,可能還湊合能看成,
假如你是一個新的同學(xué),當(dāng)你看到這樣的代碼一定是崩潰的,假如說注釋也沒有,那你內(nèi)心更是崩潰的。假如說這是一個很龐大的系統(tǒng),很復(fù)雜的業(yè)務(wù)流程,這就更不用說了。
如果讀到這里,你還是對領(lǐng)域驅(qū)動設(shè)計感到迷茫,那么就其實這個標(biāo)題也可以這樣講: 我們?nèi)绾螌τ纺[的service進行面向?qū)ο蟮脑O(shè)計。設(shè)計的過程就是對service層的代碼進行領(lǐng)域設(shè)計。
而我們之所以這樣做的目的。
- 為了快樂的coding
- 為了業(yè)務(wù)系統(tǒng)的穩(wěn)定
- 為了業(yè)務(wù)更快的迭代升級。
當(dāng)然這一切的前提是你對業(yè)務(wù)有一個全局的認(rèn)識,有一個前瞻性的判斷,否則也設(shè)計不出來,真正適合自身系統(tǒng)的領(lǐng)域驅(qū)動模型。
三、領(lǐng)域驅(qū)動的項目結(jié)構(gòu)是什么樣的?
一千個人眼里有一千個哈姆雷特,沒有最好的項目結(jié)構(gòu),只有最適合自己的業(yè)務(wù)系統(tǒng)。本文只是小編對領(lǐng)域驅(qū)動的模塊的思考和認(rèn)識。
僅供參考,希望對你有所啟示和引導(dǎo)。
1. 領(lǐng)域劃分|模塊化建造
領(lǐng)域劃分,小編感覺用另外一個詞形容也非常的合適,就是業(yè)務(wù)模塊化。所有能力都進行能力化抽象,形成模塊,形成領(lǐng)域。 當(dāng)遇到新的業(yè)務(wù)邏輯,底層的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)關(guān)系肯定也是一樣的。那么就可以像堆積木一樣,根據(jù)這些模塊快速的組裝成新的業(yè)務(wù)邏輯。快速的實現(xiàn)業(yè)務(wù)的迭代和升級。
關(guān)于這個問題,需要結(jié)合自己的業(yè)務(wù)系統(tǒng)來進行抽象和設(shè)計。而小編的能做的就是,提醒你模塊化設(shè)計,領(lǐng)域化設(shè)計的重要意義。
2. 項目結(jié)構(gòu)
基礎(chǔ)層(外部調(diào)用,db操作) + 領(lǐng)域?qū)?偏向領(lǐng)域的業(yè)務(wù)邏輯) + 業(yè)務(wù)層(對領(lǐng)域?qū)拥臉I(yè)務(wù)編排) + 外觀層(可以提供能力,可以提供視圖)。
有一個完善的領(lǐng)域?qū)?可以方便快速便捷的對業(yè)務(wù)進行擴展。
領(lǐng)域?qū)泳褪悄K化設(shè)計的積木。豐富的模塊化有助于業(yè)務(wù)擴展。
一定要控制項目的依賴情況。service只能出現(xiàn)領(lǐng)域?qū)拥囊蕾? 領(lǐng)域?qū)又荒艽嬖赿ao層和第三方服務(wù)層。各個層代碼不能平行調(diào)用。
3. 編程規(guī)范
關(guān)于項目提出6個注意的點。如果把做項目比作是前線打仗,那么打仗最重要的是戰(zhàn)斗成員目標(biāo)要一致。在目標(biāo)不一致的情況下一定要進行充分討論(項目負(fù)責(zé)人要做的),說明情況互相妥協(xié)指定出統(tǒng)一的項目編程規(guī)范。去進行執(zhí)行。一旦指定不能違背。否則項目質(zhì)量不保。
項目固然重要,但是作為軟件開發(fā)工程師,首先要對代碼質(zhì)量做保障。
4. 日志設(shè)計
天下沒有完美的項目,任何系統(tǒng)不存在bug是不可能的。想要發(fā)現(xiàn)bug并快速定位問題,日志系統(tǒng)的不能缺少的。
日志系統(tǒng)是非常重要的系統(tǒng), 對系統(tǒng)的監(jiān)控, 在設(shè)計日志系統(tǒng)中,我們需要關(guān)注的點
- 日志結(jié)構(gòu)(目的是按照結(jié)構(gòu)解析到日志引擎中)
如果想做日志的搜索平臺,一定要進行日志結(jié)構(gòu)化設(shè)計,方便被搜索平臺的解析。如ELK日志搜索系統(tǒng)。 - 日志打印降級能力
在遇到大促時候,可以減少不必要的日志打印,要對日志打印做降級的設(shè)計 - 異步輸入日志
- 日志歸檔
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 系統(tǒng)日志打印 -->
<Appender name="logfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${logger.logback.logpath}mbp-game-service.log</File>
<encoder>
<Pattern>[%date] [%-5level] %c{40} %line --%mdc{client} [%X{TRACE_LOG_ID}] [%X{dstTraceId}] %msg%n</Pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logger.logback.logpath}mbp-game-service.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>512MB</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- 異步輸出 -->
<appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丟失日志.默認(rèn)的,如果隊列的80%已滿,則會丟棄TRACT、DEBUG、INFO級別的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默認(rèn)的隊列的深度,該值會影響性能.默認(rèn)值為256 -->
<queueSize>1024</queueSize>
<!-- 添加附加的appender,最多只能添加一個 -->
<appender-ref ref="logfile"/>
</appender>
<!-- 外部jar包 日志級別設(shè)置 -->
<logger level="${logger.outside.logLevel}" name="com.ibatis"/>
<logger level="${logger.outside.logLevel}" name="org.springframework"/>
<logger level="${logger.outside.logLevel}" name="JAVA.sql"/>
<logger level="${logger.outside.logLevel}" name="org.Apache"/>
<logger level="${logger.outside.logLevel}" name="com.alibaba.dubbo"/>
<logger level="${logger.outside.logLevel}" name="org.I0Itec"/>
<logger level="${logger.outside.logLevel}" name="org.dozer"/>
<logger level="${logger.outside.logLevel}" name="kafka.producer.SyncProducer"/>
<logger level="${logger.kafka.outside.logLevel}" name="org.apache.kafka"/>
<logger level="${logger.kafka.outside.logLevel}" name="org.springframework.kafka"/>
<!-- 輸出到文件,可定義更多的 Appender -->
<root level="${logger.logLevel}">
<appender-ref ref="asyncAppender"/>
</root>
</configuration>