许多Web部署的应用程序都是在精心设计的数据库驱动的服务器端开发框架中编写的,例如PHP和Java™servlet,但是对于一些简单的程序(例如,整个数据库要能够存放在Web服务器的RAM中)来说,使用加锁的DMB文件和Perl MLDBM模块可以很容易地实现数据持久性。本文将给出一个基于Web的投票系统的真实的例子,重点介绍如何利用最小的外部模块、如何舍弃基于客户机的cookie以及如何利用CGI属性的优点。
软件正日益变得更加复杂,这并不是什么秘密;我们也看到一些额外的层次被添加到系统中,以保持软件组件的模块化。最重要的结果是,这些系统现在更易于维护,而且可扩展性也更好;但是有时这些技术有过多的重复,会导致软件的过度设计。在另外一些情况下,开发开发人员宁愿选择一些过度复杂但却非常有名的技术,也不愿意集成一些简单但却不太熟悉的技术。
不管怎样,如果所有人都有一把锤子,那么每个问题看起来都不过像一颗钉子而已。
我最近被请求为一所大学的学生组织设计一个小程序,以统计选举票数。这是一个非常简单的项目,每周处理的学生请求不会超过500个;之后该程序会立即统计并发布结果。
由于这个项目对服务级别的要求很低,因此使用一个外部数据库来处理查询并没有什么好处。相反,使用脚本可以直接快速读写数据结构。不过,我仍然希望能够将一些经过良好设计的功能封装在一起,而不是采用一些像意大利面那样,将杂乱的代码拼装在一起。我希望可以采用一个经过仔细考虑的自成体系的设计,该设计将提供一些简化的部署。
Perl看来是这个项目的首选语言——在很多平台上似乎都受到支持,此外,在Perl的知识库CPAN中,还有很多方便的库可以使用。
对于底层架构来说,CGI(CommonGatewayInterface)是第一种广泛用来扩展Web服务器从而提供交互内容的方法。开发人员通常会鼓吹一些新的标准,例如JSP、.NET、mod_perl、PHP和ISAPI,这些技术也的确可以弥补CGI的一些不足。但是在这个项目中,我们只需要对几百个用户计算投票数,这样一个CGI脚本很难构成一个大型的应用程序,因为所有的投票信息都可以放到Web服务器的系统RAM中。在用户每次提交一个读写数据的请求时,这可以将要查询的整个表装入内存中。
还有,通过将逻辑数据分隔成3个不同的物理文件,可以实现填写选票、确认选票和统计结果的逻辑顺序;这样做可以最大限度地减少打开已加锁的文件。
如果一个事务在很偶然的情况下因为加锁的文件而失败了,那么这并不会产生实际的问题。不管一个事务是由于网络问题还是加锁文件而失败的,结果都是相同的:用户只需再等待一会儿即可,选票随后很有可能对其中的一次尝试进行统计。我们应该记住这种行为,然而,对于不同的应用程序来说,情况并非总是如此,因此可能无法处理并发事务。
对于这个项目来说,CGI提供了以下几个优点:
然而,需要记住的是,由于平台的限制,CGI程序(它们会创建一些新的进程)在Win32的系统上运行速度非常慢。此外,尽管ApacheWeb服务器已经可以在Windows®上运行得很好,但是它依然被认为是一个Linux™/UNIX®系统上的程序。
现在让我们立即开始考虑这个简单项目的主要问题:功能设计。
以下是我们的一些考虑。开始的时候,用户面前会出现一个屏幕,要求输入用户自己的电子邮件地址,并从一个Web表单中选择几个候选人。选中候选人后就可以提交他们,结果会记录在本地的一个预选票中。然后,会向提供的电子邮件发送一个电子邮件确认。在这种情况中,我们假设一个经过验证的电子邮件地址就足以建立用户的身份。
这样会出现多次投票的问题。从实践角度来说,我想我们没有什么办法限制一个用户使用多个电子邮件地址进行多次投票,但是我们可以对选票进行限制,只允许一个电子邮件帐号投一票。这个电子邮件的验证中包含一个链接,它指向原来的CGI脚本,这样就可以将该链接与本地DBM文件中保存的数据进行比较。如果两个记录匹配,那么这张选票就是有效的。如果这两个记录不能匹配,那么这张选票就不会被核实。相反,会生成一个新的电子邮件确认,其中包含数据库中的一条新验证记录。这将覆盖对应电子邮件地址的预选票项,从而有效地从头再次处理选票。
如果这两条记录可以匹配,那么投票者就可以确认预选票。现在,如果投票者改变了注意,那么他可以只返回Web表单,并输入一个新的预选票,替换原来的预选票。这种设计可以得到一个比较安全的系统;条件是每个投票的用户都有且只有一个可以接受的电子邮件帐号,这样就可以保证每个用户都不会投两次票。(稍后我会回到这个问题上。)
现在让我们开始详细介绍系统的细节。
在Perl中,可以使用哈希键值来创建联合数组,从而使我们能够动态开发复杂的数据结构。当您将这种特性与将这些(任意复杂的)数据结构保存在二进制DBM文件中的能力结合在一起使用时,就可以开发出一个小型的数据库系统。完成这些工作所缺少的组件可以由MLDBM和MLDBM::Sync模块提供。
MLDBM模块可以将复杂的Perl哈希键值无缝地保存在一个本地文件中。MLDBM::Sync模块使得对这些文件进行安全加锁成为可能,它使用了$sync->Lock和$sync->ReadLock方法。在加载或保存所需要的结构之后,再调用UnLock()方法来刷新I/O并释放变量。(关于这方面的更多信息,请参阅Perl文档中有关MLDBM::Sync模块的内容:man3MLDBM::Sync。)
从根本上来说,逻辑流程非常简单,如清单1所示。
1unless(defined($q->param($vparm))){2#Displayinitialvotingstuffhere3#selectacandidate4$ballotBox->printForm($q);5}else{6#ifvoteistallied,do_not_mailaballot7if($castBallot->voteIsTallied($q)){8print"Yourvotehasalreadybeenrecorded"9}else{10#11#votenottalliedyet,checkifwehaveadraftballotonfile12#andmovethedraftBallotintothecastBallotobject13#14if($draftBallot->exactMatch($q)){15#castballot16print$q->h2(Thankyou,yourvotehasbeenrecorded.);17#addthevotetothecastballotdbfile18$castBallot->tallyVote($q);19#sumupallvotes20$ballotBox->addVotes($castBallot);21$cc_msg->send();22}elsif($draftBallot->voter_is_okay($voter_email)){23#Sende-mailtoallowvotertoconfirmvote24$mime_msg->send()25}else{26printOnlyUniversityballotsareacceptable;27}28}29}

