python做大型网站_Python中的大型Web应用:一个好的架构
如果你著手使用關系型數據庫在Python中編寫大型應用程序,這篇長文正好滿足你的需求。這里我分享下在一個大型團隊中使用SQLAlchemy(Python語言中提供最先進ORM工具的軟件)編寫超過6個月時間大型應用的經驗。
誠然,我認為這篇文章可能太復雜,嘗試一次性教太多的東西。但真的很想展示出多個方面銜接是如何導致失敗的。
隱藏的危險
如果要解釋我所遇到的所有糟糕軟件的原因是不太現實的,但是可以肯定地說它們是由于某些因素的相互作用所導致的:
草率
次優技術選擇
SQLAlchemy需要開發人員進行數周研究才能明智地使用它的客觀事實
相比MVC缺乏更好的整體架構
開發人員對于MCV的理解不一致
開發人員還沒意識到自動化測試應該告知主代碼被分解的方式
需要實踐TDD(測試驅動開發),即先編寫單元測試
關系型數據庫對于測試組件速度上的有害影響
需要學習什么是真正的單元測試:一些開發者認為集成測試就是單元測試
我將討論所有這些方面,包括它們的關系以及一些解決方案。
草率是不好的…不要著急…MKay?
除非一個軟件的生命周期真的很短,否則匆忙地編寫永遠是得不償失的。編寫一個軟件需要時間、調研、學習、實驗、重構和測試。每一個讓步都會讓你的草率產生糟糕的影響。但是不要相信我;每個人都需要親自地去嘗試這種痛苦。
謹慎地選擇你的框架
本節中提到的建議僅針對目前(2014年4月)而言。
當編寫大型應用時,每個人都應該謹慎地選擇所使用的工具。不要只是去走捷徑。舉例來說,選擇一個更加復雜的web框架是值得的,比如功能齊全、設計精美以及豐富文檔的Pyramid,它比起其他像Flask等一些框架具有更好的定義范圍以及更完善的解耦。雖然Flask之類的框架可以在完成小型工作時更加迅速,但是卻要承受著無處不豐的線程局部變量,不完整的文檔(好像只是給已經了解它的人去閱讀),以及瘋長的插件(Flask愛好者希望系統中所有東西都成為一個Flask插件)。
可以肯定地說,在Python的領域里SQLAlchemy相比于其他的ORMs軟件是非常先進的。如果你通過其他的方式來訪問關系型數據庫,那么你將錯過它。這也是你不應該去選擇web2py框架的原因,而且,它并不包含ORM,僅僅是通過簡單的DAL來生成SQL。
既然我已經推薦了Pyramid和SQLAlchemy,為什么只字不提表單驗證工具?從古老的FormEncode至今,許多庫都為此而創建,比如說WTForms和ToscaWidgets。我只會說你可以親自嘗試下將Deform和Colander結合在一起——它們具有不同的功能,比如說將數據轉換成Python結構,以及部件(Deform)的模式分離(Colander),它真的能正確地解決問題。這種架構清晰性導致它比競爭對手而言更加有力。同樣的是,你需要花費稍微多些的時間去學習如何使用這些工具。使用其他工具的學習曲線可能會比較平緩,但是你將在今后的工作中深受其擾。
MVC不足以應對大型項目
在Web應用開發中,你可能已經知道了MVC架構(model,view,controller)。如果你不清楚,你可能并沒有準備好去開發大型項目;可以先使用一個小型的MVC框架,然后在數月或者幾年之后再來看這篇文章吧。
嚴格來說,MVC是一個古老的概念,從早期的Smalltalk開始已經不再適合于web應用開發。Django的開發人員已經正確意識到在Python中我們實際使用的是MTV(model,template,view):
模板包含了HTML的內容以及頁面顯示邏輯。它是使用模板語言如Kajiki來編寫,從視圖(view)中獲取數據,然后展示在頁面中。
視圖(有時也稱為“控制器”),僅僅是使用Python語言編寫的中間代碼。它借助于web框架將所有的內容放在一起。它可以看到其他的所有層,并且定義了URLs,將它們映射到web框架中用于接收數據的函數,然后利用其他層以最終發送響應給web框架。它應該盡可能得小,因為它的代碼是不能重復利用的,即使你盡量地縮減它,web表單也會促使其逐漸地變得復雜。
模型層本質上是一個持久層:它最重要的依賴就是SQLAlchemy。模型知道如何去保存數據,構成整個項目中最可重用的代碼。當然它并不清楚HTTP相關的內容和你所使用的框架。它代表了排除用戶界面細節的系統本質內容。
但是稍等下,哪里?在視圖還是模型中?你應該在哪里放置程序的靈魂:業務規則?模板層已經自動被排除掉,因為它并不是Python編寫的。所以剩下3個可能的答案:
視圖層,這是最糟糕的選擇。視圖層應該僅僅包含中間代碼,將代碼數量保持盡可能得小,并且同系統中的其他部分隔離開,所以系統應該能在web框架中、使用中以及單元測試中獨立訪問。另外,業務邏輯應該存在于更加可重用的地方。視圖層被視為展示邏輯的一部分,所以業務邏輯被排除在外。實際上,除了Web UI之外,在創建desktop UI時,開發者應該忽略視圖和HTTP相關的內容,需要業務邏輯盡可能地被重用,因此,我們應該排除掉視圖層。
模型層,這個是可能的選擇,因為模型層至少是可重用的。但是模型層主要關注于持久化,它應該更少地依賴于SQLAlchemy(它已經是一個非常復雜的東西)。
新的層,這才是正確的答案。下面將舉例來更好地理解這部分內容。
如果你要創建一個博客,那么MTV正好滿足你的需求。但是對于更加復雜的項目來說,其實還是至少缺了一層。你應該將業務邏輯旋轉到一個新的、可重用的層次中,大多數人稱之為“Service”層,但是我更喜歡稱之為”Action“層。
為什么你需要這層?
在大型應用中,單個用戶的操作引起多項活動是非常常見的。比如,用戶成功地注冊了你的服務,那么你的業務邏輯中可能會觸發非常多的后臺處理:
在關系數據庫多張表中新增數據,使用了模型層。
將發送郵件給用戶的任務放置到隊列中。
將發送短信給用戶的任務放置到隊列中。
將創建實際使用服務時必需的空間和其他準備性資源的任務放置到隊列中。
將更新用戶數據的任務放置到隊列中。
….
這是一個理解“業務邏輯“的好例子:給定一個用戶操作(比如注冊),系統就要做一些必需的操作。這種業務邏輯被單個函數捕獲會更好;這個函數應該在哪一層呢?
如果所有的這些都實現在模型層,你能想象到它將變得多復雜嗎?模型層在只面對持久化時已經很艱難了。現在想象一下模型層處理所有這些事務,它要使用多少外部服務?文件頭部應該包括多少imports?反過來看,有多少模塊愿意引入這個模型,可能在系統啟動前就因為創建的循環依賴而導致系統崩潰。
循環依賴其實就是你沒有正確認清系統架構的明顯的標識。
對于依賴于Celery的模型來說,了解如何去發送郵件、短信以及使用外部服務等是不應該的做法。持久化對于模型層來說已經是非常復雜的主題了。你應該在模型層之外去處理這些業務邏輯——在模型層和視圖層之間的一層。所以稱之為”Action“層。
另外,模型層在關系型數據庫中經常被映射到一張單獨的表。如果你在用戶表和訂閱表中插入一條記錄,哪一個模型應該包含上述邏輯呢?這幾乎是不能確定的。因為實際執行的操作遠超過了用戶表和訂閱表的范圍。因此,業務邏輯應該被定義在任何模型之外。
當開發人員執行維護時,有時她想按步驟地執行每一步,而有時她想一次性執行完所有操作。分別地實現這些操作并在單個Action層函數中調用是有幫助的。
你可能會懷疑我提出的方法難道不是反面模式域模型的一種嗎?沒有動作的模型恰恰與面向對象設計相反!我并沒有說“將所有的方法從模型中移除”。我的意思是指“將需要外部調用的方法移除”。模型中的方法僅僅用來使用它所需要的數據,并且屬于模型中的那些數據。一個面向世界的,調用外部服務的并且很少使用自身數據的方法不應該被放置在模型中。
另一個使得這種架構成功的原因就是測試。TDD教會了程序員去讓程序變得解耦,這樣做通常會使得軟件更加健壯。如果你想要創建一個Celery的應用,并且在你測試之前已經知道了其他的外部服務,那么你將經常陷入頭痛中。
還有將業務邏輯放置在視圖層之外的最后一個原因。在未來,當你最終決定從Flask過渡到Pyramid時,你很樂意將視圖層保持簡潔。如果所有的視圖都是在與web框架間交互,并且動作層會執行所有的函數,那么你的代碼就做到了非常好的隔離。Web框架通常都很貪婪,不要讓你的系統跟隨他們的腳步。
所以下面就是我所提議的在Python中構建大型應用的層次結構:
模型層是最底層的,最可重用并且可見的層。它僅專注于持久化,模型層是可以包含動作的,只不過這個動作僅僅屬于這個模型。模型可以被其他層所返回,以各種方式在請求的結尾返回給模板。
外部服務。對每個服務都創建一個比如說發送郵件。
動作層。這是系統中的核心層,它包含了業務邏輯以及工作流。它使用外部服務去實現特定的目標并且借助模型層來持久化數據。通過以上這些層,它支撐起了整個系統,包括配置,除了用戶界面。
模板層僅包括了頁面展示邏輯比如說從列表中循環輸出構成一個HTML表格。
視圖層,這是最高層的,最不可重用的層。它依賴于(與系統中其他層隔離)web框架。并且依賴于表單驗證庫。它可以看到模板層以及動作層,但是不能直接調用模型層——它必須通過動作層。但是當一個動作層返回了模型數據,那么它可以被傳遞給模板中(一個Celery任務可以類比于一個web視圖)。
這種體系結構有助于避免了在會話層中進行調試因為它清楚地定義了各自的職責。同時它也是明顯經得起檢測的,因為它做到了很好的解耦,因此可以減少測試的并且減少了模擬的次數。
好的架構總是解耦性非常好的。如果你曾經陷入到一個循環依賴中,你可以想一下是否真的定義好了每一層的職責。當你放棄了并且從一個函數中引入內容,那么你的架構已經失敗了。
這并不是說你的web應用必須與Celery應用隔離開。在他們之間可以重用代碼——特別是模型——但是對于Celery應用來說,不應該引入web框架!獲取配置也不例外,因為在Python中讀取配置是非常簡單的。
自動化測試也是個巨大的挑戰
Python是一個非常靈活,富有表現力,反射型語言。隱藏在動態機制后的不利之處在于“編譯期”內很少能發現錯誤。如今使用Java語言構建大型系統時已經離不開自動化測試了;Python中更是如此。
一旦你意識到它對你軟件健壯性的重要性時你就會開始編寫測試用例。你理解了它并開始編寫用例,你編寫的第一個用例具有極大的價值,它會給你增加對系統的不可思議的信心。這是非常有趣的。
然而,你很快就會感覺測試對你來說更像是一種負擔。你會擁有數百個的測試用例去運行,而且通常要運行很久。在這種情況下,每一個編寫的新測試用例都會讓你的生活更加糟糕。這個時候,一些人可能會覺得幻想破滅并得出測試是不值得去做的。這個結論往往言之過早。
你認為你已經知道了如何去編寫測試代碼,其實不然,你其實是在編寫一個綜合測試。雖然你稱之為“單元測試”,但是實際并非如此。每一個測試用例都貫穿了系統整個的運行過程。你把你的模擬覆蓋到了盡可能遠的位置,你認為這是好的(通過這種方式可以測試更多)。但是你將發現這其實并不好。
單元測試其實是完全相反的,真正的單元測試會像激光一樣,它僅僅執行某一層的某個函數,并且使用模擬來避免陷入到其他層中,它永遠不會到達其他的外部資源,它斷言只有一個條件而且運行速度像光一樣快。
雪上加霜的是當測試用例開始工作時,顯示的結果會讓你陷入混亂。不同于針對單個錯誤的測試用例可以準確地告知你哪里出現了問題,數十個測試用例執行失敗(所有的失敗可能都是因為一個原因,因為它們貫穿了整個系統),但是你需要花費很長的時間去找出bug的真正所在。因而你需要編寫出更好的測試用例。
專家推薦你編寫99%的真實、專注、模擬性的單元測試,而僅1%的貫穿所有層次的綜合測試。如果你從開始就這樣做了,那么你的測試集會在數秒鐘內就運行完畢,這樣才能使得TDD是可行的。如果某個單元測試運行時間較長(超過了10毫秒),那么它可能是其他類型的測試而非單元測試。
如果你要編寫的僅僅是個小的應用,那么使用綜合測試的方法或許也可行。但是我們要討論的是大型應用,因此,在這個層面上來說,要么你去優化測試用例的性能,要么你根本應用不了TDD。
另外,你要記得有一些測試用例是比較難編寫的,它們需要大量的工作才能完成。有的人解釋說這是因為你的測試用例并非真正的單元測試,而且你并沒有以測試為先——你在給現有的未充分解耦的代碼編寫測試用例時會很難。現在你知道TDD其實并非只改變了測試相關的東西,還有將你實際的系統變得更好。
關于測試用例的編寫可以參考以下資料:
快速測試,慢速測試
綜合測試是一個騙局
停止模擬,開始測試(實際上并不是在攻擊模擬實踐,只是提出要重用模擬和存根)
如果想發現最慢的兩個測試,可以使用如下命令:
py.test -s --tb=native -x --durations 2
SQLAlchemy和測試
但是系統使用的是SQLAlchemy!數據要以模型實例的方式在不同的層之間流動。執行一次數據庫查詢,可能已經使用了超過10毫秒的時間。這迫使你意識到,如果這次查詢命中了數據庫,那么它就不再是單元測試。(想要實例化SQLAlchemy模型是非常迅速的,但是與SQLite的交互卻很耗時,即使它是保存在內存中)。TDD迫使你將查詢,session.flush()和session.commit()等放到適合于單元測試的函數之外。
即使如此,你仍然需要編寫一些綜合測試。它可以測試出不同層次之間的聯系,并且捕獲到單元測試不能查出的BUG。對于綜合測試,John Andreson有一個很好的使用方法:使用SQLAlchemy,但是永遠不要允許提交事務。在每一個測試結尾處,加上session.rollback()以使得下一次的測試可以在數據庫未被改變的情況下執行。這種方法可以讓你不用每次測試都重新建立數據表。
為了實現這一點,你不能到處提交會話,最好的是制定一個規則:系統只能在最外面的層中通過調用session.commit()實現提交,即web視圖層或者Celery任務中。不要在模型層提交,也不要在動作層提交!
這會導致最后一個問題:如果一個任務是用來提交事務的,怎么為該任務編寫單元測試?我需要一種測試調用任務的說法:異常,就這一次(因為它是測試),其他的請不要提交。否則單元測試就會命中服務器,并且執行超過10ms的時間限制。
最終我會給出一個外部的函數(比如測試用例)去控制是否提交事務。使用這種方案,默認情況下會提交事務,但是允許測試用例告知可以不提交。下面就是這段示例代碼:
歡迎討論,希望對你有幫助
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的python做大型网站_Python中的大型Web应用:一个好的架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑硬盘坏了怎么修复电脑硬盘坏了怎么修复
- 下一篇: 牙套一般花多少钱