您的位置:首页 > 编程语言

Openbiz 用最精锐的代码实现周边业务的高度重用

2012-04-09 00:00 253 查看
引言

本文继续介绍“免费开源”的Openbiz框架,它是一个基于Zend框架基础之上的应用层PHP框架。前文《Openbiz 实现PHP的元数据编程》曾提及过通过元数据(Metadata)来描述极致化描述业务逻辑,其精髓在于高内聚、低耦合的极致抽象思想。本文我们将讲解如何通过这种思路实现极致的业务逻辑重用。

构建于Zend之上的Openbiz 中间层


这种架构方式类似于Java提出的中间层概念, 在Openbiz系统中 除了数据逻辑,会话内存管理,对像工厂这些高级特性外,从它的源代码结构中我们看到大多数外部业务逻辑的实现还是依靠Zend底层来完成的,而对于这些逻辑Openbiz本身所做的工作相当于介于调用逻辑和实现逻辑只见的中间层。(结构如图所)

这样有何好处? 难道不多此一举么?

Zend的核心价值是将很多底层业务逻辑,在代码层实现业务通用性。翻译成白话,Zend确实做了不少很低层的事情,但是在使用的时候我们还必须做很多初始化和设置工作,而这些工作在Zend框架中肯定是必须通过写代码来完成的。那么这种写代码初始化的方式,就重用逻辑而言,比什么都没有已经不错了,但绝对还不够。

让我们用实例来比较一下Zend和Openbiz的不同实现方式。 例如发送电子邮件这种周边业务逻辑。

Zend的实现方法如下:

1. // Create transport

2. $config = array('name' => 'sender.example.com');

3. $transport = new Zend_Mail_Transport_Smtp('mail.example.com', $config);

4.

5. // Set From & Reply-To address and name for all emails to send.

6. Zend_Mail::setDefaultFrom('sender@example.com', 'John Doe');

7. Zend_Mail::setDefaultReplyTo('replyto@example.com','Jane Doe');

8.

9. // Loop through messages

10. for ($i = 0; $i < 5; $i++) {

11. $mail = new Zend_Mail();

12. $mail->addTo('studio@example.com', 'Test');

13.

14. $mail->setSubject(

15. 'Demonstration - Sending Multiple Mails per SMTP Connection'

16. );

17. $mail->setBodyText('...Your message here...');

18. $mail->send($transport);

19. }

20.

21. // Reset defaults

22. Zend_Mail::clearDefaultFrom();

23. Zend_Mail::clearDefaultReplyTo();

Openbiz中间层的实现如下:

//init email service

$sender = “SystemNotifier”;

$emailObj = BizSystem::getService(EMAIL_SERVICE);

$emailObj->useAccount($sender)

->sendEmail($recipient, null,null,

$subject, $content, null, true);

这样看上去显然代码与底层逻辑更加分离,让程序可读性更强,而在这个范例中关于Sender的定义和声明如下

<Account Name="SystemNotifier" Host="smtp.company.com"

FromName="System Notification"

FromEmail="notify@yourcompany.com"

IsSMTP="Y"

SMTPAuth="Y"

Username="user" Password="password" />

这样用XML语言来描述的元数据最大程度增强了代码的可读性,显然如果更换邮件地址完全不需要动实现代码,只需要修改这个XML的元数据即可。而作为Openbiz中间层的价值,它让元数据的修改甚至也不通过代码来实现。 这就是Openbiz的重UI主义。一切都从UI出发。

你可以说对于开发人员来说,修改XML文件的方式已经足够友好了。有必要把这件事情也UI化么?如果你正在开发的是一个产品级系统,通常交付使用的客户是不具备任何代码维护能力的。例如你开发一个订单管理系统,你总不能告诉客户打开某某XML文件,然后修改第几行?那客户肯定会把你问到疯掉。



基于元数据的 可配置式服务

在Openbiz框架中,元数据不单用于描述数据结构和映射关系。他还将可重用的周边业务逻辑抽象为可配置式服务。例如一个典型的Zend_Cache缓存服务,通常这样的应用在各种应用系统的使用率就非常高。 而且专业的Zend将这个服务的底层扩展的极为丰富(也极为复杂)。

如果你阅读过Zend Cache的官方手册,你会发现为你的系统实现基于Zend的缓存服务,远比想像得的复杂多了, 他提供的可选性参数太多了,而且基本上都在一个平面上。你必须先在头脑里对所有的选项有一个整体的全貌,然后才能下笔。

由于篇幅限制,本文不列举范例代码了(因为没100行以上的代码,这事儿说不明白),详细范例请参考官方资料。http://framework.zend.com/manual/en/zend.cache.theory.html

在Openbiz中,我们把Zend所有的缓存选项全部从实现逻辑中分离出来,归纳到CacheService的元数据中。然我们看一下这个默认实例:

<PluginService Name="cacheService"

Package="service" Class="cacheService">

<CacheSetting Mode="Enabled">

<Config Name="lifetime" Value="7200" />

<Config Name="write_control" Value="Y" />

<Config Name="automatic_cleaning_factor" Value="N" />

</CacheSetting>

<CacheEngine Type="File">

<File>

<Config Name="cache_dir" Value="data/" />

<Config Name="file_locking" Value="Y" />

<Config Name="read_control" Value="Y" />

<Config Name="read_control_type" Value="crc32" />

<Config Name="hashed_directory_level" Value="0" />

<Config Name="hashed_directory_umask" Value="0700" />

<Config Name="file_name_prefix" Value="openbiz" />

</File>

</CacheEngine>

</PluginService>

所有的可选性配置项都在这里了,其实底层还是Zend的缓存引擎,但如果将配置参数包装成这样那么初始化工作就清晰多了。

如何更友好的调用呢?

被元数据化封装后,调用代码可以简化成这样,而不需要在调用的时候做很重的初始化工作,只需要指明缓存ID和生存时间即可。

$cache_id = md5($this->m_Name . $sql);

$cacheSvc = BizSystem::getService(CACHE_SERVICE); $cacheSvc->init($this->m_Name,$this->m_CacheLifeTime);

if($cacheSvc->test($cache_id)){ … }

如果只是这样还并不能体现出元数据抽象后的优势性。对于系统缓存来说,最常用的调用莫过于对数据对象的缓存(当然不仅限于此)。为什么在Zend框架的数据对象Zend_DB类中我们丝毫看不到这两种业务逻辑耦合的特性。

下面这个范例 开发人员就可以快速实现关于数据对象的缓存特性:

<BizDataObj Name="DocumentDO" Class="BizDataObj"

DBName="Default" Table="document"

IdGeneration="Identity" CacheLifeTime="7200" >

</BizDataObj>

只需要在数据对象的元数据上声名一个秒为单位缓存生存时间,剩下的事情让Openbiz这个中间层去和Zend底层“沟通”。完成,开发就应该这样简单!

来让它更完善一步?

当你告诉领导或者客户“嘿!我在系统中实现了一个高级缓存特性。”你猜他会问什么? “在哪呢? 让我看看” 或者 “我怎么管理缓存呢”?

从技术角度上看,缓存这种底层功能的存在确实不是必须要有一个用户界面,而Openbiz框架中,系统默认实现了一个默认缓存管理器。这样作为开发人员,我们不用在花费任何精力去处理这些核心业务以外的事情,系统都为我们做好了。



神奇的数据触发器 Openbiz DO Trigger

上文的两个范例中,我们看到了将周边业务进行“极致内聚”和“极致分离”的架构特性。那么如何将这些业务逻辑有效进行耦合呢?

数据触发器概念,这是一个特别聪明高效的想法。这个概念源自于Oracle和SQL Server这样的关系形数据库。但如果将这种业务触发逻辑仅在数据库级别中实现,触发一些数据级操作还可以被理解,SQL语言本身并不是为描述业务逻辑而生。而且这样的架构方式也会让系统失去跨平台能力。那么最好的方法肯定是在数据对象中去实现。

例如这样一个案例: 一个代表订单的数据对象OrderDO ,不管什么情况下,一旦新的订单被创建(调用OrderDO::create()方法)系统就应该自动给客户发送一封确认电子邮件。 或者当订单状态被更新的时候 也需要发送同样的电子邮件给客户。

这是一个很典型的客户应用需求:

如果是Zend框架,这时候要干很多活儿了,为了确保数据调用的一致性,你必须重载数据对象的Create()和Update()方法,肯定要写一堆代码了吧。而如果以后你需要扩展这个对象,保持兼容性还是一个会让你头疼的问题。

怎么样更好的解决这个问题?

Openbiz框架的数据触发器机制是这样处理的。假如数据对象的主体元数据是OrderDO.xml 那么 你可以在与它同级别目录中创建一个 OrderDO_Trigger.xml (看来 Openbiz也从Zend中借鉴了命名规则这个特性)。在这个触发器的元数据中,你可以充分的对Insert/Update/Delete这些事件来按条件声明触发行为。而行为本身呢,既可以是用户自定的类的方法(Obj::method() )也可以直接用元数据的方式实现对一些通用周边逻辑的直接调用,本例中的发送电子邮件就是一个典型的通用逻辑。 然我们看一下实现代码:

<PluginService Name="OrderDO_Trigger" Description="" Package="" Class="doTriggerService" BizObjectName="collab.order.do.OrderDO">

<DOTrigger TriggerType="INSERT" >

<TriggerCondition Expression="" ExtraSearchRule="" />

<TriggerActions>

<TriggerAction Action="CallService" Immediate="Y"

DelayMinutes="" RepeatMinutes="">

<ActionArgument Name="Service" Value="service.lib.userEmailService" />

<ActionArgument Name="Method" Value="sendEmail" />

<ActionArgument Name="RecipientEmail" Value="{@profile:Email}" />

<ActionArgument Name="EmailTemplate" Value="OrderConfirmEmail" />

</TriggerAction>

</TriggerActions>

</DOTrigger>

</PluginService>

配合之前的数据对象,整个应用逻辑没有编写一行PHP代码就得以实现了,这让我们深刻的感受到Openbiz创始人RockySwen在2003年提出的 “不写代码,实现编程” 这一理念。

想法确实很震撼,虽然我们说在具体的开发案例中,特别是处理核心业务逻辑,我们必不可少的还是要动手开发。但相比之前,确实工作量已经大大减少了(只需要把精力放在思考处理最核心的业务逻辑上),这才是基于先进的软件架构能给开发人员带来的最高价值。

比谁代码写的长,以写多少万行代码为荣那个“面向过程”时代早已淡出历史舞台。

写程序,本应惜墨如金。让最精锐的代码,创建最多的价值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息