一个软件工程师在设计一个新的应用时有许多方面需要考虑,包括功能、性能、安全性和图形化的用户界面(GUI)。但是因为开始时有多种未知的和不可预测的变数导致了一些对你来说很难发现或集成到初始设计中的隐蔽因素,就像未来将会使用你的应用的用户数和他们的详细需求这样的因素。由于这样或那样的原因,你也许在将来的某个时候需要超出它最初的设计来扩充它的功能,换句话说,扩展它。举例来说,应用可能会处理超过它设计的更大的负载,这将会强迫你将你的应用拆分为部署在不同计算机上的更小的应用。
这篇文章提供了创建这种应用的指导方针:为将来某个时候扩展应用做好准备。这些凭借经验的规则将会使你的应用先从小规模开始,然后再根据需要来扩展它。另外,这篇文章将会介绍一组由MantaRay提供的新工具,它是一个基于点对点,无服务器架构的,革新的,开源的数据消息项目。
作为例子,我们将会用到一个简单的应用叫WebMonitor(网页监听器)。它最初是一个非常简单的使用Swing编写的GUI。应用从用户那里得到一个URL作为输入,并检查该URL是否对HTTP请求有响应。应用将会不时地监听该URL,并提供它的最新的状态报告。
图1显示了WebMonitor范例的GUI界面。
图1.Webmonitor范例应用的GUI
模块化
第一条规则是将你的应用拆分为模块:代码块作为独立的实体显示,每一个实体都有它自己明确的责任。一个模块可以由一个或多个对象构成,但反过来不行―一个对象封装多个模块是一种不佳实践(在“解藕模块”部分,我们将会说明原因)
很容易看出在WebMonitor应用中至少有两个模块。首先,一个GUI模块负责取得用户输入和显示结果。其次,一个引擎模块负责检查URL,并提供一个返回结果表明这个URL是否响应。
图2显示了WebMonitor中的模块
图2.WebMonitor中的模块
实际上,有人可以指出第三个模块,一个“模型”模块,它就象一个数据存储设备;当数据改变时,它通知“监听者”模块,这样就完整地构成了MVC结构。这个范例的其余部分已经通过忽略第三模块而有意简化了,我们只集中注意力在引擎和GUI模块上。
在你的应用中把模块标识出来是必须的,如果你还想在将来扩展你的应用的话。当时机来到的时候,模块可以被扩展并被分解为更小的应用;这篇文章的后面我们会讲到如何分解它们。
解藕模块
回到我们的例子,让我们想象一下过了一段时间,WebMonitor已经很成功了。你的客户非常高兴,但又要求你创建其它的用户接口。IT部想要一个命令行的用户接口,支持部想要一个基于web的用户接口,而开发者则想要保持Swing GUI。要想全部完成上述需求,你需要解藕WebMonitor的模块。
为了解藕模块,我们必须确认每一个模块是一个独立的实体。每一个完成解藕的模块必须是一个“黑盒子”,它有着一个明确定义的接口来和其它模块进行通信。
我们看一下WebMonitor并把它拆成若干个解藕的模块
图3显示了WebMonitor的解藕模块
图3 WebMonitor中的解藕模块
模块间通过一个中间模块来相互通信,这样就将模块解藕并消除了一个模块所拥有的其它模块的信息。你可以把这些中间层当成模块间的缓冲区。虽然在这一阶段这些中间层看起来并不重要,但是在这篇文章的下一节它们就变得至关重要了。
当我们完成解藕以后,按客户的要求来增加用户界面就变得容易了。必须的模块加入到WebMonitor中,并在需要时与旧模块进行通信。
图4是增加模块以后的WebMonitor
图4 增加完模块以后的WebMonitor
扩展应用
WebMonitor应用到目前为止很成功;它监听几千个站点并为几百个用户服务。然后你开始得到报告说应用的响应正在变慢,看起来好像引擎占用了太多的内存并且CPU已达到了机器的极限,客户想要增加监听的URL,但是不行,因为机器崩溃了。
你所为应用所做工作?标识出了模块并用中间层将它们解藕?马上就可以得到回报了。既然你已把WebMonitor设计为从头开始扩展,那么现在可以很简单的将引擎放在不同的机器上,甚至可以创建多个引擎并在其上分配负载。因为模块已被中间层解藕,你不需要在模块内部重写代码,但对应的,你只需简单的使用不同的中间层。这样做,你可以无缝地将你的分布式的应用扩展到很多计算机且可以轻松地应对沉重的负载。
图5显示了WebMonitor分布在了几个机器上
图5 WebMonitor分布在了几个机器上
你可能已经注意到了,中间层并没有放在任何机器上,因为它们是可以从任何机器上引用的逻辑实体。
下一节将介绍由MantaRav开源项目组提供的一组中间层,它们可以在内存中或是在一个扩展的分布式环境下工作。使用这些中间层,扩展应用不需要改变任何代码。
扩展的Stage模式和Dispatcher模式
MantaRay提供了两种中间层,点到点(stage模式)和发布-订阅(dispatcher模式),它们针对不同的中间层需求。合在一起,它们组成一个完整的集合,可以满足这篇文章中所有中间层需求。
Stage
SEDA项目定义了一个stage式事件驱动架构,引入了用于模块交互的一个解藕良好的模型,其中,stage被用来作为模块间的中间层。模块将事件排队放入stage中,其它的模块作为操作者来接收stage中的事件,然后触发操作。这些模块当需要的时候,反过来也会将事件排队放入stage队列中。
图6显示了拥有一个stage的解藕模块
图6. 用stage解藕
MantaRay项目将stage的概念提升到了更高层次。模组通过工厂(factory)对象得到一个stage的引用。工厂会动态地生成合适的实现,这依赖于stage是在一个分布式环境中还是在内存中。
几个模块(或同一模块的几个实例)可以将他们自己作为操作者加到同一个stage上,但是一个事件会且只会送给一个操作者。一个事件可以是实现了标记性接口Serializable的任意对象。大多数对象能被序列化,但像Socket和OutputStream这样的对象不能被序列化,因为实际上他们不能通过网络传递。你可以把这个标记性接口给任意对象加上而不需要增加任何方法。
在WebMonitor的例子中,GUI模组通过一个stage将一个请求发送给了引擎,让其监听一个URL。引擎将它们自己注册为stage的操作者,stage反过来由于其中间层的角色也成为了一个软件负载的平衡者。
Dispatcher
dispatcher与一个stage非常类似,但有一个显著区别。与stage事件有且只有一个操作者不同,一个dispatcher事可以被送给所有的操作者,如果它们将自己注册到了dispatcher上。这样,一个dispatcher就作为一个一对多的中间层来工作。
图7 显示了使用了dispatcher的解藕模组
图7. 用dispatcher解藕
应用到事件上的相同的角色被dispatcher分发,就像排队到stage上的事件一样。另外,算法通过工厂(和用来获得stage的引用的一样)来得到dispatcher的引用。
在WebMonitor例子中,引擎通过dispatcher告知GUI关于所监控的URL状态改变的信息。所有的GUI模块将他们自己都注册为发布者的操作者,他们都会得到所监听URL状态改变的通知。
下面是一个简短的代码示例:GUI、引擎如何与stage、dispatcher集成在一起:
GUI代码
import org.mr.api.blocks.ScalableDispatcher; import org.mr.api.blocks.ScalableFactory; import org.mr.api.blocks.ScalableHandler; import org.mr.api.blocks.ScalableStage; ... public class WebMonitorGui implements ActionListener , ScalableHandler{ // 输入的url被检查 JTextField urlInput; // 从外面指向引擎的stage public ScalableStage engineStage; // 从引擎而来的内置dispatcher public ScalableDispatcher guiDispatcher; /** * GUI分别得到一个对内的dispatcher和对外的stage的引用,* 分布式的参数被传至这些对象的工厂。 * @param distributed. 如果布尔值为真则表示这个范例是运行在 * 分布式的环境中,引擎是分布在了不同的虚拟机上。 */ public WebMonitorGui(boolean distributed){ // 从工厂中得到stage engineStage = ScalableFactory.getStage("engine" , distributed); // 从工厂中得到dispatcher guiDispatcher = ScalableFactory.getDispatcher( "gui", distributed); // 将这个对象注册为所有即将发生事件的处理器 guiDispatcher.addHandler(this); } /** * 开始SWING GUI */ public void startGUI(){//确信我们已经有了不错的GUI ..} //开始GUI /** * 当用户把URL的输入通过stage送到引擎时,这个方法被调用。 */ public void actionPerformed(ActionEve