官术网_书友最值得收藏!

實(shí)施DDD所面臨的挑戰(zhàn)

在實(shí)施DDD的過程中,挑戰(zhàn)是不可避免的。那么,有人成功過嗎?DDD都有哪些常見的挑戰(zhàn),我們又如何處理它們?我將討論以下三點(diǎn)最常見的挑戰(zhàn):

? 為創(chuàng)建通用語言騰出時(shí)間和精力

? 持續(xù)地將領(lǐng)域?qū)<乙腠?xiàng)目

? 改變開發(fā)者對(duì)領(lǐng)域的思考方式

使用DDD最大的挑戰(zhàn)之一便是:我們需要花費(fèi)大量的時(shí)間和精力來思考業(yè)務(wù)領(lǐng)域,研究概念和術(shù)語,并且和領(lǐng)域?qū)<医涣鳎园l(fā)現(xiàn)、捕捉和改進(jìn)通用語言。如果你想完全采用DDD來最大化業(yè)務(wù)價(jià)值,你需要做出很多努力,并且花費(fèi)很多時(shí)間。事實(shí)就是這樣的。

要將領(lǐng)域?qū)<乙肽愕捻?xiàng)目恐怕也不是一件易事。但是不管有多么困難,這是你必須做的。如果你連一個(gè)領(lǐng)域?qū)<叶颊也坏剑敲茨愀緹o法對(duì)一個(gè)領(lǐng)域有深入的理解。當(dāng)你找到領(lǐng)域?qū)<业臅r(shí)候,此時(shí)開發(fā)者應(yīng)該表現(xiàn)出主動(dòng)。開發(fā)者應(yīng)該找領(lǐng)域?qū)<医徽劜⒆屑?xì)聆聽,然后將你們的談話轉(zhuǎn)化成軟件代碼。

如果你所工作的領(lǐng)域和業(yè)務(wù)相去甚遠(yuǎn),領(lǐng)域?qū)<宜私獾囊仓皇且恍┻呥吔墙牵敲创藭r(shí)你應(yīng)該將這種問題暴露出來。在我曾經(jīng)工作的一個(gè)項(xiàng)目里,真正的領(lǐng)域?qū)<液茈y找到,有時(shí)他們還會(huì)到處出差,我得等上好幾周才能和他們開上一次會(huì)。在一些小型的公司里,領(lǐng)域?qū)<彝ǔJ荂EO或者副總裁,他們的事情太多了,這時(shí)你也別指望他們能做好你的領(lǐng)域?qū)<摇?/p>

牛仔的邏輯

AJ:“如果你逮不到那頭公牛,你就得挨餓咯!”

img

引入領(lǐng)域?qū)<倚枰獎(jiǎng)?chuàng)造性……

如何在項(xiàng)目中引入領(lǐng)域?qū)<?/span>

img

咖啡。使用這種通用語言:

“Hi,Sally,我給你泡了一杯泡沫牛奶咖啡,你有時(shí)間聊聊……?”

學(xué)習(xí)C級(jí)經(jīng)理使用的通用語言:“……利潤……收入……競爭優(yōu)勢(shì)……市場優(yōu)勢(shì)。”

多數(shù)開發(fā)者在采用DDD時(shí)都需要轉(zhuǎn)變自己思考問題的方式。作為開發(fā)者,我們都是技術(shù)思想者,技術(shù)實(shí)現(xiàn)對(duì)于我們來說并不是什么難事。我并不是說技術(shù)地思考不好,只是說有時(shí)少從技術(shù)層面去思考會(huì)更好。這么多年來,我們都習(xí)慣了單從技術(shù)層面完成軟件開發(fā),那么現(xiàn)在,是時(shí)候考慮一種新的思考方式了。為你的業(yè)務(wù)領(lǐng)域開發(fā)一門通用語言便是一個(gè)好的出發(fā)點(diǎn)。

牛仔的邏輯

LB:“那家伙的靴子太小了,如果他不換雙新的,他的腳指頭可能要受罪了。”

AJ:“對(duì),如果他不聽的話,就有他好受的了。”

img

在DDD中,我們會(huì)談及到對(duì)概念的命名。對(duì)于概念命名而言,我們有更高層面的要求。當(dāng)我們對(duì)一個(gè)領(lǐng)域進(jìn)行建模時(shí),我們需要仔細(xì)地考慮什么樣的對(duì)象做什么樣的事情,這是關(guān)于對(duì)象行為設(shè)計(jì)的。我們希望對(duì)對(duì)象行為的命名能夠傳達(dá)準(zhǔn)確的業(yè)務(wù)含義,也即反映通用語言。要達(dá)到這樣的目的,肯定不是先在類上定義屬性,然后向客戶端代碼暴露getter和setter那么簡單。

現(xiàn)在讓我們來看看一個(gè)更有趣的領(lǐng)域,這個(gè)領(lǐng)域比之前那個(gè)Customer例子更具挑戰(zhàn)性。這里,我刻意重復(fù)一下先前所講的。

如果我們只是對(duì)領(lǐng)域模型提供getter和setter會(huì)怎么樣?答案是,結(jié)果我們只是在創(chuàng)建純數(shù)據(jù)模型。看看下面的兩個(gè)例子,自己思考一下,哪一個(gè)在設(shè)計(jì)上是欠妥的,哪一個(gè)對(duì)客戶代碼更有益。在這兩個(gè)例子中是一個(gè)Scrum模型,我們需要將一個(gè)待定項(xiàng)(Backlog Item)提交到?jīng)_刺(Sprint)中去。這樣的事情你可能一直在做,因此對(duì)這個(gè)領(lǐng)域你應(yīng)該是很熟悉的。

第一個(gè)例子,通常的做法,使用屬性訪問的方式:

img

客戶代碼如下::

img

第二個(gè)例子使用了領(lǐng)域?qū)ο蟮男袨椋@種行為表達(dá)出了領(lǐng)域中的通用語言:

img
img

此時(shí)的客戶代碼如下:

img

第一個(gè)例子采用的是以數(shù)據(jù)為中心的方式,此時(shí)客戶代碼必須知道如何正確地將一個(gè)待定項(xiàng)提交到?jīng)_刺中。這樣的模型是不能稱為領(lǐng)域模型的。如果客戶代碼錯(cuò)誤地修改了sprintId,而沒有修改status會(huì)發(fā)生什么呢?或者,如果在將來有另外一個(gè)屬性需要設(shè)值時(shí)又該怎么辦?我們需要認(rèn)真分析客戶代碼來完成從客戶數(shù)據(jù)到BacklogItem屬性的映射。

這種方式同時(shí)也暴露了BacklogItem的數(shù)據(jù)結(jié)構(gòu),并且將關(guān)注點(diǎn)集中在數(shù)據(jù)屬性上,而不是對(duì)象行為。你可能會(huì)反駁道:“setSprintId()和setStatus()就是行為啊。”問題在于,這里的“行為”沒有真正的業(yè)務(wù)價(jià)值,它并沒有表明領(lǐng)域模型中的概念——此處即“將待定項(xiàng)提交到?jīng)_刺中”。開發(fā)者在開發(fā)客戶代碼時(shí),他并不清楚到底需要為BacklogItem的哪些屬性設(shè)值,而這樣的屬性有可能存在很多,因?yàn)檫@是一個(gè)以數(shù)據(jù)為中心的模型。

現(xiàn)在,我們來看看第二個(gè)例子。有別于第一個(gè)例子,它將行為暴露給客戶,行為方法的名字清楚地表明了業(yè)務(wù)含義。這個(gè)領(lǐng)域的專家在建模時(shí)討論了以下需求:

允許將每一個(gè)待定項(xiàng)提交到?jīng)_刺中。只有在一個(gè)待定項(xiàng)位于發(fā)布計(jì)劃(Release)中時(shí)才能進(jìn)行提交。如果一個(gè)待定項(xiàng)已經(jīng)提交到了另外一個(gè)沖刺中,那么需要先將其回收 。提交完成時(shí),通知相關(guān)客戶方。

在第二個(gè)例子中,客戶代碼并不需要知道提交BacklogItem的實(shí)現(xiàn)細(xì)節(jié)。實(shí)現(xiàn)代碼所表達(dá)的邏輯恰好能夠描述業(yè)務(wù)行為。我們很容易地添加了幾行代碼,以確保在發(fā)布計(jì)劃之外的待定項(xiàng)是不能被提交的。誠然,在第一個(gè)例子中,你可以修改setter以達(dá)到同樣的目的,但此時(shí)該setter的職責(zé)便不單一了,它需要了解BacklogItem對(duì)象的內(nèi)部狀態(tài),而不再只是對(duì)sprintId和status屬性賦值。

這里還有一個(gè)微小的區(qū)別。如果一個(gè)待定項(xiàng)已經(jīng)被提交到了另外的沖刺中,那么我們應(yīng)該先從那個(gè)沖刺中回收該待定項(xiàng)。這一點(diǎn)也是重要的,因?yàn)楫?dāng)一個(gè)待定項(xiàng)從沖刺中回收時(shí),將有領(lǐng)域事件發(fā)出以通知客戶方:

允許從沖刺中回收任何一個(gè)待定項(xiàng),回收時(shí)通知相關(guān)客戶方。

此時(shí),我們并不需要關(guān)心如何發(fā)布回收事件,因?yàn)閡ncommitFrom()方法會(huì)為我們處理這些。而commitTo()方法甚至都不知道發(fā)布回收事件這碼事,它只需要知道,在將待定項(xiàng)提交給一個(gè)新的沖刺時(shí),必須先將該待定項(xiàng)從它當(dāng)前所在的沖刺中回收。另外,commitTo()的領(lǐng)域行為還包括:在提交待定項(xiàng)完畢后,以事件形式通知相關(guān)客戶方。如果不是這個(gè)富含行為的BacklogItem,我們得在客戶代碼中發(fā)布領(lǐng)域事件,這顯然是一種領(lǐng)域邏輯的泄漏。

很明顯,在第二個(gè)例子中,我們對(duì)BacklogItem有了更多的思考,但同時(shí)我們也獲得更多的回報(bào)。沿著這條路往下走,我們將越走越容易。到后來,我們肯定會(huì)需要更多的思考、付出和團(tuán)隊(duì)協(xié)作,但是這并不會(huì)使DDD變得笨重。

白板時(shí)間

? 對(duì)于你目前正在工作的業(yè)務(wù)領(lǐng)域,思考一下模型中的通用術(shù)語和業(yè)務(wù)操作。

? 將術(shù)語寫在白板上。

? 然后,將項(xiàng)目中所用到的短語也寫下來。

? 與真正的領(lǐng)域?qū)<医涣饕幌拢纯茨男┰~匯是可以改善的(記得帶上咖啡哦)。

為領(lǐng)域建模正名

通常來說,戰(zhàn)術(shù)建模比戰(zhàn)略建模復(fù)雜。因此,如果你打算采用DDD的戰(zhàn)術(shù)模式(聚合、領(lǐng)域服務(wù)、值對(duì)象和領(lǐng)域事件等)來建立領(lǐng)域模型的話,你需要更仔細(xì)的思考和更大的投入。那么,我們有什么理由依然要采用戰(zhàn)術(shù)建模呢?我們又拿什么標(biāo)準(zhǔn)來衡量在DDD上的投入是值得的呢?

你可能已經(jīng)在盤算,這將把你帶到一個(gè)陌生的領(lǐng)地,你發(fā)現(xiàn)你得好好研究一下周邊的情況。你的團(tuán)隊(duì)可能會(huì)學(xué)著既有的線路圖,甚至開辟一條新路來決定自己的戰(zhàn)略設(shè)計(jì)方案。你可能會(huì)仔細(xì)捉摸這片新的領(lǐng)地,然后試圖使其為你所用。然而,不管你事先做了多少準(zhǔn)備,這都將是一條荊棘叢生之路。

如果你發(fā)現(xiàn)你需要在戰(zhàn)略的巖石上攀爬,那么你得找到一套合適的戰(zhàn)術(shù)工具來輔助你。站在低處往上看,你有可能看到一些特別的挑戰(zhàn)和危險(xiǎn)地帶。然而,如果不爬到那樣的高度,你又是看不清楚的。你可能需要在堅(jiān)硬的巖石上打孔插釘,但是也可以找到那些自然形成的裂縫。你可能還需要帶上鎖環(huán)以保證安全。你可以沿著一條路線順直而上,也可以打點(diǎn)布陣、步步為營。有時(shí)隨著巖石形狀的走勢(shì),你可能需要往回撤,再重新設(shè)計(jì)路線。有人認(rèn)為攀巖是種危險(xiǎn)的運(yùn)動(dòng),但是那些嘗試過的人會(huì)告訴你,攀巖實(shí)際上比駕駛汽車和飛機(jī)還安全。攀巖者需要知道如何使用工具和運(yùn)用好技能,并且能夠根據(jù)巖石狀況做出相應(yīng)的反應(yīng)。

如果說開發(fā)一個(gè)業(yè)務(wù)子域(Subdomain,2)就像攀巖一樣困難,那么我們需要隨身攜帶DDD的戰(zhàn)術(shù)模式來武裝自己。對(duì)于滿足核心域標(biāo)準(zhǔn)的業(yè)務(wù)來說,我們不應(yīng)該將戰(zhàn)術(shù)模式拒之門外。半途而廢的項(xiàng)目時(shí)有發(fā)生,而正確的戰(zhàn)術(shù)模式可以幫助我們減少這種情況的發(fā)生。

這里是一些實(shí)際的指導(dǎo)意見,我會(huì)先講高層次的,然后講更具體的:

? 如果一個(gè)限界上下文被當(dāng)成核心域來開發(fā),那么從戰(zhàn)略上來說,這個(gè)限界上下文對(duì)業(yè)務(wù)的成功是極其重要的。核心模型是不易理解的,需要不斷地嘗試和重構(gòu)。通過持續(xù)改進(jìn),我們可以延長它的效用生命,這樣的做法顯然是值得的。當(dāng)然,這個(gè)限界上下文不見得始終是你的核心域。即便如此,如果它是復(fù)雜的,創(chuàng)新性的,并且需要在不斷的變化中持續(xù)存在很長時(shí)間,我們還是建議在該限界上下文中使用戰(zhàn)術(shù)模式。這里,我們假設(shè)你的核心域是值得配置最好的開發(fā)者的。

? 一個(gè)領(lǐng)域,對(duì)于消費(fèi)方來說有可能成為通用子域(Generic Subdomain,2)或者支撐子域,但是卻有可能成為你自己的核心域。我們并不站在最終消費(fèi)方的角度來評(píng)價(jià)一個(gè)領(lǐng)域。如果你正在開發(fā)的限界上下文是你主要的業(yè)務(wù),那么它便是你的核心域,而不管消費(fèi)方是如何看待的。此時(shí),一定記得使用戰(zhàn)術(shù)模式。

? 如果你正開發(fā)一個(gè)支撐子域,但是由于種種原因,該支撐子域不能從第三方的通用子域直接獲得,那么此時(shí)戰(zhàn)術(shù)模式將幫上你大忙。在這種情況下,你需要考慮團(tuán)隊(duì)成員的技能水平,還有模型是否具有創(chuàng)新性。如果此時(shí)的模型增加了特定的業(yè)務(wù)價(jià)值,而且不只是擁有技術(shù)上的絢麗,那么該模型就可以認(rèn)為是創(chuàng)新性的。如果團(tuán)隊(duì)有能力實(shí)施戰(zhàn)術(shù)設(shè)計(jì),這個(gè)支撐子域又是創(chuàng)新性的,并且將持續(xù)存在很長時(shí)間,那么此時(shí)便是采用戰(zhàn)術(shù)設(shè)計(jì)的大好時(shí)機(jī)。盡管如此,這并不能使該子域稱為核心域,因?yàn)樵跇I(yè)務(wù)人士眼中,這樣的領(lǐng)域只是支撐性的。

對(duì)于有豐富DDD經(jīng)驗(yàn)的開發(fā)者來說,上面的指導(dǎo)建議可能就不夠了。如果你的團(tuán)隊(duì)經(jīng)驗(yàn)豐富,其中的開發(fā)者又確信戰(zhàn)術(shù)建模是種好的選擇,那么此時(shí)他們的意見可能就更值得相信。誠實(shí)的開發(fā)者,不管經(jīng)驗(yàn)豐富與否,都會(huì)在特定的情況下明確地指出領(lǐng)域建模是否為最佳的選擇。

業(yè)務(wù)領(lǐng)域的類型本身并不自動(dòng)地決定應(yīng)該選擇哪種開發(fā)方式。你的團(tuán)隊(duì)?wèi)?yīng)該考慮一些重要的問題,然后做出決定。請(qǐng)考慮以下因素,這些因素和上面提到的高層次指導(dǎo)是有對(duì)應(yīng)關(guān)系的。

? 團(tuán)隊(duì)是否有領(lǐng)域?qū)<遥绻校闳绾螄@領(lǐng)域?qū)<医M織自己的團(tuán)隊(duì)?

? 雖然就目前來說,你的業(yè)務(wù)領(lǐng)域是簡單的,但它將來會(huì)變得復(fù)雜嗎?對(duì)于復(fù)雜的系統(tǒng)來說,使用事務(wù)腳本是存在風(fēng)險(xiǎn)的。當(dāng)領(lǐng)域變得復(fù)雜時(shí),是否有可能將系統(tǒng)重構(gòu)到富含行為的領(lǐng)域模型?

? DDD的戰(zhàn)術(shù)模式是否可以簡化與其他限界上下文的集成,不管是第三方的還是定制開發(fā)的?

? 使用事務(wù)腳本是否的確可以減少代碼量?(經(jīng)驗(yàn)表明,不管是對(duì)于哪種開發(fā)方式,事務(wù)腳本都不能減少代碼量。這可能是由于在項(xiàng)目計(jì)劃階段,領(lǐng)域復(fù)雜性并沒得到正確的認(rèn)識(shí)所致。因此,我們需要在領(lǐng)域復(fù)雜性上下足功夫。)

? 你項(xiàng)目的進(jìn)度安排是否允許在戰(zhàn)術(shù)模式上有所投入?

? 在核心域上的戰(zhàn)術(shù)投入能否消除架構(gòu)變化所帶來的影響?事務(wù)腳本是做不到這一點(diǎn)的(和領(lǐng)域模型層相比,其他層更易受到架構(gòu)變化的影響)。

? 客戶是否的確能從這種持續(xù)設(shè)計(jì)和開發(fā)的方式中獲益,或者有現(xiàn)成的產(chǎn)品就能滿足他們的需求?換句話說,我們是否應(yīng)該一開始就考慮定制化開發(fā)?

? 使用DDD的戰(zhàn)術(shù)開發(fā)模式會(huì)比其他開發(fā)方式更加困難嗎,比如事務(wù)腳本?(這個(gè)問題很大程度上取決于團(tuán)隊(duì)成員的技能水平和是否有領(lǐng)域?qū)<摇#?/p>

? 如果團(tuán)隊(duì)已經(jīng)具備了實(shí)施DDD的條件,我們還會(huì)刻意地選擇另一種開發(fā)方式嗎?有些開發(fā)者已經(jīng)將模型的持久化變得很實(shí)用了,比如使用ORM、全聚合序列化和持久化、事件存儲(chǔ)(Event Store)、或者戰(zhàn)術(shù)DDD框架等。但是我們也不能排除還有熱衷于其他開發(fā)方式的開發(fā)者。

上面的列表項(xiàng)并沒有先后順序,而你自己也可以制定另外的衡量標(biāo)準(zhǔn)。你應(yīng)該知道哪些開發(fā)方法對(duì)你來說是最好的,同時(shí)還應(yīng)該全景式地了解你的業(yè)務(wù)和技術(shù)。有一點(diǎn)需要記住:你最終得取悅你的客戶,而不是技術(shù)開發(fā)者,所以你得慎重地做出選擇。

DDD并不笨重

在我看來,DDD絕非是充滿繁文縟節(jié)的笨重開發(fā)過程。事實(shí)上,DDD能夠很好地與敏捷項(xiàng)目框架結(jié)合起來,比如Scrum。DDD也傾向于“測試先行,逐步改進(jìn)”的設(shè)計(jì)思路。在你開發(fā)一個(gè)新的領(lǐng)域?qū)ο髸r(shí),比如實(shí)體或值對(duì)象,你可以采用以下步驟進(jìn)行:

1. 編寫測試代碼以模擬客戶代碼是如何使用該領(lǐng)域?qū)ο蟮摹?/p>

2. 創(chuàng)建該領(lǐng)域?qū)ο笠允箿y試代碼能夠編譯通過。

3. 同時(shí)對(duì)測試和領(lǐng)域?qū)ο筮M(jìn)行重構(gòu),直到測試代碼能夠正確地模擬客戶代碼,同時(shí)領(lǐng)域?qū)ο髶碛心軌虮砻鳂I(yè)務(wù)行為的方法簽名。

4. 實(shí)現(xiàn)領(lǐng)域?qū)ο蟮男袨椋钡綔y試通過為止,再對(duì)實(shí)現(xiàn)代碼進(jìn)行重構(gòu)。

5. 向你的團(tuán)隊(duì)成員展示代碼,包括領(lǐng)域?qū)<遥员WC領(lǐng)域?qū)ο竽軌蛘_地反映通用語言。

你可能會(huì)想:“這和我之前采用的測試驅(qū)動(dòng)開發(fā)沒什么區(qū)別啊。”對(duì),他們可能有細(xì)微的區(qū)別,但是基本思路是一樣的。測試代碼并不能保證我們的領(lǐng)域?qū)ο缶褪菬o懈可擊的。之后,我們還會(huì)添加另外的測試代碼。首先,我們關(guān)注的是客戶代碼如何使用領(lǐng)域?qū)ο螅藭r(shí)的測試代碼驅(qū)動(dòng)著模型的設(shè)計(jì)。這種方式和敏捷開發(fā)并沒有多大區(qū)別。因此,即便你并不認(rèn)為上面的步驟是敏捷,但它們的確表明,DDD采用的是一種“敏捷的”方式進(jìn)行軟件開發(fā)的。

在這之后,你會(huì)添加更多的測試,從多個(gè)角度確保新建領(lǐng)域?qū)ο蟮恼_性。此時(shí)你關(guān)注的是領(lǐng)域?qū)ο髮?duì)于領(lǐng)域概念的表達(dá)力,而測試代碼本身便是通用語言在程序中的表達(dá)。在開發(fā)人員的幫助下,領(lǐng)域?qū)<铱梢酝ㄟ^閱讀測試代碼來檢驗(yàn)領(lǐng)域?qū)ο笫欠駶M足業(yè)務(wù)需求。這也意味著測試數(shù)據(jù)應(yīng)該是真實(shí)的,因?yàn)檫@樣可以增加測試代碼的業(yè)務(wù)表達(dá)力。否則,領(lǐng)域?qū)<沂呛茈y對(duì)你的實(shí)現(xiàn)做出評(píng)判的。

以上的開發(fā)步驟將不斷重復(fù),直到領(lǐng)域模型滿足本次迭代的計(jì)劃任務(wù)為止。這種方法是敏捷的,同時(shí),它也是極限編程(Extreme Programming)所倡導(dǎo)的。因此,使用敏捷并不會(huì)消除DDD的模式和實(shí)踐,而相反,它們可以很好地結(jié)合起來。當(dāng)然,在實(shí)施DDD時(shí),你也可以不采用測試驅(qū)動(dòng)開發(fā),而是對(duì)既有的模型編寫測試。但無論如何,從客戶的角度來設(shè)計(jì)領(lǐng)域模型是大有好處的。

主站蜘蛛池模板: 兴化市| 阿鲁科尔沁旗| 岑巩县| 岗巴县| 明星| 乌鲁木齐县| 东丰县| 武川县| 寿阳县| 女性| 高邑县| 庆阳市| 南部县| 上林县| 清丰县| 汉阴县| 乌兰县| 五家渠市| 金塔县| 鹤山市| 浦北县| 凌源市| 丁青县| 蓬莱市| 揭阳市| 罗山县| 新兴县| 乌恰县| 开封市| 塔城市| 永嘉县| 重庆市| 云安县| 新和县| 兴国县| 简阳市| 澜沧| 密山市| 云梦县| 六枝特区| 红桥区|