FrankSoo是我的項目經(jīng)理。前段時(shí)間公司決定作個(gè)新的J2EE二次開(kāi)發(fā)平臺,以替換公司原有的開(kāi)發(fā)平臺。公司讓FrankSoo和我組成平臺開(kāi)發(fā)項目組,FrankSoo擔任項目經(jīng)理?,F在這個(gè)平臺整合開(kāi)發(fā)階段已經(jīng)結束,進(jìn)入項目應用階段。下面是我們的整合工作小結,介紹一下我們在工作中遇到的問(wèn)題,以及我們選擇的解決方案. 1、架構的選擇 首先,我們都同意以我們現有的能力,沒(méi)有足夠的時(shí)間和資源自行開(kāi)發(fā)一套完整的平臺。在已有的眾多開(kāi)源項目中選擇若干優(yōu)秀的項目進(jìn)行整合,才可能按時(shí)完成項目,達到項目目的。 但是在平臺項目開(kāi)始前,我們對平臺的技術(shù)架構有各自的構想。FrankSoo原來(lái)的構想是Struts+Spring+Hibernate,而我的構想是Tapestry+Hibernate。 不過(guò)FrankSoo非常open,在我向他演示了Tapestry的經(jīng)典范例workbench,介紹了Tapestry基于組件的編程方式之后,他同意選用Tapestry作為實(shí)現Web展現層的框架。我想FrankSoo以前的Struts開(kāi)發(fā)經(jīng)驗(painful)也是他做出這個(gè)決定的因素之一。 FrankSoo gave me a nice introduction of Spring Framework. Wow, what an amazing framework! IOC, Declarative Transaction Support, Hibernate Session Management, Hibernate DAO Support… These features are just what we need for a middle tire container. 至于Hibernate,這個(gè)最成功的開(kāi)源ORM項目,我們都投了它一票^_^ 最后我們確定平臺的技術(shù)架構是Tapestry+Spring+Hibernate. 2、架構整合 最初的平臺架構借鑒了一篇介紹如何集成Tapestry與Spring的文章[1]中提到的架構: Web層的Tapestry負責數據輸入輸出, 響應用戶(hù)事件,及輸入校驗的工作, 通過(guò)訪(fǎng)問(wèn)預先加載的WebApplicationContext(由Spring提供, 包含著(zhù)所有Service bean)獲得Service層的Service Bean, 把業(yè)務(wù)操作都委托給它們. Service層的bean則負責use case邏輯, domain相關(guān)的邏輯委托給domain model中的bean去實(shí)現. Service通過(guò)DAO完成對domain model的持久化工作. Service負責數據庫事務(wù)和Hibernate Session的管理(通過(guò)Spring的聲明式事務(wù)管理和與之集成的Hibernate Session管理). Service層的另一項重要工作是權限和訪(fǎng)問(wèn)控制。 Domain model負責表示問(wèn)題域的數據和domain logic. DAO使用Hibernate持久化數據以及查詢(xún). 在實(shí)現DAO時(shí), 我們使用了Spring的Hibernate DAO Support,極大地簡(jiǎn)化了代碼, 很多方法都只用簡(jiǎn)單的一行完成. 有意思的是, 最后完成的HibernateDAO的代碼量居然比我寫(xiě)的MockDAO的代碼少了一半還多 這樣的架構優(yōu)點(diǎn)很明顯, 層次清晰, 各層的職責也明確, 便于分層設計與開(kāi)發(fā), 結合mock和spring的IOC, unit test也是非常容易的. 而且后臺(Service, domain model and DAO)的代碼不依賴(lài)于Web容器或是EJB容器的API, 移植性非常好, 同樣的代碼可以在Web app中使用也可在普通的Java app中使用, 只需更換UI層. 按照這個(gè)整合的構架,我們實(shí)現一個(gè)簡(jiǎn)單的實(shí)例,實(shí)現了列表分頁(yè)查詢(xún)和顯示,數據增刪改,基于Hibernate Criteria Query提供了一個(gè)比較通用的查詢(xún)機制。利用Middlegen和Velocity我們可以從已經(jīng)建好的數據庫表結構自動(dòng)生成Hibernate映射文件,實(shí)體類(lèi)和DAO,極大地減少了工作量。我們還對這個(gè)小例子進(jìn)行了壓力測試(測試時(shí)的數據量為10萬(wàn)條記錄),確定平臺不存在性能問(wèn)題。 通過(guò)這個(gè)實(shí)例我們把整個(gè)架構基本走通一遍,并總結了使用這套架構開(kāi)發(fā)時(shí)適用的開(kāi)發(fā)流程和需要做的工作。 3、困擾我們的問(wèn)題 在實(shí)現例子和現在的項目應用過(guò)程中,我們發(fā)現了若干頭疼的問(wèn)題,有的解決了,有的還沒(méi)有。 問(wèn)題1:要不要使用DTO? 在上面的架構中我們并沒(méi)有明確Service和Web層間的數據傳輸是如何進(jìn)行的。我們討論好久要不要使用DTO,最后的結論是不用。 使用DTO有兩個(gè)主要的理由:1、減少Web層和Service層間的方法調用,通過(guò)一個(gè)方法調用就將Web需要的數據都傳給Web。2、隔離domain model和Web層。 第一個(gè)理由在我們的架構下是不成立的。因為我們的架構是集中式的,Web和Service是在同一個(gè)JVM中,它們之間的方法調用是沒(méi)有EJB遠程訪(fǎng)問(wèn)的巨大消耗的。 第二個(gè)理由還是需要考慮的。如果允許把domain model中的對象傳給Web層,那么修改domain model,就會(huì )影響到Web層。如果使用DTO,那么domain model實(shí)現上的變化就不會(huì )影響到Web。但是大量的變化不是domain model實(shí)現上的變化,而是domain model接口的變化,比如一個(gè)domain model的對象上添加了一個(gè)屬性,而這個(gè)屬性需要用戶(hù)修改,那么這時(shí)候必須修改Web層,不管是不是用了DTO。而且使用DTO,就需要維護著(zhù)一大堆對象,或是它們的生成器,這是非常無(wú)聊、且容易出錯的工作。 基于這些考慮,我們沒(méi)有使用DTO,而是選擇把domain model直接傳到Web層。下面是修改后的架構圖(呵呵,修改了別人的圖[2])。 問(wèn)題2:Entity like domain model or rich domain model? 我們使用Middlegen自動(dòng)生成Hibernate映射文件,Entity類(lèi)和DAO類(lèi), 但是生成的Entity只含有簡(jiǎn)單的屬性和getter, setter方法。因此我們遇到了一個(gè)問(wèn)題:我們的domain model還要不要包含domain logic?如果包含,那么和自動(dòng)生成工具如何結合? 我們討論后認為一個(gè)rich domain model還是非常有必要的,可以減少Service中的重復代碼,提高復用性。 如何同自動(dòng)生成結合?使用<meta>標簽,生成抽象基類(lèi),我們繼承這些自動(dòng)生成的基類(lèi),添加業(yè)務(wù)方法。 問(wèn)題3:Model driven or Data driven? 采用Model driven還是Data driven的方式大家有過(guò)熱烈的討論。我們主要是受到Rod Johnson[3]的影響,采用了Data driven的方式。先作數據庫設計,生成庫表,然后用Middlegen反向生成Hibernate映射文件和Entity及DAO。但是我們在進(jìn)入項目應用之后發(fā)現這種方法有兩個(gè)問(wèn)題: a) 數據庫設計僅說(shuō)明了系統要管理的靜態(tài)數據,我們還是得作面向對象分析,以反映系統的動(dòng)態(tài)行為。特別是當系統的業(yè)務(wù)不僅僅是簡(jiǎn)單的CRUD操作時(shí),這個(gè)問(wèn)題更嚴重。 b) 數據庫設計為了優(yōu)化性能,可能會(huì )把好幾個(gè)應該是單獨實(shí)體的數據放入一個(gè)實(shí)體中。這樣如果直接把這種極粗粒度實(shí)體映射成Entity類(lèi),那簡(jiǎn)直是不可接受的。面向對象的分析設計模型得到的類(lèi)都是相當細粒度的。這種情況還得作面向對象的分析,明確到底這個(gè)粗粒度的大表應該映射成那幾個(gè)細粒度的對象。 或許我們應該試試Model driven,用AndroMDA生成domain model,Hibernate DAO,Hibernate mapping,數據庫表,簡(jiǎn)單的Service和前臺的Tapestry頁(yè)面。 問(wèn)題4:Hibernate Session生命周期如何管理? 對于Hibernate Session的生命周期我們采用的是Session-per-Transaction模式,未采用Open Session in View模式。 雖然Hibernate team認為這種方法沒(méi)什么不好,而且FreeRoller和Atlassian的confluence都使用了Open Session In View這種模式,但是我們對它可能產(chǎn)生的影響還沒(méi)有很好的把握,所以暫時(shí)棄置不用。 如果Web層要訪(fǎng)問(wèn)lazy load的數據, 需要先調用Service的業(yè)務(wù)方法, 以獲得數據. 問(wèn)題5:Use case logic 和domain logic 如何區分? Service負責use case logic,domain model負責domain logic。這樣的劃分看起來(lái)很好,實(shí)現起來(lái)就很麻煩。如何確定什么是use case logic,什么是domain logic?TBD. 問(wèn)題6:Service粒度如何確定? 這個(gè)問(wèn)題真是很煩,原先考慮使用usecase controller的方式,每個(gè)usecase對應一個(gè)Service,但是發(fā)現這樣復用性太低,而且好多地方必須復用相同的功能 另一種方法是用package level service,每個(gè)package作個(gè)service,這樣倒是可以重用,但是感覺(jué)太死了,不好。 現在也沒(méi)有什么很好的辦法,只好在詳細設計時(shí)根據具體情況確定需要多少個(gè)Service了。TBD. 問(wèn)題7:權限如何設定?如何檢查? 權限設定也是個(gè)頭疼的問(wèn)題。我們本想是按照use case設定權限,每個(gè)用例一個(gè)權限。在角色設定的時(shí)候直接處理的都是業(yè)務(wù)意義非常明確的權限。但是在權限驗證過(guò)程中發(fā)現了問(wèn)題:如果在Service的方法中驗證權限,而且這個(gè)方法在多個(gè)用例中用到(復用Service),那么這個(gè)Service的方法就需要檢查多個(gè)權限; 如果每個(gè)Service方法對應一個(gè)權限, 那么權限又太細了, 不像use case權限那樣代表一個(gè)完整的業(yè)務(wù). 真的是很麻煩阿!TBD. Quake wang: 問(wèn)題1:要不要使用DTO? No DTO, 直接把domain object傳給Tapestry的web層,利用Tapestry提供強大的數據綁定和組件功能很方便 問(wèn)題2:Entity like domain model or rich domain model? domain model不包括復雜的domain logic, 只是作為一個(gè)data model bean, 再加上一些簡(jiǎn)單的logic, 比如addChild的同時(shí),設置child的parent此類(lèi)簡(jiǎn)單logic. 問(wèn)題3:Model driven or Data driven? 這個(gè)對我來(lái)說(shuō)不是什么大問(wèn)題,個(gè)人習慣而已,我覺(jué)得無(wú)論是Model driven或者Data driven,對于相同的需求,2者最終的設計應該都是很類(lèi)似的。我是從2邊同時(shí)考慮的,一邊做Model,一邊還要考慮數據庫的結構, 再進(jìn)行相應的調整,用下來(lái)也沒(méi)有什么問(wèn)題。沒(méi)有用代碼生成, 只是用Hibernate的Eclipse plugin (Hibernate Synchronizer)來(lái)幫我做一些code assist. 問(wèn)題4:Hibernate Session生命周期如何管理? 目前沒(méi)有采用open session in view,也是和你們一樣,在Service里面準備好所有需要的對象。 問(wèn)題5:Use case logic 和domain logic 如何區分? 由于domain model里面沒(méi)有什么domain logic,這個(gè)問(wèn)題不存在. 問(wèn)題6:Service粒度如何確定? 由于domain model里面沒(méi)有什么domain logic,所以Service的功能就切得很細,盡量重用,一個(gè)package可能有多個(gè)service, 感覺(jué)還好,沒(méi)有太死。 問(wèn)題7:權限如何設定?如何檢查? 這個(gè)也是我沒(méi)有想好的問(wèn)題, 因為不同的需求, 權限設定都不一樣,很難用AOP來(lái)做一個(gè)通用的aspect. 1. DAO的做法: 我目前不是給每個(gè)domain object都做一個(gè)DAO, 而是整個(gè)系統就一個(gè)DAO (PersistenceManager), 里面只有create, update, delete entity這3個(gè)方法。另外寫(xiě)一個(gè)DAO, 專(zhuān)門(mén)做查詢(xún) (QueryManager), 用hibernate的named query做常用的查詢(xún), 用criteria來(lái)做基于用戶(hù)輸入的動(dòng)態(tài)查詢(xún)。不知道你們的做法是怎么樣?另外criteria 和 named query其實(shí)都是hardcode,object attribute name改變的時(shí)候,還得記得手工去修改這些代碼,unit test很難覆蓋到所有的criteria search 和 named query里面的代碼,這個(gè)是目前感覺(jué)不是很方便的地方。 2. Spring 和 Tapestry的集成 目前只是用Spring到service layer而已, Tapestry通過(guò)Global(扮演Service Locator的角色)這個(gè)對象來(lái)調用,在domain Tapestry這一層還是有很多dirty code, 其實(shí)也可以用AOP來(lái)解決。如果能夠像webwork2那樣,所有的action都可以通過(guò)從Spring獲得,即通過(guò)Spring獲得page listeners, 那會(huì )方便很多,不過(guò)Tapestry也有自己的類(lèi)增強功能,好像有一定的沖突,目前沒(méi)有什么好的想法。 順便推薦3個(gè)我在項目中使用的工具 (都是eclipse plugin): 1. http://spindle.sourceforge.net 開(kāi)發(fā)Tapestry的必備 2. http://springui.sourceforge.net 寫(xiě)Spring application context file的輔助好工具 3. http://www.binamics.com/hibernatesynch/ Hibernate 開(kāi)發(fā)的輔助好工具。 |
聯(lián)系客服