標籤:
最近在實踐微服務化過程中,對其“單一職責”原則深有體會。那麼只有微服務化才可以單一職責,才可以解耦嗎?答案是否定的。
單一職責原則是這樣定義的:單一的功能,並且完全封裝起來。
我們做後端Java開發的,應該最熟悉的就是標準的3層架構了,尤其是使用Spring.io體系的:Controller、Service、Dao/Repository。為什麼要分層?就是為了保證單一職責,資料模型的事情交給Controller,商務邏輯的事情交給Service,和資料打交道的事情就交給Dao/Repository。有時候或者有些人會分層分的更多,4層,5層,我自己也這樣幹過,說白了也是為了保證單一職責,3層不能滿足單一職責了,耦合度高了,就分。
我們都知道一個webapp在經過一定時間的開發後,就慘不忍睹,即便是有標準的分層,頁面或模板檔案一大堆,最初的很清晰的3層標準架構也變味了,Controller,Service,Dao/Repository各層之間、Service之間、Dao/Repository之間互相調用,一團亂麻。這個時候沒改一行代碼都有可能一個老鼠害了一鍋湯,bug就如同螞蟻洞。
這些問題最後就造成:
- 可擴充性靈活性差,出現效能問題
- 業務變更和開發困難,維護成本很高,交付時間長
- 迴歸測試量很大
- ...
為瞭解決這些問題,就需要時時刻刻清楚的記住“單一職責”,單一職責可以用到軟體開發的任何地方。
應該說職責分離來解耦是最常用最有效架構方法,這能夠很大限度的簡化一切。
下面就從軟體開發、設計、架構,以及重構/演化/進化,從小到大幾個方面來說說單一職責:
類方法/函數
這應該是最小的能體現單一職責的程式單元了。最熟悉的最典型的莫過於Helper/Utils類方法了,但這種類方法的特徵很明顯,也很容易遵循單一職責,99%以上的開發人員都可以做到。但不僅僅這樣的類方法要遵循單一職責原則,每一個類方法都應該遵循單一職責原則,尤其是一些處理商務邏輯的類方法更要遵循單一職責原則,處理業務的類方法通常要配合類的單一職責原則進行,下節中討論。
因此,這也是為什麼很多Team Leader要求類方法程式碼數保持在20行左右,其實就是為了保證單一職責,20行左右是一個經驗粗略數字,當然,10行或者30行來完成類方法也是可以的。大部分單一職責的類方法用20行左右的代碼就夠了,如果超過20行就要考慮是否保證了單一職責了。那我們在迭代重構的過程中就要考慮拆分這樣的類方法來保證單一職責。
類方法的單一職責是最單純的,很具體的,不摻雜任何額外資訊,只關心輸入、輸出、和職責;一定要明確地定義類方法的職責,保證在迭代中不被錯誤的擴充,不被調用者錯誤地使用。
類/函數檔案
要用物件導向的設計方法,單一職責原則來定義類。開發人員一定要很好地理解“單一職責原則”,具有物件導向的抽象思維能力。
當在迭代中一個類過於龐大或者快速膨脹,說明已經有壞味道了,這時候就需要考慮用單一職責原則或者物件導向的分析方法來重構和重新定義類了,通常就是要抽象和拆分類,否則將來會變成一個方法容器。
把類比作一個人,她的職責就是完成自己職責範圍內的事情,如果她什麼事情都管,就叫多管閑事,可以想象她多管閑事的後果,會攪得雞犬不寧。同樣,類也是,類如果多管閑事,那會攪得整個應用不穩定,漏洞百出,還很難修複。所以說定義一個類,要明確這個類的職責。使用物件導向的分析和設計方法,能很好地準確定義一個類的職責範圍,通常會用到封裝、繼承、多態和抽象等設計方法。
包結構/檔案夾
分層就是最常用的架構方法之一,分層具體體現在分包和分類,就是分門別類的意思。俗話說,物以類聚,人以群分。
包結構在單一職責原則上是類的補充,職責範圍進一步擴大。如果把一個類叫做一個人,那麼包就是一個最小單位的團隊,職責就是負責一類特定事情。
如何分包呢?那就要用到分類學的知識了,要以什麼特徵來分,可能不僅僅只有一種特徵,比如,先用公司網域名稱來做基礎包名,這裡叫一級包名;然後再用一個特定的有意義的標識作為二級子包名;之後按分層(web,dao,service等等)方法做三級包名,也可以先按照業務再按分層。例如:
網域名稱:tietang.wang有個項目叫:social那麼我可以這樣分:wang.tietang - social - web - service - dao - commons也可以這樣:wang.tietang - commons - user - web - service - dao - relation - web - service - dao
多工程/module
通常以多maven module或者gradle 多module形式存在,來保證單一職責。
當業務量還沒有達到服務拆分的火候,通常在一個APP發展的太龐大時或者在工程建設初期時,需要規範和整理項目結構。這個時候需要多工程從檔案系統上隔離,通過module依賴來整合。需要注意的是這樣的架構或拆分不是隨意的,要以單一職責原則來拆分,更具體一點就是要根據業務、技術架構功能等特性來拆分。
比如,按技術組件拆分,通常會有一些技術組件,可以把她放到commons module,如果有多種類型的技術組件,就拆分為commons module的子module;也可以直接將這些技術組件拆分為獨立的工程,存在於獨立的git/svn倉庫,獨立管理,專人負責,其他哪些module需要就依賴她。那拆分的這些技術組件的每一個應該遵循單一職責原則,例如資料分區的架構、NIO基礎網路架構等等。
比如,按業務拆分,例如有使用者、訂單、商品、支付,那麼就按照這些業務拆分為子module,每一個子module就只負責自己的商務邏輯,也遵循單一職責。
那每個module的職責範圍又比類和包更大,這個時候職責也更模糊,有時候很難把握,對於技術組件可能相對清晰,而業務module就要熟悉業務,明確業務邊界。
多module拆分後也是為將來服務化埋下伏筆,同時在物理檔案系統比較清晰了,那在依賴管理上也要掌握好保持清晰的依賴邏輯,把握好單一職責原則。
微服務/可部署單元
微服務,從運行時隔離,但業務量發展到一定時候,從單體或者多module工程拆分或演化出來,可獨立打包可獨立部署並複合單一原則的application,當然了微服務所體現的價值不僅僅是隔離和獨立部署,還有很多這裡可以參考單體應用與微服務優缺點辨析。單一職責在微服務中的價值是最重要的,包含了app層面和開發app的團隊層面,微服務的大部分優點都可以圍繞單一職責來展開。
團隊
先引用《韓非子·揚權》中的一段文字:
夫物者有所宜,材者有所施,各處其宜,故上下無為。
使雞司夜,令狸執鼠,皆用其能,上乃無事。
上有所長,事乃不方。
矜而好能,下之所欺:辯惠好生,下因其材。
上下易用,國故不治。
各得其所,各司其職。所以,團隊也要遵循單一職責原則,這樣才能很好地管理團隊成員的時間,提高效率。一個人專註做一件事情的效率遠高於同時關注多件事情。同樣一個人一直管理和維護同一份代碼要比多人同時維護多份代碼的效率高很多。每一個人都有自己的個性,他有自己的擅長,讓每一個人專註自己擅長的事情,那肯定事半功倍,整個團隊績效肯定也很突出。
總之,引用古文名句說明了所有:
- 物以類聚,人以群分。
- 天下之事,分合交替,分久必合,合久必分!
- 使雞司夜,令狸執鼠,皆用其能,上乃無事。
參考
- http://www.jianshu.com/p/f9d15827465d
- https://zh.wikipedia.org/wiki/單一功能原則
軟體開發中的單一職責(轉至INFOQ)