引言
中繼資料(Metadata)編程思想源於Java這種進階語言,簡單的說就是將商務邏輯與實現代碼進行分離,僅用XML這類的描述性語言描述業務之間的映射關係,不需要寫實現代碼即完成編程。
源於Java的中繼資料編程特性
原資料是軟體架構方面的先進技術之一,讓你可以編寫更少的代碼實現更多的事情,將商務邏輯的重用性發揮到了極致。也許聽起來有些抽象,讓我們來看一個實際的例子來幫你理解我主張的中繼資料編程理念。
Openbiz架構讓 PHP的中繼資料編程變為可能
對於指令碼級的PHP語言,現在已經成為web開發的主流語言之一。但由於他的出身是一個面向過程的程式設計語言(我說的是php3,有鐵鍬那年的事兒了),並不像Python或者Ruby這樣的語言,上來就是為了對象而生的極致對象化語言。
基於這個簡單的背景,也許還有PHP本身開源免費的原因,我們看到針對php語言的進階擴充相對於.Net 、Java、Objective C這樣的商業化語言總是滯後一步。當PHP5發布時高喊"哥們兒 如今已經物件導向了!" Java,.Net和Cocoa問他"你有中繼資料編程概念嗎? 你有UI層級的可重用控制項嗎?Zend怎麼還沒給你穿上衣服?回去玩兒鐵鍬吧"
Zend 架構的發展路線始終熱衷於底層邏輯代碼重用,比如zend_cache, zend_mail, zend_gdata , zend_table 確實實現了大量的底層邏輯的重用,解決了很多微妙複雜的問題。
這裡我們積極認可Zend作出的貢獻,不過"大哥您到多往前邁一步啊!",不!咱們大哥就喜歡幹粗活,不喜歡搞文藝。
最後特別幽默,經過Zend不懈的努力,大哥終於把一件複雜的事情(PHP)抽象為另一件複雜的事情了(Zend架構)。你數數zend的API介面並不比php本身的extension簡單多少。
在此不得不提到 伊利諾大學畢業的碩士高才生,曾任摩托羅拉的技術主管,美籍華人Rocky Swen,(本人一直是他技術思想的追隨者),早在2003年,php4那個年代,他提出了讓PHP基於中繼資料,不用寫代碼即實現編程這一概念,並有了Openbiz架構的雛形,經過了9年的完善到了今天。
這個理念當時就讓我眼前一亮,你看看Zend, CodeIgniter,CakePHP 無一例外屬於繼承式架構 相當於一組可重用的程式碼程式庫,Openbiz架構特別之處在於這是一個解釋型架構,相當於"編譯器"的角色。 當其它開發環境和架構致力於讓開發人員少寫代碼的時候,Rocky兄 提出,別讓他們寫代碼了直接用簡單XML語言來描述映射關係即完成編程。
讓我們來比較一下這幾段程式
以訂單管理的資料對象為例
PHP傳統的資料對象寫法
Class OrderDO
{
protected $m_id;
protected $m_client;
protected $m_product;
protected $m_price;
protected $m_timestamp;
public function create(){ … }
public function delete(){ … }
public function update(){ … }
public function search(){ … }
public function list(){ … }
}
PHP基於Openbiz的中繼資料寫法
<?xmlversion="1.0" standalone="no"?>
<BizDataObjName="OrderDO" Class="BizDataObj" DBName="Default" Table="order" IdGeneration="Identity" CacheLifeTime="7200" >
<BizFieldList>
<BizFieldName="Id" Column="id" Type=" Number "/>
<BizFieldName="client" Column="client" Type="Text"/>
<BizFieldName="product" Column="product" Type="Text"/>
<BizFieldName="price" Column="price" Type="Number"/>
<BizFieldName="timestamp" Column="timestamp" Type="DateTime"/>
</BizFieldList>
</BizDataObj>
太棒了!這讓我看到了兩種未來的可能性,如果編程的主體工作是基於一種描述性的XML語言完成的,那麼以後極有可能出現完全所見即所式的編程方式。
另一種極致可能是下文提到的對象工廠概念,自動化編程。
對象工廠概念,一個會寫程式的程式!
每次提到這個概念都讓我激動不已,彷彿我們距離智能化編程只有咫尺之遙。這個理念據我所知最先提出的是.Net的自省(這個漢語翻譯很詭異)這一概念,即由主程式動態建立出另一個獨立的子程式,動態編譯,然後按需裝載及銷毀(跟變形金剛似的),當時看的我也十分激動,此後這個概念基本上就再也沒人提了。
直到後來我閱讀分析過了Openbiz的底層原始碼驚人地發現了基於PHP實現的對象工廠這一理念。剖析一下思路,以資料對象為例:
基於XML的中繼資料檔案被視為發給"工廠"的裝配單,上面描述了應具體如何"組裝"這個對象,以及這個對象與地層資料庫的映射關係,與同層級的其它對象的映射關係(例如一對多的ORM)
對象工廠接到建立這樣對象的生產指令後,按描述建立並組裝所需對象,並以序列化的方式將對象體和狀態緩衝在系統內,為再次觸發調用,而最佳化效能。直到中繼資料設定檔改變之前,對象只需要動態生產一次,即無限次使用。
基於這種編程邏輯,我們解決一個常見的修改和擴充問題。
例如:客戶經常會再項目驗收時提出底地層資料欄位的修改,"您看連絡人管理這個模組,能不能再增加個 生日 和 喜好 欄位,要不然這尾款恐怕..."。
怎麼辦?
改吧。 增刪讀寫(CRUD),列取(List),搜尋(Search)一個不能少全都要改。
誰改?
肯定你改啊,因為是你寫程式。
Openbiz中繼資料就不一樣了,現在我只修改一個資料描述檔案,然後是對象工廠會檢測到中繼資料設定檔發生改變,然後他來自動重新編寫對象和所有與其相關的映射調用(ORM)。
當你面對的是一個業務偶合性特別複雜的系統時,你會發現這些上層對象"你中有我,我中有你"堆疊式調用複雜至極(噁心至極)。比如在文檔修改記錄的視圖中也調用了連絡人的這幾個欄位等,你確定能一個不差的修改遍與這個資料結構的每一個角落嗎?
此非人力所能為也!但對象工廠可以,因為是按需生產建立。
PHP語言自身對對象的處理還存在這樣一個缺陷,對象的生命期是不能跨視圖的,當一個頁面請求被執行完畢,與其相關所有資源就都被自動銷毀回收了。特別是資料對象這種通常會帶有資料庫連結和遊標狀態的對象。
Openbiz的會話管理機制配合對像工廠的實現可持久的對象狀態。
對象工廠會在交付對象前調用抽象類別的MetaObject::setSessionData()介面來自動還原對象狀態。
這樣在編寫程式的時候,我們在整個使用者會話期內可以跨視圖去調用使用者曾經輸入過的資料。例如將使用者曾經輸入過的資訊作為預設值顯示在當前這個視圖上。
在php中的調用文法範例:
$defaultValue =
BizSystem::getObject("package.do.DataObjName")
->getActiveRecord()
->fieldName;
在中繼資料中通過Openbiz的SimpleExpression的文法範例
<Element ...
DefaultValue="{@package.do.DataObjName[fieldName].Value}"
/>
不僅描述資料對應,基於Openbiz中繼資料描述商務邏輯
如果你已經對這種中繼資料編程方式有興趣了,請繼續往下看。
出了資料對象與資料表之間的關聯映射可以被中繼資料化,還有什麼可以中繼資料化?
MVC的三層結構,UI也可以中繼資料化,VIew和Form這些UI級元素同樣可以通過XML語言來描述裝載與觸發關係,例如
這個View應該裝載哪幾個Form?
這個編輯Form上的文本控制項應該綁定到哪個資料對象的哪個欄位上?
當按下這個按鈕的時候,應當觸發哪個類的哪個方法?
我們想象增刪讀改(CRUD)這些常用邏輯架構底層能實現,如果我的需求是當客戶下完訂單後,自動發郵件通知我,並且發簡訊通知配貨部門。這樣的相對複雜商務邏輯,如何中繼資料化實現?
資料對象觸發器 和 可配置的插入式服務
這個郵件和簡訊的觸發肯定不應該在UI層實現,因為我們要考慮不管訂單從何處被產生,都應觸發發送郵件這個邏輯。所以這個商務邏輯應該被耦合在資料對象上,即只要有訂單被產生就應當觸發該邏輯。
而發郵件和發簡訊些種常見的可重用性邏輯,可以被定義為pluginService,例如在發郵件的Service中,收件者,標題,內容應當是API的參數,而發郵件的帳戶,SMTP伺服器資訊相對於業務整個系統來說通常變化不大,應作為中繼資料介面,而如何與伺服器連結來發送郵件則是具體被重用的對象邏輯了。這種設計的精妙之處我們將在下一篇文章中具體給大家分析。
這樣實現剛才說的邏輯,我只需要建立一個
OrderDO_Trigger.xml的中繼資料檔案
<PluginServiceName="OrderDO_Trigger" Description="" Package="" Class="doTriggerService" BizObjectName="collab.order.do.OrderDO">
<DOTriggerTriggerType="INSERT" >
<TriggerConditionExpression="" ExtraSearchRule="" />
<TriggerActions>
<TriggerActionAction="CallService" Immediate="Y" DelayMinutes="" RepeatMinutes="">
<ActionArgumentName="Service" Value="service.lib.userEmailService" />
<ActionArgumentName="Method" Value="sendEmail" />
<ActionArgumentName="RecipientEmail" Value="{@profile:Email}" />
<ActionArgumentName="EmailTemplate" Value="OrderConfirmEmail" />
</TriggerAction>
</TriggerActions>
</DOTrigger>
</PluginService>
當"INSERT" 事件發生的時候,調用發郵件服務,收件者為使用者的郵箱,郵件內容範本為OrderConfirmEmail
到現在為止,一行代碼都沒寫就把這個典型的商務邏輯用中繼資料的方式描述了出來。而用傳統開發方式,處理這樣的問題,150行以上代碼的工作開銷是怎麼也避免不了的。而且可讀性還不一定這樣清晰。
說到這裡,本人太喜歡Openbiz這隻飛鳥(我是說他的Logo)了,感覺像是給PHP插上的翅膀。
現在我們PHPer可以對Java說,PHP現在能描述商務邏輯,你Java無非只能是資料對應和配置資訊吧。
Openbiz 中繼資料編程 與 Zend命名規則匹配編程 的比較
本文介紹的Openbiz這種中繼資料思想並非唯一,如果單純就資料對象的抽象化而言,還有一種不得不提的先進模式,叫命名規則匹配模式。這種也是Java的一種擴充。翻譯成php實現方法,例如:
$obj = new stdDataObj($tableName);
$obj->name='ABC';
$obj->attribute_1 = 123;
$obj->attribute_2 = 456;
$obj->save();
我並不需要在使用前定義這個對象的結構,而是隨需要而建立,反正儲存資料的時候讓它自己去自動與資料庫匹配去。 但貌似這種邏輯的擴充性只限對資料邏輯,擴充邏輯還要用傳統的定義聲明方式。
重要的這還是不能將開發人員的主要工作從代碼中解脫出來。
本文以Openbiz架構為例,讓我們看到了編程的另一種可能性。
從如何簡化代碼,到極致的盡量不寫代碼,而只描述商務邏輯。
這才是物件導向思想中最大化重用的精髓。