使用Maypole快速部署Web应用程序
您有一个数据库。您有一个Web服务器。您有一个截止日期。
无论是为一家新企业开设电子商务店面,实现HR员工数据库的新前端,还是提供一种跟踪美式英语俚语术语引用的便捷方式,故事总是相同的——而且截止日期总是昨天。
在这个4月,我正在为Perl基金会发起一项赞助,以开发我的一个名为Maypole的项目,该项目使Perl程序员能够快速搭建数据库前端以及复杂的基于Web的应用程序。
非常快速,并且几乎不需要编写任何Perl代码。我使用Maypole搭建了一个内部网站门户、一个选择菜单和菜谱的数据库和显示系统、歌词和弦谱投影软件、开源社交网络站点,以及一个啤酒品鉴笔记的Web数据库;这仅仅是过去两周内的事情。
Maypole的灵活性源于三个基本原则
为了展示这三个原则,我们将查看一个典型的Web应用程序——在线商店的产品目录——并看看我们如何能够快速使用Maypole将其构建出来。
关注点的分离
Maypole最初被称为Apache::MVC
,反映了其基于模型-视图-控制器设计模式的基础。(我首先必须更改它,因为Maypole不依赖于Apache,其次是因为Apache::MVC
这个名字真的很无聊。)这与Java的Struts框架等其他语言中类似项目的基石相同。
这种设计模式主要存在于图形应用程序中;想法是您有一个表示和操作数据的模型类,一个负责向用户显示数据的视图类,以及一个根据用户触发的事件控制其他类的控制器类。这种类比与基于Web的应用程序并不完全对应,但我们可以从中提取一个重要的原则。正如Andy Wardley解释的那样
Web MVC群体真正试图实现的是关注点的清晰分离。将您的数据库代码放在一个地方,将您的应用程序代码放在另一个地方,将您的展示代码放在第三个地方。这样,您可以随意切割和更换不同的元素,希望不会影响其他部分(当然,这取决于您的关注点如何分离)。这是常识和良好实践。MVC通过清楚地分离输入(控件)和输出(视图)来实现关注点的分离。
这正是Maypole所做的事情。它具有多个数据库驱动程序、多个前端驱动程序以及多个模板展示驱动程序。在常见情况下,Maypole为您提供了所有这些领域的精确需求,您只需要专注于编写应用程序的业务逻辑。这也是Maypole让您能够快速开发的原因之一——因为大多数时候,您根本不需要进行任何开发。
那么,让我们先选择构成我们的产品数据库的元素。我们将实际使用迄今为止最常见的模型、视图和控制器类配置:Maypole提供了一个基于Class::DBI
的模型类,一个基于Template::Toolkit
的视图类,以及一个基于Apache mod_perl
的控制器类。我们稍后再来解释这一切意味着什么,但由于这种配置如此常见,它是默认的;不需要编写任何代码来设置它。
然而,我们需要一个数据库。我们的客户将是iSellIt
,一个虚构的电脑组件和软件供应商。我们将有产品、制造商和物品类别以及类别子类别的数据库表。以下是该数据库可能的样子。
CREATE TABLE product (
id int NOT NULL auto_increment primary key,
category int,
subcategory int,
manufacturer int,
part_number varchar(50),
name varchar(50),
cost decimal(6,2),
description text
);
CREATE TABLE manufacturer (
id int NOT NULL auto_increment primary key,
name varchar(50),
url varchar(255),
notes text
);
CREATE TABLE category (
id int NOT NULL auto_increment primary key,
name varchar(50)
);
CREATE TABLE subcategory (
id int NOT NULL auto_increment primary key,
name varchar(50),
category integer
);
我们将假设已经将一些数据加载到这个数据库中,但我们希望销售人员通过Web界面自行更新它。
为了使用Maypole,我们需要一个所谓的驱动模块。这是一个非常短的Perl模块,它定义了我们正在工作的应用程序。我说这是一个Perl模块,这可能会让你认为这是关于编写代码,但说实话,大部分实际上是伪装成配置。以下是我们的ISellIt应用程序的驱动模块。(客户可能被称为iSellIt
,但多年接触Perl模块名称使我反感以小写字母开始。)
package ISellIt;
use base 'Apache::MVC';
use Class::DBI::Loader::Relationship;
ISellIt->setup("dbi:mysql:isellit");
ISellIt->config->{uri_base} = "http://localhost/isellit";
ISellIt->config->{rows_per_page} = 10;
ISellIt->config->{loader}->relationship($_) for
("a manufacturer has products", "a category has products",
"a subcategory has products", "a category has subcategories");
1;
十行代码;这是Maypole应用程序可能的大小。让我们逐行分析。
package ISellIt;
这是我们的应用程序名称,这是我们告诉Apache将其用作我们网站Perl处理程序的内容。
use base 'Apache::MVC';
这表示我们正在使用Apache作为Maypole的前端,因此我们正在编写一个mod_perl
应用程序。
use Class::DBI::Loader::Relationship;
现在我们使用我编写的Perl模块来帮助组装Maypole驱动类。它允许我们以直接的方式声明数据库表之间的关系。
ISellIt->setup("dbi:mysql:isellit");
我们告诉ISellIt
连接到数据库,并确定应用程序中的表和列。此外,因为我们没有更改任何类默认值,所以我们假定我们将使用Class::DBI
和Template Toolkit。我们可以表示我们想使用Apache::MVC
与DBIx::SearchBuilder
和HTML::Mason
,但我们没有。
基于Class::DBI
的Maypole类使用Class::DBI::Loader
调查数据库结构,然后将product
表映射到ISellIt::Product
类,依此类推。你可以阅读更多关于Class::DBI
的表类映射是如何工作的,在Tony关于它的文章中。
ISellIt->config->{uri_base} = "http://localhost/isellit";
ISellIt
有时需要知道它的位置,以便它可以正确地产生指向应用程序内其他页面的链接。
ISellIt->config->{rows_per_page} = 10;
这表示我们不想在一页上显示整个产品列表;在得到列表的页面视图之前,每页最多有10个项目。
ISellIt->config->{loader}->relationship($_) for
("a manufacturer has products", "a category has products",
"a subcategory has products", "a category has subcategories");
现在我们定义我们的关系约束,以合理自然的语法:一个制造商有许多产品,一个类别将界定一组产品,依此类推。
十行代码。我们得到了什么?
合理的默认值
Maypole的第二个基础是它使用合理的默认值。它有一个通用的模板系统,可以“做正确的事情”,以在数据库中查看和编辑数据。在许多情况下,Web应用程序程序员根本不需要更改默认行为;在大多数情况下,他们只需要更改一些模板,在最好的情况下,他们可以声明模板是Web设计团队的问题,而不需要做任何工作。
因此,如果我们安装应用程序和默认模板,并访问我们的网站,http://localhost/isellit
;我们应该看到这个
这只用了10行代码。但这还不够,因为如果我们点击产品列表,我们会得到这样的屏幕
这是我们可能无需进一步更改即可提供给销售团队的,他们可以愉快地添加、编辑和删除产品。
同样,如果我们接着点击产品表中的一款制造商,我们会看到一个关于制造商、他们的产品等内容的实用页面。
我认为我们已经在10行代码中得到了一些收获。接下来,我们将模板交给网页设计师。Maypole在三个不同的地方寻找模板:首先,它寻找特定于一个类的模板;然后,它寻找整个应用的自定义模板;最后,它查看factory目录以使用通用的、正确的模板。
因此,为了制作更好的制造商视图,我们告诉他们把factory/view模板复制到manufacturer/view并自定义它。我们把factory/list复制到product/list并自定义它作为产品列表;我们把factory/header和factory/footer复制到custom/目录中,并将它们变成围绕每个页面的模板,等等。
现在,我并不是很擅长HTML设计,这就是为什么我喜欢Maypole——它把问题转交给别人——但这意味着我并不是很擅长展示你可以用模板做些什么。但是这里有一个原型;我用以下模板创建了product/view
:
[% INCLUDE header %]
[% PROCESS macros %]
<DIV class="nav"> You are in: [% maybe_link_view(product.category) %] >
[% maybe_link_view(product.subcategory) %] </DIV>
<h2> [% product.name %]</h2>
<DIV class="manufacturer"> By [% maybe_link_view(product.manufacturer) %]
</DIV>
<DIV class="description"> [% product.description %] </DIV>
<TABLE class="view">
<TR>
<TD class="field"> Price (ex. VAT) </TD>
<TD> £ [% product.cost %] </TD>
</TR>
<TR>
<TD class="field"> Part number </TD>
<TD> [% product.part_number %] </TD>
</TR>
</TABLE>
[% button(product, "order") %]
产生了以下截图。它可能看起来并不更好,但至少它证明事情可以被制作成不同的样子。
我们编写了一个Template Toolkit模板;被[% ... %]
包围的部分是模板指令。如果你对Template Toolkit不太熟悉,Maypole手册的视图文档中有关于TT在Maypole上下文中的良好介绍。
Maypole提供了一些默认的模板宏,如maybe_link_view
,它将对象链接到一个查看该对象的页面,尽管所有这些都可以被覆盖。它还传递了对象product
,它知道这是我们正在谈论的对象。
实际上,这正是Maypole的真正目的:我们用将其描述为将数据库的前端添加到数据库中的方式,但本质上,它负责使用URL /product/view/210
来加载ID为210
的product
对象,在其类上调用view
方法,并将其传递给view
模板。同样,/product/list
调用产品类的list
方法,它用一页的产品填充模板。
这个模板的有趣之处在于最后一行
[% button(product, "order") %]
这产生了一个按钮,它将向URL /product/order/210
发送POST请求,它与view
相同,只是这次调用的是order
方法。但是Maypole还不知道如何order
一个产品。这没问题,因为我们可以告诉它。
易于扩展
Maypole的第三个原则是易于扩展。也就是说,Maypole使从简单的数据库前端到完整的Web应用变得非常容易。这也是很好的;如上所述,一旦模板从网页设计师那里回来,你就会发现,你原本认为只是一个产品数据库的,已经变成一个在线商店。而你还有截止日期。
但在我们开始扩展我们的目录应用以适应新的规范之前(我们将在关于这个的第二篇文章中这样做),让我们看看我们已经取得了什么成就以及我们立即需要什么。
我们有了列出我们数据库中所有产品、制造商、类别和子类别的方法;我们有添加、编辑和删除所有这些内容的方法;我们可以按制造商、价格等搜索产品。什么阻止我们将它部署为一个面向客户的网站,以及产品目录的Intranet更新呢?
当前的主要问题是安全性。我们可以添加、编辑和删除产品 - 但任何人都可以这样做。我们希望只允许来自外部世界的人查看、列出和搜索;对于其他所有事情,我们要求用户来自我们内部IP地址范围内的地址。(目前是这样;当我们添加购物车时,我们会添加用户的概念,而特权用户的概念也不会太远。)
不幸的是,现在我们想要一些用户自定义的行为,我们不得不开始编写代码。幸运的是,我们不需要写很多。我们首先在我们的驱动类中添加几行代码,定义我们的私有IP地址空间为NetAddr::IP
对象,因为这样提供了一个方便的方式来确定一个地址是否在网络中。
use constant INTERNAL_RANGE => "10.0.0.0/8";
use NetAddr::IP;
my $range = NetAddr::IP->new(INTERNAL_RANGE);
现在我们编写我们的认证方法;Maypole的默认authenticate
允许每个人访问一切,因此我们需要重写这个方法。
use Maypole::Constants;
sub authenticate {
my ($self, $r) = @_;
# Everyone can view, search, list
return OK if $r->action =~ /^(view|search|list)$/;
# Else they have to be in the internal network
my $ip = NetAddr->IP->new($r->{ar}->connection->remote_ip);
return OK if $ip->within($range);
return DECLINED;
}
authenticate
类方法传递一个Maypole请求对象;这就像一个Apache请求对象,但处于一个非常高的层次 - 它包含有关网络请求的信息、将要用于满足请求的类、我们需要在类上调用的方法、将要处理的模板、任何对象、表单参数和查询参数等。
在此阶段,Maypole已经解析了URI的组件数据库表、操作和附加参数,因此我们首先检查操作是否是普遍允许的其中之一。
如果不是,我们从Maypole对象中提取存储在其中的Apache::Request
对象,并请求远程IP地址。如果它在私有范围内,我们可以做任何事情。如果不是,我们什么也不能做。很简单。
当设计人员告诉你,他们非常希望将图片放置在产品描述旁边时,几乎可以立即上线。没问题。
有两种方法可以实现这一点;一种看起来非常简单,使用文件系统来存储图片,并在模板中放置类似以下内容
<IMG SRC="/static/product_pictures/[% product.id %].png">
但尽管这样做查看图片非常简单,并制作出很好的原型,但上传图片并不那么简单。所以你决定将图片放在数据库中。你在产品表中添加一个“图片”二进制列,然后查阅Maypole手册。
Perl基金会赞助的其中一个好处是,它允许我编写一个真正出色的手册,其中包含处理Maypole的各种技巧;Request章节包含了一些上传和显示图片的配方。
我们需要做一些新的操作 - 一个用于上传图片,另一个用于再次显示图片。我们只展示显示图片的操作,因为你可以在手册中找到它们,而且查看它们实际上是理解如何更广泛地扩展Maypole的一个方便方式。
可视化我们最终将得到的结果是有用的,并从后往前工作。我们将有一个类似/product/view_picture/210
的URL,生成一个包含产品图片的image/png
或类似页面的页面。这允许我们在模板中
<IMG SRC="/product/view_picture/[% product.id %]/">
并且将图片显示在我们的产品视图页面上。实际上,我们更有可能想要说
[% IF product.picture %]
<IMG SRC="/product/view_picture/[% product.id %]/">
[% ELSE %]
<IMG SRC="/static/no_picture.png">
[% END %]
现在,我们已经解释了Maypole将URL转换为方法调用,所以我们将把一个view_picture
方法放入产品的类中;这个类是ISellIt::Product
,所以我们开始如下
package ISellIt::Product;
sub view_picture {
my ($self, $r) = @_;
# ...
}
这里有一个大问题。我们实际上不希望人们能够通过网络调用我们类中的任何方法;这是不明智的。Maypole会拒绝这样做。因此,为了告诉Maypole我们可以远程调用这个方法,我们用属性装饰它
sub view_picture :Exported {
my ($self, $r) = @_;
}
此时,我们可以通过网络调用view_picture
;我们现在需要让它填充Maypole请求中的适当数据
sub view_picture :Exported {
my ($self, $r, $product) = @_;
if ($product) {
$r->{content_type} = "image/png";
$r->{content} = $product->picture;
}
}
这是一个略为不寻常的Maypole方法,因为我们绕过了整个视图类处理和模板阶段,手动生成内容,但这有助于说明一点:Maypole安排适当的对象传递到方法中;我们已经从URL转换到对象,而不需要我们自己的任何代码。
当我们在下一篇文章中实现排序时,我们将添加更多类似的行为,将产品放入用户的购物车,结账,验证他的信用卡等等。但这对现在来说应该足够了:一个有模板的、可网络编辑的产品数据库,带有图片,没有压力,代码不多,并在截止日期内。嗯,几乎是这样。
总结
Maypole正在快速发展,这主要归功于Perl基金会,它使我能够在这个月专注于它;它使我能够写出成千上万字的文章、示例应用程序和与Maypole相关的代码,这有助于Maypole成为开发Web应用程序的极其有用的框架。
标签
反馈
这篇文章有问题吗?请在GitHub上打开一个问题或拉取请求来帮助我们。