疯狂的想法——基于.NET的软件超市平台构想与5年实现之路
在2005年的时候,我曾经基于.NET 2003开发了一个小的组件,这个组件的目的是为了解决模块化开发和模块复用的问题。我将该组件命名为Common Form Framework,它的目的是允许每一个开发人员独立的开发自己的模块且可以直接专注于业务模块,然后通过配置可以快速将所有开发人员开发的业务逻辑窗体集成到这个组件中。
该组件的思路如下图所示。该组件提供了一个如“2”标识的空的窗体,每一个开发人员通过编写一个如“1”的XML配置文件即可将一个模块的功能附加到空窗体,最终组合成一个如“3”所示的软件产品。
这个组件成功的应用在一个由9个人合作开发,历时1年的应用系统开发中。它的想法和Microsoft Composite Application Block有一些类似,不过没有CAB那么强大了。
在参考了CAB和经历更多应用系统之后,我发现该组件有不少缺点,比如:模块化定义不够标准、界面元素无法扩展、模块交互非常复杂、功能复用低、不能应用于Web或者其它应用环境等。为此,我参考了CAB和SCSF的一些功能,并在2007设计了一个Commom UI Platform规范,旨在设计一个更为强大简单的模块化快速开发平台。
不过在2008年的时候,我有了更多的想法,提出了一个UIShell产品构思。UIShell是英文User Interface Shell的缩写,中文译为“用户界面外壳”。它是由一个软件模板框架。它由框架层、服务层、Shell层和系统模块层组成,提供了基于UIShell的软件设计和开发规范。 Shell中文译为“外壳”,它是应用系统的主界面,由可扩展的界面元素(如菜单、工具栏)和可替换的界面(如显示区)元素组成,如下图所示。
这个产品面向的用户有2种:(1)开发人员——该产品能够为开发人员提供一个模块化设计规范、通用的界面框架和通用的服务,从而使得开发人员可以直接设计业务模块,不需要关心软件的界面、用户体验等;(2)最终用户——最终用户不需要去购买任何软件,可以通过基于该平台的软件超市中下载到所需的界面框架和应用模块,然后自己组装成最终的软件。
在我现在看来,当时的想法确实有点疯狂,因为我想的太简单太远大了(不过,有时候还真需要疯狂才能干点什么,:)),不过我那会一点都没有意识到这点。我当时组了一个UIShellDev Team。我很骄傲的告诉团队,“一旦我们实现了UIShell,我们或许能够为软件行业开辟一个新的方向,为其贡献点什么”。
于是我们便开始了UIShell产品之路,我们疯狂的学习了Enterprise Library、SCSF、SharpDevelop、Egeye Addin、MAF、MEF等,分析了SCSF的源代码、SD源代码,学习了Framework Design Guideline,关注每一个新出现的产品并分析竞争优势与劣势(如Google App Engine、Sina App Engine、MEF等,我们不能开发一个对开发人员来讲没有用且过时的产品,因此需要时刻保持警惕),制定了产品开发规范——“用户场景设计规范、需求规范、设计规范、质量保证体系等”…… 这个产品设计目标以“易用性”为首要目标,这意味着我们做任何功能都应该先想到用户,并模拟用户的行为习惯来不断的优化产品的设计。然而这条路并没有像我预想的那么容易,我原来以为这个产品早该在2009年底就发布了。设计的过程中,问题一个接一个,且由于我们团队是兼职的,进度比我预想的慢了许多。更为重要的是,当时想法是基于SCSF来做的,SCSF太过于复杂,并不能够满足我们的需求。在一个偶然的机会,我接触了OSGi规范,并利用业余时间将OSGi规范翻译了。看了OSGi后,我眼前一亮,我意识到了这就是我想要的。然而OSGi是基于Java的规范,由于.NET平台和Java平台的差异,我们需要设计一个符合.NET平台的规范。于是,我们便动手自己设计了OSGi.NET规范,在设计这个规范时,我们借用了OSGi规范但调整了它的目标,即OSGi.NET的定位是一个满足.NET不同应用环境的通用模块化运行时,它实现了OSGi的模块化与插件化、面向服务、模块扩展和安全性的功能。
OSGi.NET规范及接口设计在2008年底设计完成,我记得当时完成设计的时候,我正在美国的Dublin,通过Email把设计的图纸和规范发送给UIShellDev Team。这是该规范的初稿。在接下来的日子里,我们不断的对设计进行重构,最终在2009年8月份实现了内核原型,在2009年10月份完成了OSGi.NET设计最终稿。当然,在重构的过程中,团队其他成员已经开始动手设计了。在这里我们设计了一个能够通用于各种.NET运行环境的模块化运行时,它实现了UIShell产品所有功能,并且易用性依然保持。我们自行设计了模块化规范、模块运行时类加载规范、SOA规范和扩展规范、开发与调试规范。不过,中间有一个决策比较困难,因为ASP.NET不同于WinForm、WPF和Console,它必须宿主在Web Server。那么,我们的争论就在于——是IIS宿主模块运行时还是模块运行时宿主IIS呢?如果模块运行时宿主IIS,那么它就有完全的控制权,不够运行于IIS的模块与模块运行时的其它模块间的通讯就麻烦了,因为这是跨进程通讯。如果IIS宿主模块运行时,那么模块运行时就比较被动了。最终讨论的结果是采用第二种方案,因为这种方案性能高、简单。在完成最终稿设计时,产品设计的所有问题便解决了。我们便投入所有的精力去实现。
目前UIShell产品设计与实现已经进入尾声,这也意味着软件超市的基础平台已经基本构建完成。我们实现的OSGi.NET内核已经能够成功的宿主在.NET各种不同环境,并且各种环境的设计思路、开发思路完全一致。软件超市以后将会有不同环境的Shell模块、通用服务和应用模块,这样,用户和开发人员都可以去下载和组装软件,并且也可以去贡献自己开发的东西。
还需要提到的是,UIShell产品在实现的过程中,关于质量保证体系的构建。事实上,产品设计的初始阶段,我是很希望所有的东西都能够非常的完善,包括质量保证体系,我当时是一个完美主义者。不过,我们并没有足够的资源来支撑“完美”。在这一过程中,我学会了妥协、学会了“软件中庸”,我们只能把有限的资源投入到最需要的地方,况且每一个阶段的目标还不同。当然了,我们现在已经构建了一个简单且有效的质量保证体系,它基于“Subversion/TotoiseSVN/AnkhSVN + CruiseControl.NET/NAnt + BugTracker.NET”实现。Subversion提供了类似ClearCase的配置管理功能,是一个开源免费的产品,它提供了强大的Branch/Tag管理,Branch/Tag是我当时选择配置管理工具的首要要求,这是产品线管理的必备功能。CruiseControl.NET/Nant用于持续集成,在每一个代码更新时,它都会自动Build,我们可以看到产品线是否健康,此外,还有一个很重要功能,我们可以随时构建一个新的用于测试的安装包。BugTracker.NET也是一个开源的缺陷管理工具,我们可以随时创建Bug。它在每次Bug更新时,都会向团队发送邮件。它提供了强大的缺陷统计管理,在Bug Fixing阶段,我们可以方便的安排产品不同阶段需要Fix的所有Bug,也可以用于统计每一个人的工作量。当然了,我们还根据需要对BugTracker.NET进行了改进,主要有2个:(1)当代码提交时,Bug状态自动变为Check in并发送邮件;(2)加入代码审计功能,可以方便的为每一个Bug生成代码审计包,从而使得我们可以方便查看每一个Bug所做的更改。以下是一个BugTracker.NET Email通知示例。
目前UIShell内核产品由安装包工程、VS插件工程、Remote Console工程、OSGi.NET工程、SaaS工程、Web Extension工程、Shell工程、测试工程和Help工程构成。只要在不同环境中采用如下方式宿主模块运行时,这个环境便具有了OSGi.NET的所有特性。现在经过测试的环境有控制台、WinForm和ASP.NET,接下来我们在完善了文档、Sample之后将发布第一个版本,并在下个版本中实现对更多环境的集成测试,完善产品,并构建软件超市网站。
此外,我们还将构建一个SaaS商店,不过这是另一个产品了,我将会在以后介绍我们SaaS商店产品了。最后我要感谢UIShellDev Team的所有成员,他们为产品的构建付出了很大的努力,提出了很多有建设意义的想法,这个产品是一个团队的结晶。在产品研发过程中,我们体验了团队1+1>2的力量。没有他们的付出,UIShell产品是不可能实现的,更别提其它宏伟的想法了。每次想起与团队开发过程中的细节,我都非常的骄傲和感动,这些人真nice!
namespace SimpleBundleShell
{
class Program
{
static void Main(string[] args)
{
// 启动模块化运行时,这样,它就会自动的从plugins加载所有模块
// 并启动它们。在这里,一个模块=普通.NET项目 + Manifest.xml。
// 因此,模块的开发和我们原来开发一个.NET项目的方式是完全一样
// 的,我们不需要学习新的知识。
using (BundleRuntime bundleRuntime = new BundleRuntime())
{
bundleRuntime.Start();
Console.WriteLine("Press enter to exit...");
Console.ReadLine();
}
}
}
}