基于yaf的模塊化開發方案
來自: http://www.imsiren.com/archives/1236
隨著我們網站流量越來越大,用戶越來越多,需求也越來越復雜,一站式開發已經遠遠不能支撐我們的業務發展需要,在產品發展期內碰到的問題越來越多,技術架構限制了業務的發展,這對于研發人員來講是不能忍的,業務制約技術發展可以接受,但技術制約了業務的發展就是問題了。
我先來總結下一站式開發將面臨的問題:
1、代碼量大,開發、部署、測試都將是問題
這個很容易理解,我們的業務需求全部在一個系統中的時候,開發、部署、測試必然都會存在很大的風險,比如開發,即使從一開始就參與項目的成員,在業務規模龐大的時候,也很難做把控控制風險;比如部署,因為量大,導致代碼發布會非常慢,大部分發布平臺會先打包,到線上服務器解壓,這個過程當在緊急上線發布的時候會急死人的,一旦上線出現問題,回滾成本將會更高,影響的不單單是發布系統,嚴重的將導致服務不可用的狀態。
2、嚴重制約業務發展
有一個好的習慣,開發人員會在開發過程中習慣性的將公用的功能封裝成類或方法,如果這是核心功能,那它可能出現在整個系統的任何地方,當核心功能需要開發的時候,則會牽一發動全身,很可能面臨的將是系統重構。
舉個很簡單的例子。
如我們有一個函數(或者一個類)
</div>
function custom_function($a,$b) { //TODO }
實現了最基礎的業務功能,一開始它滿足了當前的業務,實現了基本功能,但隨著業務發展,這個基礎功能變得更強大,支持的特性更多了,為了支持業務,我們把函數擴展根據參數做兼容,如:
function custom_function($a,$b,$c){ if(empty($c)){ //兼容老業務 }else if($c== "XXX"){ //支持新業務 }else if($c == "ccc"){ //新業務 } }
雖然能滿足需求,但可以想像維護這樣一段代碼會讓后來人罵娘多少次?當然,雖然我們聽不見,但畢竟讓后人非常不爽。
3、不得已的推倒重構
基于各種壓力,更多的是基于業務發展的壓力,我們不得已痛苦的對整個系統進行重構,這個過程漫長而痛苦,一方面要支持現有系統的業務,另一方面還要對整個系統進行技術重構,讓他們之間的耦合更少,這個成本和風險將會讓我們痛不欲生。
那我們在項目最初是不是就該考慮從業務上拆分成多個模塊?答案是肯定的。但是也將帶來一些問題:
1、PM不懂技術,不清楚怎么拆分
這個問題還好解決,在規劃和項目評審的時候,跟技術一起討論,側重于在業務功能上進行拆分,比如用戶相關操作全部歸為用戶模塊,后臺相關歸為后臺模塊,瀏覽的歸為view模塊
2、怎么控制粒度,控制不好反而會增加更多的成本
這個要在技術內部好好的做溝通和規劃,控制好邊界
3、數據共享?
雖然拆成了多個模塊,但是對于數據倆講,它遍布整個系統,模塊與模塊之間的數據如何共享?
在項目初期的解決方案是,
- 將最基礎的數據獨立成library模塊,它提供最細單元的數據共享,這些可以在整個模塊中調用;
- 提供跨模塊調用的業務接口,比如在模塊A下,讓框架支持調用B模塊下的接口;
- 提供http服務化接口,這個大多數是在項目成熟階段的時候才會做的事情;
</div>
解決困難問題的方向就在框架身上了,它將具備以下特性:
1、代碼量少、精簡
2、可跨模塊調用,模塊中幾乎不用包含任何框架的實現
3、可調用最基礎的library模塊
4、管理配置化
在目前所有php框架中,要想實現模塊化編程還是比較困難的,大部分的框架實現都是在框架內部有自己的application或module,這里面是具體的模塊化的業務代碼。
還有一種完全的模塊化,每一個模塊業務都有這樣一套框架實現:
但是這樣的缺點顯而易見,一方面很難做到跨模塊調用,需要在外層在包一層邏輯實現,另一方面框架維護和代碼量讓模塊顯得更重。
所以更多的是采用第一種方式,今天的主題yaf,其實也是這樣一種架構,只是框架邏輯實現在so擴展中了,
但今天所要談的是基于這兩種方案之中的辦法,既能讓模塊之間完全獨立,又能讓它跨模塊調用接口,而且模塊中基本不用維護任何框架代碼,只需要按照框架的規范寫代碼就可以了。
每個模塊之間提供interface,供其他模塊直接調用,每個模塊只處理自己的業務,當有業務需要共享時直接按照規范寫interface就可以了。
項目目錄如下:
但是yaf不支持跨模塊的調用,模塊與模塊之間是不能通過interface來通信的,為了解決這個問題,我增加了兩個類Yaf_Init和Yaf_Caller
Yaf_Init
$app = Yaf_Init::init(); $app->Bootstrap()->run();
這個只是一層封裝,根據目錄解析出當前模塊名,省去配置config.ini的煩惱,為了靈活性,Yaf_init::init()后面將增加參數可以靈活自定義配置,每一個webroot/module/index.php中都是上面的代碼。
等同于下面
$config = array( 'application'=> array( 'directory' => app目錄 ) ) $application = new Yaf_Application($config); $application->Bootstrap()->run();
為了能夠找到app的目錄,還需要在php.ini中增加一個配置,
yaf.app_path = /home/admin/web/htdocs/app/
如果訪問/home/admin/web/webserver/webroot/module/index.php時,
會解析出module拼成app目錄:
/home/admin/web/htdocs/app/module/傳給$config.application.directory,
將$config 配置傳遞給yaf_application的構造函數進行初始化
</div>
Yaf_Caller
$Object = new Yaf_Caller('moduleName','className'); $Object->test();
這個類用來實例化其他模塊中interface的類庫,其實它也是一層簡單的包裝,保存環境變量,切換到某模塊,然后調用Yaf_loader::autoload(),該構造函數接收兩個參數,第一個是模塊名,第二個是interface的類名,
interface的命名規范:
/app/htdocs/moduleA/Interface/Admin.php
比如模塊A提供了一個interface,類名是admin
則Admin.php的類寫法
</div>
class Interface_Admin{ function test(){ //TODO } }
其他模塊調用時
//object返回的是Interface_Admin類 $object = new Yaf_Caller('moduleA','Admin'); 它直接可以調用Interface_Admin下的public方法。 $object->test();
這套方案有以下優點:
- 模塊代碼量小,只需要實現業務代碼就可以
- 模塊之間可以無縫調用inteface接口
</div>
未完待續……
原文出處:
</div>