Spring 中的注解與分層思想
在Spring框架中最常見(jiàn)的幾個(gè)注解
@Controller, @Service, @Component, @Repository
其中@Component是一種通用名稱,泛指任意可以通過(guò)Spring來(lái)管理的組件,@Controller, @Service, @Repository則是一種特定的組件,通常用來(lái)表示某種特定場(chǎng)合下的組件,比如@Repository用來(lái)表示倉(cāng)庫(kù)(數(shù)據(jù)層,DAO),并且Spring 框架會(huì)根據(jù)這種應(yīng)用場(chǎng)景做些定制,比如@Repository同時(shí)具備了自動(dòng)化的異常轉(zhuǎn)換。類似的, @Service則用來(lái)表示服務(wù)層相關(guān)的類, @Controller則用來(lái)表示展示層(presentation)的類。
那Service是什么呢?
Service 表示了在軟件分層設(shè)計(jì)中的Service層,用來(lái)連結(jié)數(shù)據(jù)層(DAO)和展示層(Presentation)。
為什么要在DAO層上加一層Service呢?
在某些簡(jiǎn)單的應(yīng)用中,DAO層的功能和Service的功能很接近,甚至初學(xué)者會(huì)覺(jué)得Service層做的事情和DAO層都一樣,那為啥還要將Service層單獨(dú)拿出來(lái)做一遍呢?而且,很多場(chǎng)景下,Service層和DAO層同時(shí)存在,往往會(huì)增加代碼復(fù)雜度,編碼工作量,寫(xiě)的不好甚至?xí)斐苫煜?/p>
通常來(lái)說(shuō),DAO層應(yīng)盡力保持簡(jiǎn)單,其功能僅僅是提供了數(shù)據(jù)庫(kù)的連接,以及最簡(jiǎn)單的增刪改查(Crud),有時(shí)還需要做些抽象,以此來(lái)連接使用不同技術(shù)的數(shù)據(jù)庫(kù)。除此之外,任何業(yè)務(wù)相關(guān)的操作都應(yīng)該放到Service層,即Service層用來(lái)編寫(xiě)業(yè)務(wù)邏輯,即操作從DAO層讀取的數(shù)據(jù),或者將處理好的數(shù)據(jù)給DAO層,當(dāng)使用Domain Driven Design時(shí), 這兩個(gè)類通常會(huì)放到同一個(gè)Domain(包)中,即便在簡(jiǎn)單的應(yīng)用中,他們的代碼可能極其類似,但是仍應(yīng)該分別對(duì)待。而不是跳過(guò)service層(service)直接去使用DAO層(repository)來(lái)放業(yè)務(wù)邏輯數(shù)據(jù)。
這樣帶來(lái)的好處帶來(lái)更好的模塊化結(jié)構(gòu),有便于后期的擴(kuò)展和維護(hù),比如更換數(shù)據(jù)庫(kù)實(shí)現(xiàn)時(shí),我們僅僅需要處理DAO層的內(nèi)容就好了。并且,當(dāng)業(yè)務(wù)邏輯比較復(fù)雜的時(shí)候,比如有很多報(bào)告要出的時(shí)候,Service層就提供了一個(gè)很好的空間來(lái)實(shí)現(xiàn)這些代碼。
其次,在web應(yīng)用開(kāi)發(fā)中,使用Service層可以將web類的活動(dòng)限制在controller中,這樣可以獨(dú)立的測(cè)試service層
另外,還有一種情況,就是當(dāng)應(yīng)用極其復(fù)雜,需要同時(shí)使用多種數(shù)據(jù)庫(kù)時(shí),將從DAO中獲取數(shù)據(jù)的動(dòng)作放到一起可以減少數(shù)據(jù)庫(kù)的操作,并且可以保證數(shù)據(jù)的一致性。同時(shí)Service可以嵌套,因此如果需要使用不同的數(shù)據(jù)庫(kù)時(shí),可以在service中指定。
在Service中也可以放一些通知類的操作,比如發(fā)送郵件等,這樣也可以保持controller的整潔。
還有一個(gè)潛在的好處是安全性,當(dāng)使用service層包裹DAO層后,數(shù)據(jù)庫(kù)的鏈接是被service層保護(hù)起來(lái)的,這樣如果客戶端被某種情況攻陷,其只能使用service層提供的有限數(shù)據(jù),而無(wú)法直接攻擊數(shù)據(jù)庫(kù)
另外,在Spring 框架中,security也是在Service層實(shí)現(xiàn)的。根據(jù)上面的邏輯,我們?cè)趯?shí)際開(kāi)發(fā)中,應(yīng)該不去實(shí)現(xiàn)自己的DAO層,而是使用Spring Data JPA,因?yàn)镾pring Data JPA已經(jīng)實(shí)現(xiàn)了DAO層。
這種寫(xiě)法常見(jiàn)的問(wèn)題有啥?
最常見(jiàn)的寫(xiě)法(或者是錯(cuò)誤的寫(xiě)法)有以下幾種
1、面向領(lǐng)域的模型對(duì)象僅僅用來(lái)存儲(chǔ)應(yīng)用中的數(shù)據(jù),換句話說(shuō),是不太符合domain model 設(shè)計(jì)的
2、處理模型數(shù)據(jù)的業(yè)務(wù)邏輯分散在service層
3、每個(gè)entity都有對(duì)應(yīng)的service類
這樣寫(xiě)的原因很大程度來(lái)源于上面的分層理論,我們確實(shí)將應(yīng)用分成了展示層(web layer),服務(wù)層(service layer),數(shù)據(jù)層(repository/dao),但是實(shí)際后果卻是一個(gè)極其龐大的service層,這種寫(xiě)法可以算是一個(gè)面向過(guò)程開(kāi)發(fā)的代碼(procedural code), 而不是面向?qū)ο箝_(kāi)發(fā)。好處是簡(jiǎn)單,當(dāng)業(yè)務(wù)不復(fù)雜時(shí),確實(shí)沒(méi)有必要使用一個(gè)龐大的面向?qū)ο箝_(kāi)發(fā)框架(domain driven design)。
一個(gè)責(zé)任并不明確的service層主要有以下問(wèn)題
1、業(yè)務(wù)邏輯分散在service層中,當(dāng)我們需要確認(rèn)或者檢查某個(gè)業(yè)務(wù)邏輯時(shí),可能要在多個(gè)service類中尋找,也許并不那么容易,另外如果同樣的業(yè)務(wù)邏輯在多個(gè)service類中用到時(shí),那么可能會(huì)存在大量的重復(fù)代碼,這種重復(fù)代碼對(duì)于維護(hù)人員來(lái)說(shuō)就是惡魔。
2、在service層中,每個(gè)entity都有對(duì)應(yīng)的service類時(shí),service層會(huì)有過(guò)多的依賴,甚至是循環(huán)依賴關(guān)系,而不是由松散耦合的service類構(gòu)成service層,理想中的service層應(yīng)該是由具有單一責(zé)任的service類構(gòu)成,并且這些service類具有松耦合關(guān)系,如果不是這樣的service層,將難以理解,維護(hù)和重用。
主要的解決方法是
1、將與entity相關(guān)的業(yè)務(wù)邏輯統(tǒng)一放到領(lǐng)域模型對(duì)象相關(guān)的類中,即所謂的domain service中。這樣做的好處時(shí),傳統(tǒng)概念中的service層僅僅處理應(yīng)用相關(guān)的業(yè)務(wù)邏輯,即作為Application Service。 然后domain service中處理domain 內(nèi)的業(yè)務(wù)邏輯。業(yè)務(wù)邏輯將按照domain和application的方式分開(kāi),容易定位和維護(hù)。傳統(tǒng)意義上的applicationservice層將變得整潔。
2、在domain service中我們將按照entity來(lái)編寫(xiě)對(duì)應(yīng)的service,這些都是特定的service,很小,僅僅面對(duì)很專一的功能。舉例來(lái)說(shuō),如果應(yīng)用中的某個(gè)service提供person類的crud, 同時(shí)還提供用戶帳號(hào)的操作,那么我們應(yīng)該將person的crud單獨(dú)放到一個(gè)service中,然后將用戶帳號(hào)相關(guān)的操作放到另一個(gè)service中。
所有這些分層方式都是為了解決應(yīng)用從小項(xiàng)目成長(zhǎng)為大項(xiàng)目時(shí)可能遇到的隱患,代價(jià)是在項(xiàng)目還小時(shí),增加了項(xiàng)目的復(fù)雜度,往往一句代碼就能搞定的事情,卻要拆到三個(gè)類中去。但是太多的實(shí)際例子表明,如果沒(méi)有好的架構(gòu),當(dāng)小項(xiàng)目膨脹到一定程度時(shí),往往是無(wú)法維護(hù)的,只能全部推倒重寫(xiě)。
在Domain Driven Design中如何區(qū)分各種Service?
在DDD中,service有三種類型
Domain Service
Domain Service: 用于放置領(lǐng)域?qū)ο笙嚓P(guān)的業(yè)務(wù)邏輯,這些業(yè)務(wù)邏輯通常并不適合放到entity中,也不是常見(jiàn)到的CRUD(這些應(yīng)該放到Repository), 將Domain Service 和Domain Objects放到一起是合理的,它們都是關(guān)注于domain相關(guān)的業(yè)務(wù)邏輯。在Domain Service中可以使用注入repository的方式來(lái)使用entity對(duì)應(yīng)的repository。
舉一個(gè)例子:
一個(gè)圖書(shū)館有三個(gè)entity:Book, Client,Inventory, 當(dāng)把一本書(shū)借給一個(gè)客戶時(shí),就對(duì)應(yīng)了一個(gè)Domain Service。在一個(gè)例子,在Eric Evans的《Domain Driven Design》書(shū)中,轉(zhuǎn)賬服務(wù)(FundsTransferService)也是一種domain service,它涉及到帳號(hào)BankAccount,但是并不適合放到BankAccount中。
Application Service
Application Service: 用于為應(yīng)用外的client或consumer提供應(yīng)用級(jí)別的服務(wù),比如一個(gè)外部客戶端(程序)需要使用某個(gè)entity的CRUD時(shí),這些服務(wù)程序放到Application Service。
Application Service通常會(huì)使用Domain Service和repository來(lái)處理外部的請(qǐng)求。常見(jiàn)的場(chǎng)景是,從repository中拿到一些domain objects, 然后執(zhí)行某些操作,在將其放回repository(或者不放), Application Service對(duì)應(yīng)著大部分用戶使用場(chǎng)景,在寫(xiě)一個(gè)應(yīng)用時(shí),可以先從Application service寫(xiě)起,這樣可以很好界定應(yīng)用的功能和范圍。repository雖然可以在某些場(chǎng)景下注入到domain service中,但是更常見(jiàn)的是注入到applicatinoservice中。
Infrastructure service
還有一種Infrastructure service:用于抽象一些技術(shù)問(wèn)題,比如消息隊(duì)列,郵件服務(wù)
具體例子spring-petclinic