案例研究:巴西國家醫(yī)療保健系統(tǒng)作者 Fabiane Nardon, Floyd Marinescu譯者 沙曉蘭 發(fā)布于 2007年12月2日 上午9時0分 巴西國家醫(yī)療保健系統(tǒng)曾被喻為全球最大的Java企業(yè)應(yīng)用,涉及200多萬行代碼,囊括一個350個類的區(qū)域模塊。該系統(tǒng)把所有能想象到全國范圍內(nèi)的各類行政地區(qū)模塊化,其所實現(xiàn)的自動化給公共醫(yī)療保健系統(tǒng)創(chuàng)造了巨大的價值,讓巴西人受益匪淺。本案例分析,從系統(tǒng)構(gòu)架、解決方案、教訓(xùn)與啟發(fā)以及項目的未來趨向等做全方面詳細(xì)深入的探索。 問題域巴西是世界上僅有的幾個提供完全免費公共醫(yī)療保健系統(tǒng)的國家之一。和所有重大的公共服務(wù)設(shè)施一樣,它存在很多操作性問題,原先很多工作都基于大量的紙質(zhì)文件之上,政府部門和地區(qū)醫(yī)療保健部門的IT系統(tǒng)間只有很小一部分實行了集成。例如:
考慮到這么多問題的存在,政府批準(zhǔn)創(chuàng)建一個自動化醫(yī)療保健系統(tǒng),命名為Siga Saude。系統(tǒng)設(shè)計目標(biāo)是覆蓋所能想到的公共醫(yī)療保健信息系統(tǒng)的所有方面,包括會診調(diào)度,醫(yī)生和醫(yī)療器械的目錄清單管理,帳單,病理跟蹤,審核報告,規(guī)章制度的遵循,以及安全訪問控制等。 系統(tǒng)首先在圣保羅市(巴西的最大城市,有近2000萬人口,也是世界第4大城市)的所有醫(yī)療保健單位投入使用。如今,該應(yīng)用程序除了在圣保羅和其他20個城市中投入使用以外,也正逐漸覆蓋到巴西其他一些城市。除此之外,還有一些想要實現(xiàn)全國醫(yī)療保健系統(tǒng)自動化的國家對此系統(tǒng)也產(chǎn)生濃厚的興趣,例如葡萄牙語國家安哥拉和莫桑比克等。 解決方案概述該應(yīng)用開發(fā)基于EJB2.1和Struts,采用成熟的EJB設(shè)計模式,引入數(shù)據(jù)傳送對象(data transfer objects)、會話外觀(session facade),服務(wù)定位( service locator),業(yè)務(wù)委托( business delegates),完美定義了一個層次分明的構(gòu)架。整個開發(fā)在Eclipse環(huán)境下完成,測試和最后部署使用JBoss應(yīng)用服務(wù)器。在一些特定部分,也采用了Drools規(guī)測引擎(rules engine)。目前,應(yīng)用程序非集群地(non-clustered)部署運行于Dual Xeon 3.1服務(wù)器之上,該服務(wù)器具有4GRAM,Linux操作系統(tǒng),并運行JBoss 3.2.7。 從需求到開發(fā),150多人參與了系統(tǒng)的功能定義工作,這些人中包括醫(yī)生和健康資訊專家。根據(jù)功能定義,研發(fā)團(tuán)隊最終把系統(tǒng)分解成以下這些模塊:
上述模塊圖中所標(biāo)出的每個模塊代碼行的數(shù)量均包括了服務(wù)層和與該模塊相對應(yīng)的域模型的代碼。另外,在服務(wù)層,有10萬行代碼是模塊間共享,有57萬行是各域模型間共享。代碼行的數(shù)量聽上去有點嚇人,但實際上58%是自動生成代碼,這在下文主題中關(guān)于注釋(annotation)的部分會討論到。 上述所有業(yè)務(wù)模型都包含JSP頁面,Struts Actions 和在服務(wù)層實現(xiàn)的業(yè)務(wù)委托。服務(wù)層包括EJB2.1的會話beans(session beans)和自動生成的會話外觀(session facade)。域模塊包括pojo's和提供自動生成實例Bean的注釋(annotation)。接下來會從URL到SQL一步一步詳細(xì)分析最后實現(xiàn)的調(diào)度系統(tǒng)。這里提到的系統(tǒng)模塊劃分、分層和其他的功能實現(xiàn)相一致,該模塊劃分圖實際上也幫助我們清晰地去了解該系統(tǒng)是如何構(gòu)建的。 經(jīng)驗一:專家門診預(yù)約調(diào)度用例無論從經(jīng)濟(jì)還是技術(shù)觀點出發(fā),該系統(tǒng)最重要的用例之一是描述一個地方小診所的前臺如何幫助病人預(yù)約專家門診。用例在調(diào)度模塊中實現(xiàn),同時牽涉到許多其他模塊。這個預(yù)約調(diào)度用例是這樣的:
首先,前臺在時間安排表中找到空的時間隙。這可以通過許多途徑查找,比如查找一個指定的醫(yī)生、??啤⑻囟ǖ尼t(yī)療設(shè)備、醫(yī)療過程等等(點擊這里查看輸入界面), “getSlots”的序列圖參考下圖。當(dāng)他們找到某個空的時間隙(在序列圖上標(biāo)識為chooseSlot())以后,可以通過掃描病人醫(yī)療卡上的條形碼或者在病人忘了攜帶醫(yī)療卡的情況下通過輸入病人的姓名、出生日期或病人任何其他聯(lián)系方式查找到該病人的記錄,使用國家醫(yī)療保健卡模塊將病人簡歷關(guān)聯(lián)到該時間隙(查看searchPatient())。然后,在同一界面上輸入所需的療程類型,(參考第二個用戶輸入界面,序列圖上標(biāo)識為saveAppointment())。 如果診所沒有病人所需要的療程,可能的原因之一是診所沒有空的時間隙安排給病人預(yù)約就診,或者就是診所不提供這樣的療程,在這種情況下,前臺或者醫(yī)生可以幫助病人通過系統(tǒng)在其他診所預(yù)約合適的診療。
此序列圖描繪了一個典型用例在系統(tǒng)中的執(zhí)行過程。首先,一個Struts Action類(ScheduleAction)執(zhí)行用戶請求并處理表示層邏輯。當(dāng)需要讀取數(shù)據(jù)或者需要引用業(yè)務(wù)規(guī)則的時候,Action類調(diào)用實現(xiàn)了業(yè)務(wù)委托設(shè)計模式(ScheduleCF)的POJO對象的方法。這個類從會話Facade類(ScheduleCF)自動生成,并將Web層從服務(wù)層使用的技術(shù)中獨立出來。那些實現(xiàn)了會話Facade設(shè)計模式(ScheduleCF)的會話Beans負(fù)責(zé)對其他類執(zhí)行業(yè)務(wù)規(guī)則。例如,類AppointmentService實現(xiàn)方法lockSlot()和searchForSlots(),當(dāng)用例需要保持?jǐn)?shù)據(jù)的時候,方法setAppointmentVO()在一個實例Bean上被調(diào)用。實例Bean機制不執(zhí)行數(shù)據(jù)庫查詢,但可以使用一個稱為Searcher的查詢優(yōu)化控件來彌補這個空缺。 會話Facade對象負(fù)責(zé)提供事務(wù)處理服務(wù)(transaction service)、數(shù)據(jù)庫連接和對應(yīng)用程序其他部分的安全服務(wù)。因為服務(wù)對象是POJO,它無法從Web層直接訪問實例Beans,這時就需要加入一個會話bean層來幫忙了。 如果某個時間隙被鎖定,在會診預(yù)約安排數(shù)據(jù)庫中會存入相應(yīng)的時間戳和鎖定號(lock ID)。為了防止競爭條件(race condition),這兩項數(shù)據(jù)通過一個SELECT FOR UPDATE命令保存。時間戳用來決定該預(yù)約是否過期,而儲存鎖定號的目的是為了避免對象試圖儲存過期預(yù)約的情況出現(xiàn)。當(dāng)對象請求儲存鎖定預(yù)約的時候,它會將收到的鎖定號和數(shù)據(jù)庫當(dāng)前鎖定號做比較,如果這兩個號不相同,那么這個新的診療預(yù)約信息不會被儲存。這樣一來,即使某個鎖定發(fā)生在發(fā)送鎖定請求和診療預(yù)約儲存之間,都能被應(yīng)用系統(tǒng)偵測到且向用戶發(fā)送警告。該用例實現(xiàn)了一個邏輯鎖定。無論是數(shù)據(jù)庫還是EJB鎖定都不需要很長的時間,利于實現(xiàn)業(yè)務(wù)規(guī)則。 該用例中涉及到的域?qū)ο蟮暮喕悎D如下:
經(jīng)驗二:應(yīng)用注釋(annotation)生成代碼項目初期,開發(fā)團(tuán)隊采用XDoclet1來生成部署描述器(deployment descriptors),值對象(value objects),查詢,會話facades,Struts表和動作,值驗證,以及本地和遠(yuǎn)程EJB接口等。由于項目的短時間開發(fā)周期(short time frame)計劃,需要應(yīng)用一些額外技術(shù)幫助開發(fā)團(tuán)隊在短時間內(nèi)和保證減少bug的前提下盡可能多產(chǎn),代碼生成也就成為這個項目中的一個關(guān)鍵策略。也因為它的關(guān)鍵性,最初的XDoclets模板后來被修改生成“專家代碼”。 起初的10個月里,XDoclet方式在項目進(jìn)行中很適用,幫助開發(fā)團(tuán)隊達(dá)到了所需的開發(fā)能力,但沒過多久,隨著代碼的不斷增多,生成代碼的速度卻越來越慢,以至于生產(chǎn)率慘遭嚴(yán)重影響。于是,開發(fā)團(tuán)隊決定采用另一種代碼生成策略,這就是Java 5 注釋 (annotations)。 注釋策略可以解決XDoclet的以下幾項缺點:
團(tuán)隊為EJB 3.0 注釋和其他一些自定義注釋開發(fā)了處理器,使其最后生成的代碼和采用XDoclet時所生成代碼的相似。這些生成的代碼在EJB 2.1下也同樣兼容。 注釋處理器使用Velocity模板。從XDoclet轉(zhuǎn)移到自己編寫使用的模板中,最大的挑戰(zhàn)是構(gòu)建和XDoclet提供的同樣容易理解的一組模板和幫助類。 采用注釋以后,代碼生成時間縮短很多。使用XDoclet,如果其中一個類有所修改,需要1分50秒的時間從400個類中重新生成代碼,但是使用APT以后,同樣的操作僅需10秒鐘。 經(jīng)驗三:規(guī)則引擎如何簡化業(yè)務(wù)邏輯根據(jù)政府立法,一些業(yè)務(wù)邏輯一直在修改中,有些按城市各異,系統(tǒng)某些部分不得不處理這些特殊的業(yè)務(wù)邏輯。比方說,某診所想要提供x光透視服務(wù),這等價于幾項服務(wù)規(guī)則,擁有x光透視設(shè)備,有授權(quán)資格的放射科醫(yī)生等。由于這些規(guī)則經(jīng)常在變,所以最好把它們留在主代碼庫外頭,這樣即使規(guī)則被修改也不用去修改代碼。 在SIGA-Saude系統(tǒng)中,因為引入了Drools 規(guī)則引擎而使這個難題迎刃而解。Drools實現(xiàn)了JSR-94-Java 規(guī)則引擎應(yīng)用程序接口。規(guī)則由SIGA-Saude系統(tǒng)中一個稱作Decision的控件來處理。該控件工作于一組規(guī)則組和一個工作記憶區(qū)(working memory)。規(guī)則組工作于一組消息,每條規(guī)則都相應(yīng)有一條由實現(xiàn)了br.com.vidatis.common.decision.message.RuleMessage接口的類描述的消息。當(dāng)一條規(guī)則或是規(guī)則組的子集被滿足的時候,對應(yīng)的消息會從隊列中移除,這樣做可以追蹤規(guī)則的處理。如果最后在隊列中沒有任何消息剩下,表明規(guī)則組中的所有的規(guī)則都被滿足。任何一條規(guī)則都可以觸發(fā)許多不同的動作,也可以觸發(fā)新的規(guī)則組,規(guī)則在XML文件中描述,然后交由Drools引擎處理。 XML文件范例: <rule name="if_clinic_code_then_checksum_digit_valid"> 這個例子中,保存某診所數(shù)據(jù)的時候,服務(wù)類會調(diào)用Drools規(guī)則引擎檢查和認(rèn)證所有的規(guī)則,數(shù)據(jù)只有符合所有的規(guī)則才能被儲存。這些認(rèn)證規(guī)則隨著政府制定的規(guī)章制度的修改而變化。由于修改經(jīng)常發(fā)生,所以把它們和應(yīng)用程序代碼劃分開來非常重要,這樣才能在維護(hù)和修改程序的時候不需要重啟整個應(yīng)用系統(tǒng)。 教訓(xùn)與啟發(fā)代碼生成在這個項目中是成功的關(guān)鍵,它不但提高了開發(fā)人員的生產(chǎn)率,同時也保證了代碼風(fēng)格一致。在50個開發(fā)人員在各個獨立團(tuán)隊中各自工作且共享基礎(chǔ)組件的情況下,使他們保持代碼一致性非常困難。起初采用XDoclet,然而好景不長,由于生成文件時間越來越長,對開發(fā)速度帶來出乎意料的壞影響。之后,轉(zhuǎn)而采用注釋策略,才大大地減短了生成時間,真正體現(xiàn)了生成代碼的優(yōu)越性。 好的團(tuán)隊交流是必須的。特別是像這樣的大項目,總會有些時候有些人發(fā)表不同的意見。讓所有的人互相理解對方的觀點實在很難。特別是在這樣一群人中,交流真的是讓人頭疼的難題:
如果你的構(gòu)架定義確切,有規(guī)則、有代碼生成、套用設(shè)計模式、構(gòu)件的話,就比較容易進(jìn)行小范圍或中等程度的軟件重構(gòu),大范圍重構(gòu)往往很麻煩。然而,現(xiàn)實系統(tǒng)的部分構(gòu)架不夠確切,嚴(yán)格的規(guī)則更使得20%的應(yīng)用程序那以實現(xiàn)。明確你究竟能夠在加強構(gòu)架標(biāo)準(zhǔn)這點上走多遠(yuǎn)至關(guān)重要。項目開發(fā)過程中,有時候就算一些解決方案顯然不是最好的,開發(fā)人員也只是強行將它們向構(gòu)架靠攏,而非重新探討最佳方案。另一方面,系統(tǒng)中某些部分,規(guī)則要求并不嚴(yán)格,比如說用戶界面,對于這些部分,開發(fā)人員則各取其愛,以至于造成系統(tǒng)這些部分質(zhì)量較差、代碼不易管理等預(yù)料未及的壞結(jié)果。 在龐大的應(yīng)用程序中,面對繁雜的依賴性和代碼,開發(fā)周期往往不得不延長。有時候,在某個界面上加上一個簡單的域(field)就意味著系統(tǒng)的許多部分都隨之需要修改,這很費時間。無論是開發(fā)人員還是客戶都因此感到頭痛,工具變得太過沉重以至于所有一切都比原本更花時間。把應(yīng)用程序劃分成許多構(gòu)件,還是不足以徹底解決問題。盡管J2EE有很多優(yōu)點,但同時代碼也更加復(fù)雜,生產(chǎn)率還是受到影響,這些很難跟管理人員和客戶解釋清楚。 項目啟動最初,很多人都說按照那樣的短時開發(fā)周期,項目根本沒有辦法完成。在其他一些國家的公共醫(yī)療保健系統(tǒng)開發(fā)過程中,也都發(fā)生過太多太多令人汗顏的經(jīng)驗。所以,從這個項目中得到的最后一個啟示是:當(dāng)別人說某件事不可能的時候,千萬不要讓他們這樣的泛泛之說影響了你去將這件事變得可能。 未來方向容器外測試無法進(jìn)行容器外測試讓開發(fā)團(tuán)隊很困擾,無法在初期就進(jìn)行容器外測試的原因是他們被迫從合法的代碼開始工作。政府制定的規(guī)則和規(guī)章條例模塊是一個巨大的EJB 1.0系統(tǒng)。結(jié)合代碼生成技術(shù)來處理所有的實例bean和其他容器假象(container artifact),程序的新生部分變的越來越基于POJO。但將他們部署到容器中以后,甚至只是一個漸進(jìn)變化也緩慢到開發(fā)人員無法接受的地步。 遷移到基于POJO的構(gòu)架目前的系統(tǒng)明顯有大量的容器依賴和EJB樣板代碼(plumbing code)(盡管大部分都隱藏在生成的業(yè)務(wù)委托和會話外觀中),但開發(fā)團(tuán)隊計劃的遷移方向是完完全全基于POJO,包括業(yè)務(wù)規(guī)則,他們計劃將所有的會話Bean重構(gòu)成一個單一的會話bean interceptor,由這個單一的會話bean interceptor專門向服務(wù)層調(diào)用。構(gòu)建這個單一的會話bean interceptor的目的在于提供像事務(wù)(transaction)和線程管理這樣的中間件服務(wù)。他們原先設(shè)想在修改之后的構(gòu)架上采用Spring,但這注定開銷太過昂貴。鑒于當(dāng)時擁有的會話bean都已經(jīng)全部生成,相比較從EJB中完全引身而退來說,修改代碼生成邏輯來貫徹會話bean interceptor明顯簡單得多。將這樣的重構(gòu)變得可能的另一個元素是現(xiàn)存的業(yè)務(wù)委托層可以將這些大幅修改從Web端屏蔽。 為什么他們沒有一并擺脫掉EJB呢?理由是最后他們必須將應(yīng)用程序展示給在醫(yī)療衛(wèi)生部門的另外的團(tuán)隊,這些團(tuán)隊需要訪問該系統(tǒng),并正準(zhǔn)備簡單地讓工作人員(從圣保羅市)接手開始投入使用可行的業(yè)務(wù)委托。由于業(yè)務(wù)委托隱藏了遠(yuǎn)程的各方面,他們知道合作方將不會工作于Java上,所以預(yù)期系統(tǒng)集成會非常順利。 不同的事務(wù)類型又將如何處理呢?不同的事務(wù)界限有不同的方法,目標(biāo)方法和事務(wù)類型間有一個映射,所以他們知道該執(zhí)行哪個方法來處理相應(yīng)的事務(wù)。順便提一句,這里的會話Bean interceptor和事務(wù)策略同F(xiàn)loyd Marinescu在EJB設(shè)計模式一書中描述的極為相近。 :) 最終,他們實在是想擺脫掉實例Bean,也許甚至想直接只保存值對象和Hibernate。面對這樣修改的潛在可能性最大花銷是測試,他們必須徹徹底底地測試系統(tǒng)的每個方面,單一的單元測試無法覆蓋全部。 AJAX簡化Web用戶界面AJAX被公認(rèn)是為非技術(shù)終端用戶簡化用戶界面工作流程的有效工具。就如在調(diào)度用例中,自動補全(auto-completion)可以幫助輸入醫(yī)生姓名,療程名稱,??泼Q,實在沒有理由去調(diào)用彈出窗口來做查詢,從長長的列單中去尋找這些名字。另外,在使用大而冗長的表單時,使用AJAX一步一步將表單域的值保存到HTTPSession中去無疑會更好,還能防止一些用戶因不小心錯關(guān)頁面而遺失所輸入的信息。 最近AJAX也被部署項目中,被用來瀏覽一個需要讀取數(shù)據(jù)的巨大的流程樹,幫助我們在縮短了數(shù)據(jù)錄入時間的同時也給終端用戶提供更漂亮的界面。點擊這里查看界面截圖。 查看英文原文:Casestudy: Brasilian National Healthcare System7 條回復(fù)little mistake 發(fā)表人 曉 汪 發(fā)表于 2007年12月2日 下午6時26分
Re: little mistake 發(fā)表人 霍 泰穩(wěn) 發(fā)表于 2007年12月2日 下午9時52分
Re: little mistake 發(fā)表人 曉 汪 發(fā)表于 2007年12月2日 下午10時15分
擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市 發(fā)表人 wander zhou 發(fā)表于 2007年12月3日 下午6時29分
Re: 擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市 發(fā)表人 sihai T 發(fā)表于 2007年12月3日 下午7時55分
Re: 擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市 發(fā)表人 霍 泰穩(wěn) 發(fā)表于 2007年12月3日 下午8時15分
Re: 擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市 發(fā)表人 涼粉 小刀 發(fā)表于 2007年12月4日 上午5時45分
|
|
返回頂部
little mistake
2007年12月2日 下午6時26分 發(fā)表人 曉 汪
在"經(jīng)驗三:規(guī)則引擎如何簡化業(yè)務(wù)邏輯"的內(nèi)容中,jsp-94是否應(yīng)為 jsr-94? 還有,可否發(fā)原文鏈接?謝謝
回復(fù)
返回頂部
Re: little mistake
2007年12月2日 下午9時52分 發(fā)表人 霍 泰穩(wěn)
經(jīng)核實,確實是“JSR-94”(一個Java規(guī)則引擎),已經(jīng)做了修正,并補全了原文鏈接。謝謝汪曉朋友的指正!
回復(fù)
返回頂部
Re: little mistake
2007年12月2日 下午10時15分 發(fā)表人 曉 汪
^_^多謝
回復(fù)
返回頂部
擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市
2007年12月3日 下午6時29分 發(fā)表人 wander zhou
2億人口的城市太可怕了。
回復(fù)
返回頂部
Re: 擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市
2007年12月3日 下午7時55分 發(fā)表人 sihai T
“擁有2億多人口的巴西最大的城市”應(yīng)該是“擁有2億多人口的巴西的最大城市”
回復(fù)
返回頂部
Re: 擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市
2007年12月3日 下午8時15分 發(fā)表人 霍 泰穩(wěn)
這句話確實有歧義,已經(jīng)根據(jù)Sihai T朋友的建議對原文做了修改。非常感謝wander zhou和sihai T朋友的指正!
回復(fù)
返回頂部
Re: 擁有2億(應(yīng)該為2000萬)多人口的巴西最大的城市,也是世界第4大城市
2007年12月4日 上午5時45分 發(fā)表人 涼粉 小刀
2000萬那里還要改一下的