Preface:

这篇文章是在图灵社区中接受的第一份翻译工作 —- 翻译AOSA(The Architecture of Open Source Application),目的是:

在翻译文档的过程中深入学习开源软件架构,同时进行知识共享
认识更多的人
以下是AOSA的第2卷的第22章,关于Yesod这个Web框架的(由),Yesod的简单介绍可以参照Wiki,及其官网. 由于是初稿翻译,今后也将不断完善,欢迎指正。

Yesod 是用Haskell语言编写的Web框架。目前流行的众多Web框架得益于其宿主动态语言的某些特性,而Yesod则不同—-其得益于Haskell的静态语言的特性,使得代码更高效,也更安全。

Yesod的开发从两年前起至今,一路不断地发展,也使之变得越发地强大。Yesod被创造伊始,便是为了满足解决实际项目问题的需要,同时其也在实际项目经验积累中不断地完善起来。起初,该框架的开发以及维护完全就是“个人秀”的行为,而紧接下来的一年里,在源自于社区不断开发以及维护努力之下,终于使得Yesod一跃成为了非常活跃的一个开源项目。

Yesod处于萌芽阶段时,并没有很好的定义、设计,有哪个实际项目组愿意尝试其进行开发是一件让人想都不敢想的事。后来,Yesod的变更使之逐渐进入稳定阶段,并且也被应用到了一些实际项目中,这时候我们也开始着手对设计决策的不足进行重新考察。那时起,我们将改善项目的主要精力投放于如何让面向用户的API更易用 —- 很快我们有了稳定的1.0发行版。

你可能会问:为什么要再开发一个Web框架呢? 或者让我们换一种提问方式:为什么要用Haskell呢? 一般看来,目前世界上大多数开发者都围绕以下两类语言风格:

静态类型语言,比如Java,C#和C++。这些编程语言提供高效的代码执行以及类型安全,但开发起来总让人觉得比较笨重

动态类型语言,比如Ruby和Python。这些编程语言极大地提升了生产效率(至少短期来说是这样),但是却难掩执行效率的相对低下以及编译器对程序正确性验证支持不足的特点(对于最后这点而言,解决方案是进行一定的单元测试。我们一会儿会谈到。)

这是对语言的一种错误的二分法。静态类型语言没有理由变得这么愚蠢。Haskell在作为强类型语言的同时,也兼顾了许多Ruby和Python中的表达特性。事实上,Haskell的类型系统比Java及其语言家族更加严谨,能在编译期纠正更多的错误:空指针异常被排除了;不可变的数据结构简化了代码推理的同时,也简化了并行与并发编程。

那么为什么选择Haskell? 它是一门高效的,对开发者友好的编程语言,它也提供了许多编译时期程序正确性的检查。

Yesod的目标就是Haskell的强大特性引入到Web开发中去。Yesod努力让你的代码更加紧凑,尽最大可能让你的每一行代码在编译器都完成正确性检查。换句话说,与需要大型库去单元测试程序基本属性相对地,编译器都为你做好了。

从深层面来说,我们在Yesod中运用了尽可能多的高级性能优化技术,有了它们的帮助,你的高层次代码运行起来便能如虎添翼。

22.1 与其他Web框架的比较

总的来说,Yesod与其他主流框架(Rails,Django之流)大同小异。其主要采用了MVC范式—-通过一个模板系统将逻辑与表现分离,同时提供了一个对象关系映射(ORM)系统,以及一个用于完成路由的前端控制器。

Yesod强大的地方在于一些细节之处。Yesod尽可能在编译阶段捕获错误或者异常,而非延迟到运行时,并且通过类型系统提供自动化捕捉bug以及保证安全性缺陷。尽管Yesod的目标是维护一系列用户友好,高层的API,但其采用了许多函数式编程世界的新技术来提升程序的性能的同时,也不惧对开发者暴露这些内部细节。

Yesod在架构上的主要挑战来自于如何平衡这两个看上去相互冲突的目标。举例来说,Yesod的路由方式(称为类型安全的路由)并没有格外标新立异。从历史上来看,实现类似问题的解决方案总是一个乏味,伴随错误丛生的过程。应对类似的问题,Yesod的解决方案在于采用了模板Haskell(Template Haskell,代码生成的一种形式)使得一些中间步骤实现了自动化,从而加速了解决方案实现的过程。

类型安全的HTML已经存在好一段时间了,在这个方面,Yesod既保证类型安全的威力得到充分发挥,也保持了对于开发者友好的通用模板语言所应有的特点。

22.2 Web应用接口(WAI)

一个Web应用程序需要某种方式与服务器进行通信。第一种方法是将服务器直接放入到框架中,但这样会限制你的部署选项,导致接口过少。很多语言都创建了标准的接口来解决这个问题:Python有WSGI和Ruby有Rack。在Haskell中,我们有WAI:Web应用程序接口。 WAI不是一个高层次的接口。它有2个特定的目标:通用性和性能。在一般情况下,WAI已经能够支持从独立服务器的老学校的CGI一切后端甚至直接与WebKit生产仿桌面应用程序。性能方面将向我们介绍一些Haskell的很酷的功能。

Haskell的一个最大的优点——也是我们在yesod中用的最多的东西——是强大的静态类型。在我们开始编写代码来解决问题之前,我们需要考虑一下数据会是什么样子。WAI是这个例子中完美的范例。我们要表达的核心理念是一个应用程序。而应用程序最基本的表达式是一个函数,这个函数接收一个请求,返回一个响应。

在Haskell语言中:

type Application = Request -> Response 下划线__

这就引出了一个问题:请求和回应是什么样子?请求有许多部分,但是最基本的是请求路径,查询字符串,请求头和请求主体。一个响应有三个部分:状态码,响应头,响应体。我们如何表示类似的查询字符串?

Haskell保持文本和二进制数据之间的严格分离。前者是由bytestring,后者的文本。两者都是高度优化的数据类型,提供了一个高层次的、安全的API。在查询字符串的情况下我们店转移过来的线作为一个bytestring和解析的原始字节,解码值为文本。

一个bytestring代表一个内存缓冲区。如果我们天真地使用纯bytestring持有整个请求或响应的身体,我们的应用程序不能适应大规模的请求或响应。相反,我们使用的技术称为枚举数的概念非常类似Python中的发电机。我们的应用成为一种消费流表示传入的请求和响应的身体bytestrings,生产一个单独的流。

我们现在需要稍微修改我们的应用程序的定义。一个应用程序将一个请求值,包含头文件,查询字符串,等等,都会消耗bytestrings流,产生一个响应。因此,修改后的应用程序的定义是:

type Application = Request -> Iteratee ByteString IO Response

该输入可以简单地解释一个应用程序可以执行哪些类型的副作用。在输入输出的情况下,它可以进行任何形式的互动与外部世界,一个明显的必要性,绝大多数的网络应用。

生成器

我们项目的关键是如何制造我们的反应缓冲区。我们在这里有2个相互竞争的欲望:最小化系统调用,和最小化缓冲区副本。一方面,我们要尽量减少系统调用在套接字上发送数据。要做到这一点,我们需要在缓冲区中存储输出数据。然而,如果我们把这个缓冲区太大,我们会耗尽我们的记忆,减缓应用程序的响应时间。另一方面,我们要尽量减少缓冲区之间的时间数据复制的次数,最好是从源到目的地缓冲区复制一次。

Haskell的解决方案是建造。一个生成器是一个如何填充内存缓冲区的指令,例如:在下一个打开位置放置五个字节“你好”。不把内存缓冲区传递给服务器,围应用程序通过这些指令的流。服务器采用流,并用它来填充最佳大小的内存缓冲区。当每一个缓冲区被填充时,服务器会发出一个系统调用来发送数据,然后开始填充下一个缓冲区。

(缓冲区的最佳大小将取决于许多因素,如缓存大小。潜在的火焰生成器库进行了显着的性能测试,以确定最佳的权衡。

在理论上,这种优化可以在应用程序本身进行。然而,在接口这种方法编码,我们可以简单地预先设置响应头的响应体。结果是,对于小型到中型的响应,整个响应可以被发送一个单一的系统调用和内存是复制只有一次。

处理程序

现在,我们有一个应用程序,我们需要一些方法来运行它。在外的说法,这是一个处理程序。围有一些基本的、标准的处理程序,如独立服务器经(下面讨论),FastCGI,保国和CGI。该频谱允许外应用程序运行在任何从专用服务器到共享托管。但除了这些,外有一些更有趣的后台:

Webkit:这个后端嵌入经服务器和呼唤QtWebKit。通过启动一个服务器,然后启动一个新的独立的浏览器窗口,我们有人造的桌面应用程序。

Launch:这是在WebKit的轻微变种。在部署Qt和WebKit库可以有点麻烦,所以我们只是推出用户的默认浏览器。

Test:即使测试计数作为一个处理程序。毕竟,测试只是一个应用程序的运行和检查的行为。

大多数开发商可能会使用Warp。它是轻量化,可用于测试。它不需要配置文件,没有文件夹层次结构,没有长时间运行,管理员拥有的过程。这是一个简单的图书馆,被编译到您的应用程序或运行通过Haskell解释器。经是一个令人难以置信的快速的服务器,从各种攻击的保护,如Slowloris和无限的标题。经可能是唯一的Web服务器,你需要的,虽然它也很乐意坐在反向的HTTP代理。

Pong基准措施每各服务器的请求响应的4字节的第二身体“傍”。在图22.2中显示的图,Yesod测量作为经纱的框架上。可以看出,Haskell服务器(经纱,happstack和SNAP)领导包。

大部分的理由是因为Warp的速度已经远远超出了WAI的速度:统计员、建设者和包装类型。在最后一块拼图是从格拉斯哥Haskell编译器(GHC的多线程运行时)。GHC,Haskell编译器的旗舰,具有轻质绿色线程。与系统线程不同的是,它可以在没有严重的性能攻击的过程中旋转数千个。因此,在经处理的每个连接是由它自己的绿色线程。

下一个窍门是异步的,任何一个网络服务器都希望能够扩展到每秒成千上万的请求,需要某种类型的异步通信。在大多数语言中,这涉及到复杂的编程涉及回调。GHC让我们作弊:我们计划,如果我们采用同步API,GHC自动切换不同的绿色线程间等待活动。

在表面,GHC使用任何系统是由主机操作系统提供,kqueue,epoll和选择。这给了我们一个基于事件的系统的性能,而不必担心跨平台的问题或以一个回调的方式编写。

中间件

在处理程序和应用程序之间,我们有中间件。从技术上讲,中间件是一个应用程序变压器:它需要一个应用程序,并返回一个新的。这被定义为:

Middleware
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

了解中间件的目的的最好的方法是看一些常见的例子:

·gzip压缩响应从一个应用程序自动。

·JSONP自动转换成JSON响应JSON-P响应时,客户端提供一个回调函数的参数。

·autohead将生成基于获取应用程序的响应适当的响应头。

·调试将打印调试信息到控制台或每个请求的日志。

这里的想法是,从应用程序中的通用代码,让它可以很容易地共享。请注意,基于中间件的定义,我们可以很容易地把这些东西堆起来。中间件的一般工作流程是:

1.取请求值并应用一些修改。

2,。通过修改后的请求到应用程序和接收响应。

3.修改响应并将其返回给处理程序。

在堆栈中间件的情况下,而不是传递到应用程序或处理程序,中间件之间的,实际上分别是通过内部和外部的中间件。

**外测试**

没有量的静态类型将排除测试的需要。我们都知道,自动化测试是一个必要的任何严重的应用。围试是推荐的方法来测试一个围申请。由于请求和响应都是简单数据类型,很容易模拟出一个虚假的请求,把它传给一个应用程序,并测试了反应特性。外测试只是提供了一些方便的功能,用于测试类似于头或状态码的通用属性。

**22.3.模板**

在典型的模型视图控制器(MVC)模式,其中的一个目的是独立的逻辑视图。部分的分离是通过使用一个模板语言来实现的。然而,有许多不同的方法来解决这个问题。在光谱的一端,例如,PHP,ASP和JSP将允许您将任意代码在你的模板。在另一端,你有像StringTemplate和多变的系统,这是通过一些参数,没有其他的方式与程序的其余部分相互作用。

每一个系统都有它的优点和缺点。有一个更强大的模板系统可以是一个巨大的方便。需要显示数据库表的内容吗?没有问题,拉它与模板。然而,这样的做法会导致复杂的代码,在数据库游标更新HTML代。这通常可以看到在一个不好写的项目。

虽然弱模板系统制作简单的代码,但它们也趋向于大量的冗余工作。你会经常需要不仅保持原来的值在数据类型,而且可以创造的价值传递给模板字典。维护这样的代码是不容易的,通常是没有办法的编译器来帮助你。

Yesod家族的模板语言,莎士比亚语言,争取中间立场。通过利用Haskell的标准引用透明性,我们可以放心,我们的模板不产生副作用。然而,他们仍然有完全访问所有的变量和函数在你的Haskell代码可用。同时,由于他们完全都检查合法性,可变分辨率和类型安全在编译时,错别字是不太可能有你通过搜索你的代码试图牵制的bug。

----
为什么叫莎士比亚?
HTML语言,哈姆雷特,是第一语言写的,和原来的语法对HAML。因为这在当时是一个“减少”HAML,哈姆雷特似乎是适当的。当我们添加CSS和JavaScript选项,我们决定与卡和尤利乌斯保持命名的主题。在这一点上,哈姆雷特看起来一点也不像HAML,但名字贴呢。
***


类型

在Yesod的主题之一是让开发人员的生活更轻松类型的正确使用。在Yesod模板,我们有两个主要的例子:

1.所有的内容嵌入到一个村庄的模板必须有一个类型的HTML。后面我们会看到,这迫使我们正确的回避危险的HTML在必要的时候,同时避免意外逃逸以及双。

2.而连接URL直接在我们的模板,我们有数据类型被称为类型安全的网址,代表在我们的应用程序的路径。

作为一个现实生活中的例子,假设用户通过表单提交他/她的名字。这一数据将以文本数据类型。现在我们要显示这个变量,叫做名称,在一个页面中。类型系统在编译的时候不能仅仅停留在哈姆雷特的模板,因为它不是类型的HTML。我们必须以某种方式转换它。为此,有2个转换功能:

1.toHtml会自动逃脱任何实体。因此,如果用户提交该字符串<script src="http://example.com/evil.js" ></script>不到的迹象将自动转换为&amp;lt;

2.preEscapedText,另一方面,现在也会把内容准确地留下。

所以在不可信的输入的情况下从一个可能的恶意用户,toHtml将我们推荐的方法。另一方面,让我们说,我们有一些静态的HTML存储在我们的服务器,我们想插入几页逐字。在这种情况下,我们可以将它加载到一个文本值,然后应用preescapedtext,从而避免任何双逃逸。

默认情况下,将任何内容哈姆雷特你尝试使用toHtml函数插值。因此,你只需要显式地执行转换,如果你想逃避。在此之前,谨慎为上的格言。
name <- runInputPost $ ireq textField "name"
snippet <- readFile "mysnippet.html"
return [hamlet|
<p>Welcome #{name}, you are on my site!
<div .copyright>#{preEscapedText snippet}
1
2
3
类型安全链接的第一步是创建一个数据类型,代表着你的网站的所有路线。让我们说你有一个网站显示Fibonacci数。该网站将有一个单独的网页,每个号码的序列,加上主页。这可以用Haskell数据建模:
data FibRoute = Home | Fib Int
然后,我们可以创建一个这样的页面:

You are currently viewing number #{show index} in the sequence. Its value is #{fib index}.


Next number


Homepage

1
然后,我们需要的是一些功能,将一个类型安全的网址转换成一个字符串表示。在我们的情况下,这可能看起来像这样:

render :: FibRoute -> Text
render Home = “/home”
render (Fib i) = “/fib/“ ++ show i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
幸运的是,所有的定义和绘制类型安全的URL类型模板处理开发商自动Yesod。我们将在更多的深度覆盖。

**其他语言**

除了哈姆雷特,还有其他三种语言:尤利乌斯、卡和卢修斯。尤利乌斯是使用JavaScript;然而,它是通过语言简单的通过,只允许插值。换句话说,除非意外使用插值的语法,任何JavaScript可下降到尤利乌斯和有效。例如,为了测试尤利乌斯的表现,jQuery是贯穿语言没有问题。

其他两种语言交替的CSS语法。那些熟悉的青菜少的区别认识这立即:卡修斯是空白分隔,而卢修斯用括号。卢修斯其实是CSS的超集,这意味着所有有效的CSS文件是有效的卢修斯文件。除了允许文本插值,有一些提供给模型单元的大小和颜色的辅助数据类型。此外,在这些语言中的类型安全的网址工作,使其方便指定背景图像。

除了类型安全和编译时检查上面所提到的,在CSS和JavaScript的专业语言给我们一些其他的优势:

·生产,所有的CSS和JavaScript编译成最终的可执行程序,提高性能(避免文件I / O)和简化部署。

·通过建立在有效的构建结构的基础上,该模板可以很快地呈现。

·有内置的支持自动包括这些最后的网页。在描述下面的小部件时,我们会更详细地介绍这个细节。

**22.4。持续性**

大多数网络应用程序都希望将信息存储在数据库中。传统上,这意味着某种SQL数据库。在这方面,Yesod继续一个悠久的传统,是我们最常用的PostgreSQL后端。但我们在最近几年已经看到,SQL不是持久性问题的答案。因此,Yesod旨在与NoSQL数据库,以及很好的工作,并与MongoDB的后端作为一等公民的船只。

这一设计决策的结果是持久的,Yesod的首选存储选项。真的有两种指示灯持续性:使其后端不可知的可能,并让用户代码是完全类型检查。

同时,我们充分认识到,它是不可能完全屏蔽用户从后端的所有细节。因此,我们提供2种逃生路线:

·后端特定功能。例如,持续提供的功能列表和哈希联接和MongoDB数据库。适当的移植警告将适用,但是如果你想要这个功能,它就在那里。

·容易获得原始查询。我们不相信任何抽象,以涵盖底层的图书馆的每一个使用的情况下,它是可能的。如果你想写一个5-table,就去查询SQL。

**术语**

在持续的最原始的数据类型是persistvalue。这表示在数据库中可能出现的任何原始数据,例如数字、日期或字符串。当然,有时候你会有更多的用户友好的数据要存储,像HTML。为此,我们有persistfield类。在内部,一个persistfield表现在一persistvalue术语数据库。

所有这一切都很好,但我们要把不同的领域组合成一个更大的画面。为此,我们有persistentity,这基本上是一个集persistfields。最后,我们有一个persistbackend描述如何创建、读取、更新和删除这些实体。

作为一个实际的例子,考虑在数据库中存储一个人。我们想把这个人的名字,生日,和轮廓图像(PNG文件)。我们创建一个新的实体的人与三个领域:一个文本,一天一个PNG。每个人都用不同的persistvalue构造函数在数据库存储:persisttext,persistday和persistbytestring,分别。
前两个映射没有什么令人惊讶的,但最后一个是有趣的。这是存储在数据库中的内容没有具体PNG的构造函数,所以我们用一个更通用的类型(一bytestring,这仅仅是一个字节序列)。我们可以使用相同的机制来存储其他类型的数据。

(通常是保存图像的最佳做法是将数据保存在文件系统中,并且在数据库中保留一个路径的图像。我们不提倡使用这种方法,但使用数据库存储的图像作为一个例子。
数据库中的所有这些都是如何体现的?考虑SQL为例:该人实体成表三列(姓名,生日,和图片)。每个字段都存储为不同的SQL类型:文本成为一个VARCHAR,天变成了一个日期和PNG成为一滴(或bytea)。

MongoDB的故事很相似。人成为自己的文档,和它的三个领域成为一个MongoDB领域。没有必要在MongoDB架构数据类型或创作。

![](/cdn/images/aosabook/7.png)

**类型安全**

持续的处理所有的数据封送处理幕后的关注。作为持续的用户,你可以完全忽略这一事实,文本成为一个VARCHAR。你可以简单地声明你的数据类型和使用。

强类型的每一个相互作用。这会阻止你不小心在日期字段中放置一些,编译器将不接受它。在这一点上,整个类的微妙的错误消失了。

无处是强大的打字更为明显比在重构。让我们说你已经在数据库中存储用户的年龄,你意识到你真的想要过生日。您可以对您的实体声明文件进行一个单行更改,打编译,并自动找到需要更新的每一行代码。

在大多数动态类型化的语言,以及它们的网络框架,推荐的方法来解决这个问题是编写单元测试。如果你有完整的测试覆盖,然后运行你的测试将立即显示什么代码需要更新。这一切都很好,但它比真正的类型是一个较弱的解决方案:

·这是所有的前提是全面测试覆盖。这需要额外的时间,更糟的是样板代码,编译器应该能够为你做的。

·你可能是一个完美的开发者会编写一个测试,但是你可以为每个人谁会碰你的代码说的一样吗?

·甚至100%测试覆盖率并不保证你真的已经测试过每一个案例。所有这一切都是经过验证你已经测试了每一行代码。

**跨数据库的语法**

创建一个SQL模式适合多个SQL引擎可以是足够的。你如何创建一个架构,也将与非SQL数据库,如MongoDB的工作吗?

持续性允许你在一个高层次的语法定义的实体,并将自动为你创建SQL架构。在MongoDB中的情况下,我们目前使用的架构方法。这也让持续保证你的Haskell和数据库定义数据类型完全匹配。

此外,拥有所有这些信息提供了持续的能力来执行更高级的功能,如迁移,自动为您。

**迁移**

持久性不仅创建了模式文件,而且还可以自动应用数据库迁移,如果可能的话。修改数据库的SQL标准之一是欠发达的碎片,从而每个引擎有不同的接受过程。因此,每个持续的后端定义了它自己的迁移规则集。在PostgreSQL,具有丰富的设置修改表的规则,我们使用广泛。由于SQLite缺乏的功能,我们减少了创建临时表和复制的行。MongoDB的无模式的方法意味着没有迁移的支持是必需的。

此功能是故意限制,以防止任何类型的数据丢失。它不会自动删除任何列;相反,它会给你一个错误信息,告诉你是必要的不安全的操作,以便继续。然后你将可以手动运行SQL提供你选择,或改变你的数据模型来避免危险行为。

**关系**

执着是非关系的性质,这意味着它不需要后台支持的关系。然而,在许多情况下,我们可能要使用关系。在这种情况下,开发人员将有充分的访问他们。

假设我们现在要存储一个与每个用户的技能列表。如果我们写一个特定的应用程序中,我们可以继续前进,只是存储在原始人的实体列表中的一个新领域。但这种方法不会工作在SQL。在SQL中,我们称这种关系一一对多的关系。

这个想法是存储一个引用“一个”实体(人)与每个“许多”实体(技能)。然后,如果我们想找到所有的技能,一个人,我们只是找到所有的技能,参考人。为这个引用,每个实体都有一个身份证。当你现在可以期待的,这些入侵检测系统是完全类型安全的。一个人身份的数据类型是事。因此,增加我们的新技能,我们只需添加以下我们的实体定义:

Skill
person PersonId
name Text
description Text
UniqueSkill person name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
这个ID数据类型的概念出现在持续Yesod。你可以派遣基于一个ID,在这种情况下,Yesod会自动元帅身份的文本表示的内部,捕捉任何语法错误的路上。这些ID用于查找和删除的,删除的功能,并通过插入和查询功能插入SelectList返回。

22.5. Yesod
如果我们看的是典型的模型视图控制器(MVC)模式,持续性是模型和莎士比亚的观点。这会让Yesod作为控制器。

对Yesod最基本的特征是路由。它具有一个声明式的语法和类型安全的调度。第一层,Yesod提供许多其他功能:流媒体内容生成、小工具、国际化、静态文件,表格和认证。但核心特征补充Yesod真是路由。

这种分层的方法使得用户可以更简单地交换系统的不同组件。有些人不喜欢使用持久性。对于他们,没有什么在核心系统甚至提到持续。同样,当它们是常用的功能时,不是每个人都需要身份验证或静态文件服务。

另一方面,许多用户都希望将所有这些功能集成。这样做,同时使所有的优化可在Yesod,并不总是一帆风顺的。为了简化这一过程,Yesod还提供了脚手架的工具,建立一个基本的网站最常用的功能。

路线

由于路由是Yesod的主要功能,让我们从这里开始。路由语法非常简单:资源模式、名称和请求方法。例如,一个简单的博客网站可能看起来像:

/ HomepageR GET
/add-entry AddEntryR GET POST
/entry/#EntryId EntryR GET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
第一行定义主页。这个说:“我对该域的根路径,我称之为homepager,我回答的请求。”(后面的“R”在资源名称是一个简单的会议,它没有特殊的意义,除了给予暗示的开发商是一个路由。)

二行定义添加进入页面。这一次,我们得到的答案都得到了回复。你可能会奇怪为什么Yesod,相对于大多数的框架,你需要明确你的请求方法。原因是,Yesod试图坚持宁静的原则尽可能和GET和POST请求真的有非常不同的含义。你不只是单独对这两种方法进行单独的状态,但稍后你会单独定义它们的处理函数。(这实际上是在Yesod可选功能。如果你想要的话,你可以把所有的方法和你的处理程序的功能列表中的所有方法都处理掉。

第三行有点更有趣。我们#项标识二斜线后。这个定义的参数类型项标识。我们已经提到的这一特点,持续的部分:Yesod现在会自动元帅路径组件相关的ID值。假设一个SQL后端(Mongo处理以后),如果一个用户请求/进入/ 5、处理函数会调用一个参数项标识5。但如果用户请求/进入/一些博客,Yesod会返回一个404。

在大多数其他的网络框架中,这显然是可能的。该方法由Django,例如,可以使用正则表达式匹配的路线,如R”/进入/(\ D +)”。的Yesod的方法,然而,提供了一些优势:
·输入“项标识”更比一个正则表达式的语义/开发者友好。

·正则表达式不能表达的一切(或至少不那么简洁)。我们可以在Yesod使用/日历/ #天;你想要类型的正则表达式来匹配您的路线的日期吗?

·Yesod也自动将数据我们。在我们的日历情况下,我们的处理函数会收到一天的值。在Django等效,功能将得到一段文字,它就必须元帅本身。这是繁琐、重复、低效的。

·到目前为止我们已经假设数据库标识只是一个数字串。但是,如果它更复杂呢?MongoDB使用GUID,例如。在Yesod,你#项标识仍然会工作,和类型系统将指导Yesod如何解析路径。在一个正则表达式系统,你将经历所有你的路线和改变\ D +任何怪物正则表达式是需要匹配的GUID。

**类型安全的网址**

这种方法对路由产生一个Yesod最强大的特点:类型安全的网址。而不是文本块拼接一起指的路线,您的应用程序中的每个路径可以用Haskell值表示。这立即消除了大量的404没有发现错误:这是根本不可能产生一个无效的网址。(它仍然是可能的产生一个网址,将导致一个404错误,如通过引用一个不存在的博客文章。然而,所有的网址将形成正确。
那么这个神奇的工作又如何?每个站点都有一个路由的数据类型,每种资源的模式有它自己的构造函数。在我们前面的例子中,我们会得到一些看起来像的东西:

data MySiteRoute = HomepageR
| AddEntryR
| EntryR EntryId

1
如果你想要链接到的网页,你用homepager。链接到一个特定的条目,你会用entryr构造函数参数一项标识。例如,创建一个新的输入和重定向到它,你可以写:

entryId <- insert (Entry “My Entry” “Some content”)
redirect RedirectTemporary (EntryR entryId)

1
哈姆雷特,卢修斯和尤利乌斯都包括内置支持这些类型安全的网址。在一个小村庄里,你可以很容易地创建一个链接到添加条目页:

Create a new entry.

1
2
3
4
5
6
7
8
9
最好的部分?就像执着的实体,编译器会让你诚实。如果你改变你的航线(例如,你想包括在您的进入途径,年和月),Yesod将迫使你更新每一个参考您的整个代码库。

**处理程序**

一旦你定义了你的路线,你需要告诉Yesod要如何回应请求。这是处理程序功能发挥作用的地方。安装简单:每个资源(例如,homepager)和请求的方法,创建一个功能命名methodresourcer。我们先前的例子中,我们将需要四个功能:gethomepager,getaddentryr,postaddentryr,和getentryr。

从路由中收集的所有参数都作为参数传递给处理程序函数。getentryr将第一个参数的类型的项标识,而其他所有的功能将没有参数。

处理函数,生活在一个处理单子,它提供了大量的功能,如跳转,访问会话,并运行数据库查询。对于最后一个问题,一个典型的方式开始的getentryr功能会:

getEntryR entryId = do
entry <- runDB $ get404 entryId

1
2
3
4
5
6
7
8
9
10
这将运行一个数据库操作,该操作将从数据库中获取与给定的标识相关联的条目。如果没有这样的条目,它将返回404响应。

每个处理函数会返回一些值,它必须是hasreps实例。这是在玩另一个宁静的特征:而不是返回一些HTML或JSON,你可以返回一个值,将返回一个,根据HTTP Accept请求报头。换句话说,在Yesod,资源是一个特定的数据块,它可以在一个多次交涉归还。

**窗口小部件**
假设你想包括在几个不同的页面,网站的导航栏。这个导航栏将加载了五最新的博客文章(储存在你的数据库),生成一些HTML,CSS和JavaScript,然后需要一些风格和提升。

没有一个更高级别的接口,将这些组件绑在一起,这可能是一个痛苦的实施。你可以添加CSS的网站广泛的CSS文件,但这是添加额外的声明,你不需要总是。同样的,JavaScript,虽然有点糟糕:有额外的JavaScript可能会在一个页面是不打算生活在引起问题。您还将通过在多个处理程序函数中生成数据库结果来打破模块化。

在Yesod,我们有一个非常简单的解决方案:小工具。一个部件是一块代码联系在一起的HTML,CSS和JavaScript,让你的头和身体都添加内容,可以运行任意代码,属于处理程序。例如,实施我们的导航栏:

– Get last five blog posts. The “lift” says to run this code like we’re in the handler.
entries <- lift $ runDB $ selectList [] [LimitTo 5, Desc EntryPosted]
toWidget [hamlet|


    $forall entry <- entries
  • #{entryTitle entry}
    |]
    toWidget [lucius| .navbar { color: red } |]
    toWidget [julius|alert(“Some special Javascript to play with my navbar”);|]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    但这里有更多的权力在这里工作。当你产生Yesod页面,标准的做法是将多个组件组合成一个单一的部件包含所有您的网页内容,然后将defaultlayout。此功能是定义为每个站点,并适用于标准的站点布局。

    有两开箱的方法来处理,CSS和JavaScript去:

    1.将他们放进风格

    2.脚本标记,分别在你的HTML。

    将它们放在外部文件中,并用链接和脚本标记来引用它们。

    此外,JavaScript会自动缩小。选项2是首选的方法,因为它允许一些额外的优化:

    1.这些文件是以一个基于散列的名称创建的。这意味着你可以将缓存的值放置到未来,而不用担心用户会接受过期的内容。

    2.JavaScript可以异步加载。

    第二点需要一点阐述。部件不仅包含原JavaScript,也包含一个列表的JavaScript的依赖。例如,很多网站会引用jQuery库,然后添加一些JavaScript的使用它。Yesod是能够自动将所有为异步加载通过yepnope.js。

    换句话说,工具允许你创建模块化、可组合的代码会导致令人难以置信的高效率服务于你的静态资源。

    **子站**

    许多网站共享公共领域的功能。也许最常见的例子是提供静态文件和验证。在Yesod,你可以很容易地减少使用子网站在这个代码。所有您需要做的是添加一个额外的路线到您的路线。例如,添加静态网站,你可以写:

    ```/static StaticR Static getStatic

第一个参数告诉在网站的子网站开始。静态网站通常是用在静态的,但你可以使用任何你想要的。staticr是路线的名称;这也完全取决于你,但公约是用staticr。静是静态网站的名字;这是一个你无法控制的。获取静态是一个函数,返回静态站点的设置,如在静态文件的位置。

喜欢你的所有处理程序,处理程序也可以访问子站的defaultlayout功能。这意味着,一个精心设计的网站会自动使用你的网站的皮肤而没有任何额外的干预。

22.6。经验教训

Yesod一直工作在一个非常有价值的项目。它给了我一个机会,在一个庞大的系统与一组不同的开发。一个真正让我震惊的事情是,不同的产品最终会变成我原本想要的东西。我开始Yesod创建一个目标清单。极少数的主要特点目前我们吹捧Yesod在列表和列表的一部分不再是我计划实施。第一节课:

你有一个更好的想法,你需要在你开始工作后的系统。不要把你自己绑在你最初的想法上。

这是我第一篇重要Haskell代码,Yesod的发展过程中,我学到了很多关于语言。我相信其他人会涉及到“我写过这样的代码是什么感觉?”尽管最初的代码是不是同一口径为代码我们在Yesod在这一点上,它是坚固的足以启动项目。第一课是:

被认为缺乏的工具掌握在手不要。写出最好的代码,并不断改进。

一个在Yesod的发展最困难的步骤是从一个人的团队我与他人合作。它开始简单,合并拉要求在GitHub上,并最终转移到具有多个核心的维护者。我已经建立了一些我自己的发展模式,这是没有解释或记录的。作为一个结果,作者发现很难把我的新发行的变化和玩弄他们。这阻碍了别人的贡献和测试。

当格雷戈韦伯上船作为Yesod的另一主角,他把大量的编码标准,缺乏。但问题是,有一些固有的困难玩Haskell开发工具链;特别是在处理Yesod的大量包。一整个Yesod团队目标已经建立标准的脚本和工具的自动化建设。许多这些工具使他们的方式回到通用Haskell社区。最后的教训是:

及早考虑如何让你的项目为他人接近。