# Chicago Boss: A Rough Introduction

# 1. Introduction 介绍

If you want to keep a secret, tell it to a Swede. Born in Stockholm over 20 years ago, Erlang is the most advanced open-source server platform in existence, but it seems almost no one knows about it. Erlang can handle hundreds of thousands of simultaneous connections; it can spawn millions of simultaneous processes in under a second; server code can be upgraded, in production, without any interruption of service; and errors are handled in such a way that server crashes are extremely uncommon.

如果您想保守秘密,请告诉瑞典人。 Erlang是20年前在斯德哥尔摩诞生的,是现有的最先进的开源服务器平台,但似乎几乎没人知道它。 Erlang可以处理数十万个同时连接。它可以在一秒钟内产生数百万个同时进行的进程;服务器代码可以在生产中升级,而不会中断服务;错误的处理方式极少发生服务器崩溃。

What is not to like? Why isn't the entire world programming in Erlang? Well, Erlang is a functional language, which means that to implement any algorithms, you must use recursion instead of familiar “for” and “while” loops. Unlike every major scripting language, there is no built-in syntax for dictionaries or hash maps. And to actually write a functioning server, you must learn an additional layer of magic known as OTP. These barriers, in my opinion, have prevented Erlang from gaining much traction outside of Scandinavia.

不喜欢什么?为什么整个世界都不用Erlang编程?嗯,Erlang是一种函数式编程语言,这意味着要实现任何算法,必须使用递归而不是熟悉的“ for”和“ while”循环。与每种主要的脚本语言不同,字典或哈希图没有内置的语法。要真正编写运行正常的服务器,您必须学习另一层称为OTP的魔力。在我看来,这些障碍使Erlang无法在斯堪的纳维亚半岛以外的地区获得更多关注。

But Chicago Boss changes all that. It makes Erlang accessible to hackers who just want to write a reliable website in a nifty language. Boss uses code generation to get around the historic hash-map quandary, and takes care of all the OTP business so that you can focus on writing the features you need for your website. As for the supposed burdens of functional programming, I find that recursion is rarely necessary in workaday web programming; I would guess that 99% of server application code simply shuttles data to and from a database, so in the course of building a website, the pedestrian procedural programmer will hardly miss his “do/ while” loops.

但是芝加哥老板改变了这一切。黑客只想用漂亮的语言编写可靠的网站,就可以访问Erlang。 Boss使用代码生成来解决历史悠久的哈希图难题,并处理所有的OTP业务,以便您可以专注于编写网站所需的功能。至于函数式编程的假定负担,我发现递归在工作日Web编程中几乎没有必要;我猜想99%的服务器应用程序代码只是在数据库之间来回穿梭,因此在构建网站的过程中,行人程序程序员几乎不会错过他的“ do / while”循环。

If you are an experienced web programmer, you'll probably enjoy all the amenities that CB has to offer: an advanced ORM with support for database associations, sharding, and caching; lightning-fast templates compiled down to Erlang bytecode; automatic recompiling and in-browser error reporting; simple directives for reloads and redirects; routes for constructing URLs and handling requests; full frameworks for sending and receiving email; a built-in message queue; a framework for writing and running functional tests; and a first-of-its-kind event system for monitoring the data model.

如果您是一位经验丰富的Web程序员,则可能会喜欢CB必须提供的所有便利:支持数据库关联,分片和缓存的高级ORM;快如闪电的模板编译成Erlang字节码;自动重新编译和浏览器内错误报告;用于重新加载和重定向的简单指令;构造URL和处理请求的途径;发送和接收电子邮件的完整框架;内置消息队列;用于编写和运行功能测试的框架;以及第一个用于监视数据模型的事件系统。

In the end, by combining the Erlang platform with its own innovations, Chicago Boss makes websites a delight to develop and a joy to deploy. Boss applications can be written in the same time or less as equivalent Rails applications, and they will almost never crash or leak memory. Since the underlying networking is all asynchronous, you can easily write concurrent services, such as chat, that previously were only possible in callback-based frameworks (such as Nginx, Node.js, Twisted, or Perlbal).

最后,通过将Erlang平台与自己的创新相结合,Chicago Boss使网站开发充满乐趣,部署也充满乐趣。 Boss应用程序可以与等效的Rails应用程序在同一时间或更短的时间内编写,并且它们几乎不会崩溃或泄漏内存。由于基础网络都是异步的,因此您可以轻松编写并发服务,例如聊天,以前只能在基于回调的框架(如Nginx,Node.js,Twisted或Perlbal)中进行。

The importance of this advancement cannot be overstated. It is now feasible for a very small team to develop and operate a database-driven, highly interactive, heavily trafficked website with very little capital outlay. Although Chicago Boss can’t tell you how to acquire users, the rest of this manual will show you everything you need to do to handle their requests and (with luck) fulfill their desires.

这种进步的重要性不可夸大。现在,非常小的团队可以开发和运营一个数据库驱动的,高度交互的,流量巨大的网站,而资本支出却很少。尽管Chicago Boss不能告诉您如何获取用户,但本手册的其余部分将向您展示处理用户要求并(满足您的需要)满足您的需求所需做的一切。

# 2. Architecture 结构

Chicago Boss is really just a compiler chain and a run-time library for Erlang web applications. Since compilers are scary, we’ll focus on the runtime library so that you understand the basics of how requests are fulfilled.

Chicago Boss实际上只是Erlang Web应用程序的编译器链和运行时库。由于编译器很可怕,因此我们将重点放在运行时库上,以便您了解如何满足请求的基础。

A single Chicago Boss server hosts one or more CB applications. A CB application is a set of controllers, views, models, and URL routes assigned to a base URL in the CB server configuration. You might deploy a blog application to /blog, a wiki application to /wiki, and a main website application to /. Applications can redirect to one another and access each other’s data models and message queues. This application transparency is useful for creating reusable components that interact with one another, but where security is a consideration, you should probably run untrusted applications in standalone servers.

单个Chicago Boss服务器托管一个或多个CB应用程序。 CB应用程序是在CB服务器配置中分配给基本URL的一组控制器,视图,模型和URL路由。您可以将博客应用程序部署到/ blog,将Wiki应用程序部署到/ wiki,将主网站应用程序部署到/。应用程序可以彼此重定向,并可以访问彼此的数据模型和消息队列。这种应用程序透明性对于创建相互交互的可重用组件很有用,但是考虑到安全性,您可能应该在独立服务器中运行不受信任的应用程序。

A single CB applications might have several parts:

* Web controllers, which are given information about an HTTP request and decide what to do with it

* Web views, which render can data returned by a controller

* Models, which provide an abstraction layer over the database (if any)

* Email controllers and views

* Initialization scripts

* Test scripts

* A route configuration file

单个CB应用程序可能包含以下几个部分:

* Web控制器,提供有关HTTP请求的信息并决定如何处理

* Web视图,该视图可以呈现控制器返回的数据

*模型,在模型上提供抽象层数据库(如果有)

*电子邮件控制器和视图

*初始化脚本

*测试脚本

*路由配置文件

But wait... there’s more! The CB server starts a number of services which are available to all applications residing in that server:

* A URL router (BossRouter)

* A session storage layer (BossSession)

* A database connection and caching layer (BossDB and BossCache)

* A message queue (BossMQ)

* A model event system (BossNews)

* An email server (BossMail)

但是,等等...还有更多! CB服务器启动该服务器中驻留的所有应用程序可用的许多服务:

* URL路由器(BossRouter)

*会话存储层(BossSession)

*数据库连接和缓存层(BossDB和BossCache)

*消息队列(BossMQ)

*模型事件系统(BossNews)

*电子邮件服务器(BossMail)

For the serious website developer, a “server” might actually consist of multiple Erlang nodes linked together. In this case all of the nodes in the cluster defer to a “master node” (specified in the configuration) for shared services, such as the database cache, the message queue, and incoming email. The rest of the nodes can then handle regular HTTP traffic or perform other tasks. In these configurations you will want a proxy server, such as Nginx, to distribute incoming requests across the cluster. Since the Erlang VM is multi-threaded, an Erlang node should correspond to a physical machine. Running more than one node on a machine, while possible, is a waste of resources.

对于认真的网站开发人员而言,“服务器”实际上可能由多个链接在一起的Erlang节点组成。在这种情况下,群集中的所有节点都将转为共享服务的“主节点”(在配置中指定),例如数据库缓存,消息队列和传入电子邮件。然后,其余节点可以处理常规HTTP流量或执行其他任务。在这些配置中,您需要代理服务器(例如Nginx)在群集中分配传入的请求。由于Erlang VM是多线程的,因此Erlang节点应对应于物理计算机。在可能的情况下,在一台机器上运行多个节点会浪费资源。

Incoming HTTP requests are first parsed by Mochiweb or Misultin (depending on your server configuration). If parsing succeeds, a SimpleBridge request object is handed to the CB server and Boss gets to work.

传入的HTTP请求首先由Mochiweb或Misultin解析(取决于您的服务器配置)。如果解析成功,则将SimpleBridge请求对象移交给CB服务器,然后Boss开始工作。

First, BossRouter parses the requested URL and figures out which application should process the request. The URL is then mapped to a controller and an action. A controller consists of a set of actions, which might have names like “create”, “edit”, and “delete”; each action corresponds to a function with the same name in the controller module.

首先,BossRouter解析请求的URL,并找出哪个应用程序应处理该请求。然后将URL映射到控制器和操作。控制器由一组动作组成,这些动作可能具有“创建”,“编辑”和“删除”之类的名称。每个动作对应于控制器模块中具有相同名称的功能。

But before any action is invoked, Boss checks to see if authorization is required by executing a special authorization function in the requested controller. The authorization function has full access to the database and session information, and can perform a redirect if authorization fails (for example, to a log-in page).

但是在调用任何动作之前,Boss通过在请求的控制器中执行特殊的授权功能来检查是否需要授权。授权功能具有对数据库和会话信息的完全访问权限,并且可以在授权失败时执行重定向(例如,到登录页面)。

If authorization succeeds, the action is invoked; it is passed the HTTP method (such as GET or POST), a list of tokens in the URL, and (optionally) the return value from the authorization function. Because controllers are parameterized modules, controller actions also have the SimpleBridge request object and the current session ID available in scope as module parameters.

如果授权成功,则调用该操作;否则,将执行该操作。它将传递HTTP方法(例如GET或POST),URL中的令牌列表以及(可选)授权函数的返回值。由于控制器是参数化的模块,因此控制器操作还具有SimpleBridge请求对象和作用域中的当前会话ID作为模块参数。

The controller action might do any of a number of things to fulfill a request, such as pull information from the database, wait on a message from the message queue, or send an email. The return value from the action determines how the CB server will respond to the client. In the normal case, a data structure for populating an HTML template will be returned, but the return value can also direct the CB server to generate JSON, redirect the request to another URL, return an error, or perform other actions.

控制器操作可能会执行许多操作来满足请求,例如从数据库中提取信息,等待消息队列中的消息或发送电子邮件。该操作的返回值确定CB服务器如何响应客户端。在正常情况下,将返回用于填充HTML模板的数据结构,但是该返回值还可以指示CB服务器生成JSON,将请求重定向到另一个URL,返回错误或执行其他操作。

After the controller action has returned, the next stage of processing is to generate a response. Normally, the response will be generated from an ErlyDTL template with the same name as the controller action. (ErlyDTL is a very fast, pure-Erlang implementation of the Django Template language.) Because templates have access to the data model, additional database fetches might be performed in the HTML generation process. As a result, there is no need for the controller to pre-fetch all of a record’s associations. For example, the controller might simply retrieve a “blog post” record, and the template can fetch for itself additional information about the blog post’s author’s employer’s second cousin’s daughter’s cat, or whatever.

控制器动作返回后,下一步处理是生成响应。通常,响应将从与控制器动作同名的ErlyDTL模板生成。 (ErlyDTL是Django模板语言的非常快速的纯Erlang实现。)由于模板可以访问数据模型,因此可能会在HTML生成过程中执行其他数据库提取。因此,控制器无需预先提取记录的所有关联。例如,控制器可以简单地检索“博客文章”记录,并且模板可以自己获取有关博客文章作者的雇主的第二堂兄的女儿猫的其他信息。

But the generated response is not returned to the client immediately; controllers can define a special post-processing function to transform (or cache, or log) the output before sending it on to the client. This postprocessing phase might be used to add a timestamp or perform compression on the output.

但是生成的响应不会立即返回给客户端。控制器可以定义特殊的后处理功能,以在将输出发送到客户端之前转换(或缓存或记录)输出。此后处理阶段可用于添加时间戳或对输出执行压缩。

After post-processing, the client finally receives the response, and with any luck will make another request.

经过后期处理后,客户端最终会收到响应,如果运气好的话,还会提出另一个请求。

The workflow for handling incoming email is similar, but less involved. There is only one email controller per application, and return values are ignored. There is also no routing file for email; the recipient name is always mapped to the name of a controller function, so sending mail to “post@domain.com” will invoke the “post” function. The controller function is passed the sender’s email address and a parsed version of the email body, and can do with them as it pleases (probably stick a version of it into the database). If security is an issue, an authorization function can authorize the sender’s email address and IP address before receiving the email and invoking the handler function.

处理传入电子邮件的工作流程类似,但是涉及的较少。每个应用程序只有一个电子邮件控制器,返回值将被忽略。也没有电子邮件的路由文件。收件人名称始终映射到控制器功能的名称,因此将邮件发送到“ post@domain.com”将调用“ post”功能。控制器功能将传递给发件人的电子邮件地址和已解析的电子邮件正文,并可以根据需要进行处理(可能将其版本粘贴到数据库中)。如果安全性成问题,授权功能可以在接收电子邮件并调用处理程序功能之前授权发件人的电子邮件地址和IP地址。

But enough about architecture; let’s get on to the building.

但是关于架构已经足够了;让我们进入建筑物。

# 3. Getting Started 入门

To get going with Chicago Boss web development, you will need a machine that has Erlang installed, version R13A or later. The machine should also have a terminal, a web browser, and an editor that makes you feel warm and mushy.

要开始Chicago Boss网站开发,您将需要一台装有Erlang版本R13A或更高版本的机器。该机器还应具有一个终端,一个Web浏览器和一个使您感到温暖和糊状的编辑器。

# 1. Hello, world 你好,世界

Download the Chicago Boss 0.7.0 source code from here:

从此处下载Chicago Boss 0.7.0源代码:

http://www.chicagoboss.org/ (opens new window)

The first task is to open the archive, compile the code, and create a new project.

第一个任务是打开存档,编译代码并创建一个新项目。

tar xzf ChicagoBoss-0.7.0.tar.gz
cd ChicagoBoss-0.7.0
make
make app PROJECT=cb_tutorial
1
2
3
4

cb_tutorial will be the name of the new project. You can name it something else, but the project name must start with a lowercase letter, and contain only lowercase letters, digits, and underscores.

cb_tutorial将是新项目的名称。您可以使用其他名称,但项目名称必须以小写字母开头,并且只能包含小写字母,数字和下划线。

The second task is to enter the new project directory.

第二项任务是进入新的项目目录。

cd ../cb_tutorial
1

Go ahead, take a look around (I'll wait). Some items of interest that you'll see:

来吧,四处看看(我等)。您会看到一些感兴趣的项目:

boss.config - The configuration file, where you can do useful things like connect to a real database, set up caching, or define the port to listen on. For this tutorial we'll be using a fake in-memory database, listening on port 8001, and not caching a darn thing.

boss.config-配置文件,您可以在其中做一些有用的事情,例如连接到真实数据库,设置缓存或定义要侦听的端口。在本教程中,我们将使用伪造的内存数据库,在端口8001上侦听,而不缓存高速缓存的东西。

rebar, rebar.config - The build tool and its configuration file. You won’t need to touch this directly.

rebar,rebar.config-构建工具及其配置文件。您无需直接触摸它。

cb_tutorial.app.src - This is processed to produce a .app file, which helps the OTP fairies manage dependencies and upgrade your application in production. Edit the file to give your project an accurate description and a version number other than 0.0.1.

cb_tutorial.app.src-处理该文件以生成.app文件,该文件可帮助OTP精灵管理依赖关系并在生产中升级您的应用程序。编辑文件,为您的项目提供准确的描述和0.0.1以外的版本号。

ebin/ - The home of compiled modules as well as the .app file. It is not referenced in development, but before starting the production server you need to populate it with make. You can always clear out this directory with make clean.

ebin /-编译模块的主目录以及.app文件。开发中没有引用它,但是在启动生产服务器之前,需要使用make填充它。您始终可以使用make clean清除此目录。

init-dev.sh - Run this to start a development server, which automatically recompiles code for you, pretty-prints error messages in the browser, and includes an interactive console. You can terminate the development server by typing q().

init-dev.sh-运行此命令以启动开发服务器,该服务器将自动为您重新编译代码,在浏览器中漂亮地打印错误消息,并包括一个交互式控制台。您可以通过键入q()终止开发服务器。

priv/ - A directory of junk that doesn't belong anywhere else. The name “priv” makes it sound somehow elite or exclusive, but it's just init scripts, language files, routing tables, static images, CSS, and related bric-a-brac.

priv /-不属于任何其他地方的垃圾目录。名称“ priv”使它听起来有点精英或独占,但它只是初始化脚本,语言文件,路由表,静态图像,CSS和相关的bric-a-brac。

src/ - The future home of your source code. Peek inside! You'll see:

controller/ - controllers. It's empty now, but it's the first place we'll go when we want to start writing the application.

model/ - models for the ORM. All files in here are compiled with a special model compiler, so don't put any library modules in here.

lib/ - where the library modules go. In development these are potentially recompiled with every request, so if you're using a large 3rd party                   library it's best to keep it outside the project directory and include the path in your server start scripts.

view/ - templates. You’ll need to create a subdirectory for each controller. Don't miss view/lib/, where you can put reusable template                                  components and code for custom tags and filters.

src /-您的源代码的未来归宿。偷看里面!您会看到:

controller/-控制器。现在是空的,但是它是我们要开始编写应用程序时要去的第一个地方。

model/-ORM的模型。此处的所有文件均使用特殊的模型编译器进行编译,因此请勿在此处放置任何库模块。

lib/-库模块所在的位置。在开发中,它们可能会与每个请求一起重新编译,因此,如果您使用大型的第三方库,最好将其保留在项目目录之外,并将路径包            含在服务器启动脚本中。

view/-模板。您需要为每个控制器创建一个子目录。不要错过view/lib/,您可以在其中放置可重用的模板组件以及用于自定义标签和过滤器的代码。

log/ - Error logs galore. CB will maintain a symlink (log/boss_errorLATEST.log) that always points to the most recent log file.

log/-错误记录丰富。 CB将维护一个始终指向最新日志文件的符号链接(log/boss_errorLATEST.log)

include/ - Header files. Often they provide record definitions for library modules found in src/lib/, but you might put application-wide macros and definitions here.

include/ -头文件。通常,它们为src/lib/中的库模块提供记录定义,但是您可以在此处放置应用程序范围的宏和定义。

All right, enough looking around, let's start the development server.

好了,环顾四周,让我们启动开发服务器。

./init-dev.sh
1

You will see many messages fly by the screen, such as this one:

您将看到许多消息在屏幕上飞舞,例如以下消息:

=PROGRESS REPORT==== 27-Nov-2011::18:54:17 ===
  supervisor: {local,sasl_safe_sup}
  started: [{pid,<0.40.0>},
  {name,alarm_handler},
  {mfargs,{alarm_handler,start_link,[]}},
  {restart_type,permanent},
  {shutdown,2000},
  {child_type,worker}]
1
2
3
4
5
6
7
8

To this day I have no idea what that means, but I am assured by various credible sources that it is harmless. Anyway, below all the messages, you will see an Erlang shell prompt that looks like this:

直到今天,我仍然不知道这意味着什么,但是各种可靠的消息来源都向我保证,这是无害的。无论如何,在所有消息下方,您将看到一个如下所示的Erlang Shell提示符:

(cb_tutorial@blackstone)1>
1

This is the server’s shell. cb_tutorial, besides being the name of our project, is also the name of the development node. blackstone is the name of my computer; on your computer you will see something else after the @ sign.

这是服务器的外壳。 cb_tutorial除了是我们项目的名称之外,还是开发节点的名称。blackstone是我的计算机的名称;在您的计算机上,@符号后会看到其他内容。

You can type Erlang statements directly into the server shell; here you have complete access to the server state and the loaded code libraries. Just remember to end any statements with a period. You can always halt the server with the q(). command.

您可以直接在服务器外壳中键入Erlang语句。在这里,您可以完全访问服务器状态和已加载的代码库。只要记住以句号结束任何语句即可。您始终可以使用q()暂停服务器。命令。

We are now ready to write the obligatory “Hello, world!” application. In the project directory, open a file (in another terminal, or with a desktop text editor) called src/controller/cb_tutorial_greeting_controller.erl. This file will be the greeting controller of the new cb_tutorial application. (The names of controller modules always take the form __controller.)

现在,我们准备编写强制性的“ Hello,world!”应用程序。在项目目录中,打开一个名为src/controller/cb_tutorial_greeting_controller.erl的文件(在另一个终端中,或使用桌面文本编辑器)。该文件将成为新cb_tutorial应用程序的问候语控制器。 (控制器模块的名称始终采用<应用程序名称> _ <控制器名称> _controller的形式。)

To get started, type (or paste) this into it:

首先,将其键入(或粘贴)到其中:

-module(cb_tutorial_greeting_controller, [Req]).
-compile(export_all).
hello('GET', []) ->
 {output, "Hello, world!"}.
1
2
3
4

Now point your web browser to http://localhost:8001/greeting/hello (opens new window). You should see a familiar message.

现在,将您的Web浏览器指向http://localhost:8001/greeting/hello (opens new window)您应该会看到一条熟悉的消息。

We’ve written a short controller with one action, called “hello”. You can create other actions in the same controller. Each action will have its own URL of the form //. If the URL contains additional slash-separated tokens beyond the action name, these will be passed as a list to the controller action in the second argument. The first argument, you might have guessed, is an atom specifying the HTTP method that initiated the request: 'GET', 'POST', 'PUT', or 'DELETE'.

我们用一个动作编写了一个简短的控制器,称为“ hello”。您可以在同一控制器中创建其他动作。每个动作将具有其自己的URL,格式为/<控制器名称>/<动作名称>。如果URL除了操作名称之外还包含其他用斜杠分隔的标记,则这些标记将作为列表传递给第二个参数中的控制器操作。您可能已经猜到,第一个参数是一个原子,它指定发起请求的HTTP方法:“ GET”,“ POST”,“ PUT”或“ DELETE”。

This controller module is a parameterized module, as indicated by the parameter list ([Req]) in the -module directive. Although Erlang is a functional language, parameterized modules add a little OO sauce so that we don't have to pass the same values all the time to all the functions of a particular module. In the case of CB controllers, every function will have access to the Req variable, which has a lot of useful information about the current request. We'll learn more about parameterized modules later.

该控制器模块是参数化模块,如-module指令中的参数列表([Req])所示。尽管Erlang是一种函数式语言,但是参数化的模块会增加一些面向对象,因此我们不必始终将相同的值传递给特定模块的所有功能。对于CB控制器,每个函数都可以访问Req变量,该变量具有有关当前请求的许多有用信息。稍后我们将学习有关参数化模块的更多信息。

Controller actions can return several values. The simplest is {output, Value}, and it returns raw HTML. We can also use {json, Values} to return JSON:

控制器动作可以返回多个值。最简单的是{output,Value},它返回原始HTML。我们还可以使用{json,Values}返回JSON:

hello('GET', []) ->
 {json, [{greeting, "Hello, world!"}]}.
1
2

I've changed the string to a proplist, which is just a list of key-value tuples. Proplists are ubiquitous in Erlang programming, so I hope you like them. The json return value tells CB to transform the given proplist into honest-togoodness JSON for consumption by the client. Refresh your browser to see the result.

我已将字符串更改为属性列表,该属性列表只是键值元组的列表。在Erlang编程中,无处不在的专家,所以希望您喜欢他们。 json返回值告诉CB将给定的属性列表转换为诚实至善的JSON,以供客户端使用。刷新浏览器以查看结果。

The json return value is convenient for creating JSON APIs with Chicago Boss. Of course, if we wish to generate HTML, it will be convenient to use a template.

json返回值便于使用Chicago Boss创建JSON API。当然,如果我们希望生成HTML,则使用模板会很方便。

To use templates, we will need to change the controller code, and to create an actual template file. First, alter the controller file and replace json with ok:

要使用模板,我们将需要更改控制器代码,并创建一个实际的模板文件。首先,更改控制器文件并将json替换为ok:

hello('GET', []) ->
 {ok, [{greeting, "Hello, world!"}]}. 
1
2

The variable list will now be passed to the associated template, if there is one. There's not one, so we need to create it. Open a new file src/view/greeting/ hello.html (you may need to create a greeting subdirectory), and type or paste this into it:

变量列表现在将传递到关联的模板(如果有的话)。没有一个,所以我们需要创建它。打开一个新文件src/view/greeting/ hello.html(您可能需要创建一个问候子目录),然后输入或粘贴到其中:

<b>{{ greeting }}</b>
1

That is a snippet of Django template language, which has become something of a lingua franca in Erlang web development. The language is simple, and it is easy for non-Erlang programmers to understand and write. Anyway, refresh the browser to see the greeting in bold.

那是Django模板语言的一小段,它已成为Erlang Web开发中的通用语言。该语言很简单,并且对于非Erlang程序员来说也很容易理解和编写。无论如何,请刷新浏览器以粗体显示问候语。

To see an emphatic greeting, try this:

要看到强调的问候,请尝试以下操作:

<b>{{ greeting|upper }}</b>
1

Here we've used a filter, whose function should become obvious once you refresh the browser. Filters offer a cornucopia of formatting functionality, and they can be chained together in the same way that UNIX commands are chained: simply insert a pipe character ("|") between each filter name. Some other filters to try on your own time are length, slugify, title, and wordcount.

在这里,我们使用了过滤器,刷新浏览器后,其功能将变得显而易见。过滤器提供了格式化功能的聚宝盆,并且可以使用与链接UNIX命令相同的方式将它们链接在一起:只需在每个过滤器名称之间插入一个竖线字符(“ |”)。您可以根据自己的时间尝试其他一些过滤器,例如长度,字形,标题和字数统计。

To summarize, we have seen that controllers can return three types of tuples:

总而言之,我们已经看到控制器可以返回三种类型的元组:

- {output, Value} - Send Value as raw HTML                                        -将值作为原始HTML发送

- {json, Values} - Format Values (a proplist) as JSON                          -将值(属性列表)设置为JSON格式

- {ok, Values} - Pass Values (a proplist) to the associated template   -将值(属性列表)传递给关联的模板

Well, that was fun. We have said hello to the world, but what shall we do when the world says hello to us? Most web applications will need to store or retrieve data. Having touched on the View and Controller, we now visit the Model.

好吧,那很有趣。我们已经向世界打了招呼,但是当世界向我们打招呼时该怎么办?大多数Web应用程序将需要存储或检索数据。触摸视图和控制器后,我们现在访问模型。

# 2. Hello, database 您好,数据库

For dealing with databases, Chicago Boss includes a special querying syntax, a full ORM, and a handy set of database adapters. To get started, we'll create a simple model file and see what we can do with it. Open a new file called src/ model/greeting.erl, and type or paste this into it:

为了处理数据库,Chicago Boss包括特殊的查询语法,完整的ORM和一组方便的数据库适配器。首先,我们将创建一个简单的模型文件,并查看如何处理。打开一个名为src/model/greeting.erl的新文件,并将其键入或粘贴到其中:

-module(greeting, [Id, GreetingText]).
-compile(export_all).
1
2

This is the model for “greeting” objects. It is really a parameterized module with two parameters (Id and GreetingText). All models must have Id as their first parameter, but subsequent parameters can be whatever you like.

这是“greeting”对象的模型。它实际上是一个带有两个参数(Id和GreetingText)的参数化模块。所有模型都必须将Id作为其第一个参数,但是后续参数可以是您喜欢的任何参数。

This looks like an ordinary Erlang module, but it is harboring a number of secret superpowers. It is time for a little detour into the hidden wonders of the Chicago Boss ORM, called BossRecord.

这看起来像一个普通的Erlang模块,但其中包含许多秘密的超级大国。现在是时候绕开Chicago Boss ORM(称为BossRecord)的隐藏奇迹了。

Refresh the browser (so that this model file is properly compiled and loaded), then try this in the server shell:

刷新浏览器(以便正确编译和加载此模型文件),然后在服务器外壳中尝试以下操作:

> Greeting = greeting:new(id, "Hello, world!").
{greeting,id,"Hello, world!"}
1
2

The new function just returns a new instance of greeting. I passed in id as the first argument because that tells BossDB to generate a new ID number when the time comes; if I wanted a particular ID, such one that included my basketball jersey number, I could have specified it.

新函数仅返回问候的新实例。我传入id作为第一个参数,因为它告诉BossDB在时间到来时生成一个新的ID号。如果我想要一个特定的ID,例如包含我的篮球球衣号码的ID,我可以指定它。

As you can see above, the greeting instance is really just a tuple that includes the module name followed by all of the passed-in parameters. When you call a function of a parameterized module (other than new), the run-time system just binds values from that tuple to the module's parameter list before executing the function. Even though it sort-of looks like object-oriented programming, passing around the greeting instance really is just passing around all of its parameter values. Like other Erlang variables, the values are immutable, so we don't need to worry about locks, side effects, or other perils of object orientation.

正如您在上面看到的,greeting实例实际上只是一个元组,其中包含模块名称和所有传入的参数。当您调用参数化模块的功能(新功能除外)时,运行系统只会在执行功能之前将该元组中的值绑定到模块的参数列表。即使它看起来像是面向对象的编程,但传递问候实例实际上只是传递其所有参数值。与其他Erlang变量一样,这些值是不可变的,因此我们不必担心锁定,副作用或其他面向对象的危险。

It's time to get to know the model. Try this:

现在是时候了解模型了。尝试这个:

> Greeting:greeting_text().
"Hello, world!"
1
2

"But wait just a gosh-darn minute," you might be thinking, "I don't remember putting a greeting_text/0 function in my greeting module! In fact, I don't remember putting any functions in there. Am I getting Alzheimer's? Where am I?"

“但请稍等片刻,”您可能会想,“我不记得在我的Greeting模块中放置了greeting_text / 0函数!实际上,我不记得在其中放置任何函数。阿兹海默氏症?我在哪里?”

Don't worry, you are not losing your mind (and you are sitting at your computer). Before loading model modules into the system, the Chicago Boss compiler surreptitiously attaches extra functions to them. Here are a couple of others to try. I'll let you figure out what they do.

不用担心,您不会失去理智(而您正坐在计算机旁)。在将模型模块加载到系统中之前,Chicago Boss编译器会秘密地向其附加额外的功能。这里有一些其他的尝试。我会让你弄清楚他们的所作所为。

> Greeting:attributes().
[{id,id},{greeting_text,"Hello, world!"}]
1
2
> Greeting:attribute_names().
[id,greeting_text]
1
2

This one is useful too, but it might not behave exactly as you expect:

这个也很有用,但它的行为可能与您预期的不完全相同:

> Greeting:set(greeting_text, "Good-bye, world!").
{greeting,id,"Good-bye, world!"}
1
2

Erlang variables are immutable, so calling set/2 returns a new record without altering the old one in any way. Try this to see what I mean:

Erlang变量是不可变的,因此调用set/ 2会返回一条新记录,而不会以任何方式更改旧记录。试试看我的意思:

> Greeting.
{greeting,id,"Hello, world!"}
1
2

See? The Greeting variable remains alive and full of hope.

看到? Greeting变量仍然有效并且充满希望。

These generated functions come in mighty handy in day-to-day Chicago Boss programming. Without them, you would need to call proplists:get_value/2, proplists:delete/2, and similar verbosities. But the most important function of them all, the function without which all the other functions would be useless and nothing except error messages would ever persist to disk, is this:

这些生成的函数在日常的芝加哥Boss编程中非常有用。没有它们,您将需要调用proplists:get_value/2,proplists:delete/2和类似的详细信息。但是,所有这些功能中最重要的功能是:如果没有该功能,所有其他功能将无用,并且除了错误消息外,其他任何功能都不会持久存在于磁盘上:

> Greeting:save().
{ok,{greeting,"greeting-1","Hello, world!"}}
1
2

Try it. Congratulations! You just saved your first record to the database, and the id atom was replaced with a real-live identification string. Note that generated IDs in Chicago Boss take the form "model-number" (here it is "greeting-1"). By including the model name in the ID, Chicago Boss can guarantee that the IDs are unique across all of the different models. (This simple fact greatly simplifies the API design.)

试试吧。恭喜你!您刚刚将第一条记录保存到数据库中,而id原子被替换为真实的标识字符串。请注意,Chicago Boss中生成的ID的格式为“型号”(此处为“ greeting-1”)。通过在ID中包含模型名称,Chicago Boss可以确保ID在所有不同模型中都是唯一的。 (这个简单的事实大大简化了API设计。)

You might be wondering how you are going to remember all of these useful functions that are clandestinely attached to each BossRecord. Can you keep a secret? This is a secret you shouldn't even tell your spouse (particularly if he or she is a Rails programmer). Open up a browser and visit http://localhost:8001/ doc/greeting. Quick, close it before anyone sees. You just caught a glimpse of the Chicago Boss's automatic EDoc, which will tell you about all the functions generated for each model. In the Chicago Boss community we call this feature "/doc", for reasons you are left to ponder in private.

您可能想知道如何记住所有秘密地附加到每个BossRecord的所有这些有用功能。你能保守秘密吗?这是一个秘密,您甚至不应该告诉您的配偶(特别是如果他或她是Rails程序员)。打开浏览器,然后访问http://localhost:8001/doc/greeting (opens new window)。快速,在任何人看到之前将其关闭。您只是瞥了一眼芝加哥Boss的自动EDoc,它会告诉您为每种模型生成的所有功能。在芝加哥Boss社区中,我们将此功能称为“ /doc”,原因是您不得不私下考虑。

Anyway, back to the shell. I've been avoiding mentioning this, but we have a slight problem. After saving the greeting, the Greeting variable is still bound to the unsaved version:

无论如何,回到外壳。我一直在避免提及这一点,但是我们有一个小问题。保存问候语后,Greeting变量仍绑定到未保存的版本:

> Greeting.
{greeting,id,"Hello, world!"}
1
2

Oh dear. It would seem that the saved record is "lost" because we didn't bind anything to the return value of save/0. What is a budding CB programmer to do?

噢亲爱的。似乎已保存的记录是“丢失”的,因为我们没有将任何内容绑定到 save/0 的返回值。崭露头角的CB程序员该做什么?

Not to worry -- it's time to turn to BossRecord's best friend since childhood, the API with an attitude, Mr. BossDB.

不用担心-是时候转向BossRecord的自幼以来最好的朋友了,BossDB先生以一种态度对待API。

BossDB is a library for querying the database. It is unusual in that it will accept a language-integrated syntax that is illegal outside of Chicago Boss projects (in fact, it is illegal in the Chicago Boss server shell). For now we will use the more verbose syntax that will not cause us any trouble with the Erlang interpreter.

BossDB是用于查询数据库的库。这是不寻常的,因为它将接受在Chicago Boss项目之外是非法的语言集成语法(实际上,在Chicago Boss服务器外壳中是非法的)。现在,我们将使用更详细的语法,这不会给我们使用Erlang解释器带来任何麻烦。

To find the record that we saved, it is best to cast a wide net. We use boss_db:find/2.

要找到我们保存的记录,最好进行广泛的筛选。我们使用 boss_db:find/2 。

> boss_db:find(greeting, []).
[{greeting,"greeting-1","Hello, world!"}]
1
2

Whew! The saved record hasn't gone anywhere. We can retrieve it anytime.

ew!保存的记录没有任何地方。我们可以随时检索它。

boss_db:find/2 is the simplest way to query the database; it takes a model name and a list of conditions and returns a list of results. We didn't want to impose any search conditions, so the list here is empty.

boss_db:find/2是查询数据库的最简单方法;它使用一个模型名称和一个条件列表,并返回结果列表。我们不想强加任何搜索条件,因此此处的列表为空。

But try a few search conditions, just for fun:

但是,尝试一些搜索条件只是为了好玩:

> boss_db:find(greeting, [{id, 'equals', "greeting-1"}]).
> boss_db:find(greeting, [{greeting_text, 'matches', "^Hell"}]).
> boss_db:find(greeting, [{greeting_text, 'gt', "Hellespont"}]).
> boss_db:find(greeting, [{greeting_text, 'lt', "Hellespont"}]).
1
2
3
4

See if you can figure out what each does. There are many more query operators available besides equals, matches, gt, and lt, some 18 in total. You will want to spend some time with the API documents to learn more about them.

看看是否可以弄清楚每个功能。除了equals,matchs,gt和lt之外,还有更多查询运算符可用,总共约18个。您将需要一些时间来使用API​​文档,以了解有关它们的更多信息。

Of course, if we know the record ID, we don't need to mess with search conditions and query operators at all. We can use the very handy boss_db:find/1, like this:

当然,如果我们知道记录ID,则完全不需要弄乱搜索条件和查询运算符。我们可以使用非常方便的 boss_db:find/1,如下所示:

> boss_db:find("greeting-1").
{greeting,"greeting-1","Hello, world!"}
1
2

The function returns either a record with the provided ID, or the atom undefined. Note that we didn't have to formally specify the name of the model, since it is embedded in the ID string.

该函数返回具有提供的ID的记录或未定义的原子。请注意,由于模型名称嵌入在ID字符串中,因此我们不必正式指定模型名称。

Before we forget that we're working on a web application, let's get back to the source code and figure out how to take advantage of BossRecord and BossDB on our website.

在我们忘记使用Web应用程序之前,让我们回到源代码,并弄清楚如何在我们的网站上利用BossRecord和BossDB。

# 3. A database-driven website 一个数据库驱动的网站

Open up a new template src/view/greeting/list.html. We'll use it to display all the greetings in the database. (By the way, if you use Vim as your text editor, use :setf htmldjango to get proper syntax highlighting.)

打开一个新模板src/view/greeting/list.html。我们将使用它来显示数据库中的所有问候语。 (顺便说一句,如果您使用Vim作为文本编辑器,请使用:setf htmldjango获得正确的语法突出显示。)

<html>
<head>
 <title>{% block title %}Greetings!{% endblock %}</title>
</head>
<body>
{% block body %}
<ul>
{% if greetings %}
 {% for greeting in greetings %}
 <li>{{ greeting.greeting_text }}
 {% endfor %}
{% else %}
 <li>No greetings!
{% endif %}
</ul>
{% endblock %}
</body></html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

In the code above, we've used a number of Django template tags that may be unfamiliar to you, but they should be easy to figure out. The {% if %} tag tests to see if a variable is defined and not false. The {% for %} tag iterates over a list, and the {% block %} tag is used in template inheritance; we'll explore template inheritance shortly. Anyway, with this template in place, point your browser to http://localhost:8001/greeting/list (opens new window) to see the greeting you created in the console formatted in beautiful HTML.

在上面的代码中,我们使用了许多您可能不熟悉的Django模板标签,但是应该很容易弄清楚。 {%if%}标签会测试以查看变量是否已定义且不是false。 {%for%}标签遍历列表,而{%block%}标签用于模板继承;我们将在短期内探讨模板继承。无论如何,有了此模板,将浏览器指向http://localhost:8001/greeting/list (opens new window)即可查看以精美HTML格式在控制台中创建的问候语。

Gotcha! There’s nothing there. We need to create a controller action to retrieve the greetings from the database. Open the controller (src/controller/ cb_tutorial_greeting_controller.erl) and add a list action:

知道了!那里什么都没有。我们需要创建一个控制器动作来从数据库中检索问候语。打开控制器(src/controller/cb_tutorial_greeting_controller.erl)并添加列表操作:

list('GET', []) ->
 Greetings = boss_db:find(greeting, []),
 {ok, [{greetings, Greetings}]}.
1
2
3

By now this controller code should be comprehensible; the first line queries the database for all greetings, and the second sends them all to the template. Refresh the browser, and you’ll see your HTML-formatted greeting at long last.

到目前为止,该控制器代码应该是可理解的;第一行查询数据库中的所有问候语,第二行将所有问候语发送至模板。刷新浏览器,您将很快看到HTML格式的问候语。

Next we will create a form for submitting new greetings. Add this to the bottom of the body block of src/view/greeting/list.html:

接下来,我们将创建一个用于提交新问候的表格。将其添加到src/view/greeting/list.html的body块的底部:

<p><a href="{% url action="create" %}">New greeting...</a></p>
1

Here we've used a new tag, the url tag. It takes a key="value" argument list for constructing a URL, and must always have an action key.

在这里,我们使用了一个新标签url标签。它使用用于构造URL的key =“ value”参数列表,并且必须始终具有操作键。

Then create a new template, src/view/greeting/create.html:

然后创建一个新模板,src/view/greeting/create.html:

{% extends "greeting/list.html" %}
{% block title %}A new greeting!{% endblock %}
{% block body %}
    <form method="post">
       Enter a new greeting:
      <textarea name="greeting_text"></textarea>
      <input type="submit">
    </form>
{% endblock %}
1
2
3
4
5
6
7
8
9

Using the {% extends %} tag, this template inherits from the list.html template we created earlier. With it, the create.html template inserts its own content into the title and body blocks defined in list.html; in this way, we don't need to redefine the HTML,, andtags in every template. As you might imagine, template inheritance is an essential strategy when creating and maintaining anything but a one-page website.

使用{%extended%}标签,此模板继承自我们先前创建的list.html模板。有了它,create.html模板将自己的内容插入到list.html中定义的标题和正文块中。这样,我们无需在每个模板中重新定义HTML <head>,<title>和<body>标记。您可能会想到,模板继承是创建和维护除一页网站之外的任何其他内容时的基本策略。

Anyway, refresh the browser, and click the link to make sure that the form we created is there. Even though we won't be winning any design awards, we need to process the form, so add a create action to the controller:

无论如何,刷新浏览器,然后单击链接以确保存在我们创建的表单。即使我们不会获得任何设计大奖,我们也需要处理表单,因此请向控制器添加一个create动作:

create('GET', []) ->
 ok;
create('POST', []) ->
 GreetingText = Req:post_param("greeting_text"),
 NewGreeting = greeting:new(id, GreetingText),
 {ok, SavedGreeting} = NewGreeting:save(),
 {redirect, [{action, "list"}]}.
1
2
3
4
5
6
7

The first clause handles GET requests, and simply renders the template without an variables.

第一个子句处理GET请求,并仅呈现不带变量的模板。

The second clause handles POST requests. It first pulls the "greeting_text" parameter from the form using Req:post_param/1. Recall that Req is a parameter of the controller module (in fact, the only parameter). The action then creates a new greeting with the greeting text and saves it to the database. Finally, the function returns a {redirect, Location} directive, which we have not encountered before. This directive performs an HTTP 302 redirect to the specified location, which can be either a URL string or a proplist with an action key (just like the {% url %} tag in the template). In this example, we redirect to the list action.

第二个子句处理POST请求。首先使用Req:post_param/1从表单中提取“ greeting_text”参数。回想一下,Req是控制器模块的参数(实际上是唯一的参数)。然后,该操作将使用问候语文本创建一个新的问候语,并将其保存到数据库中。最后,该函数返回一个{redirect,Location}指令,这是我们之前从未遇到过的。此伪指令执行HTTP 302重定向到指定的位置,该位置可以是URL字符串或带有操作键的属性列表(就像模板中的{%url%}标签一样)。在此示例中,我们重定向到列表操作。

We now have a database-driven website. Submit a greeting or two, and marvel at the power of the Internet.

我们现在有一个数据库驱动的网站。提交一两个问候,并惊叹于Internet的强大功能。

It is likely that our application will occasionally display a greeting that does not have any merit, so next we will add a delete button to our application.

我们的应用程序偶尔可能会显示没有任何价值的问候语,因此接下来我们将向我们的应用程序添加一个删除按钮。

Add the following form to the bottom of the body block of the list template (src/view/greeting/list.html):

将以下形式添加到列表模板的body 的底部(src/view/greeting/list.html):

<form method="post" action="{% url action="goodbye" %}">
  Delete:
  <select name="greeting_id">
    {% for greeting in greetings %}
      <option value="{{ greeting.id }}">{{ greeting.greeting_text }}
    {% endfor %}
  </select>
  <input type="submit">
</form>
1
2
3
4
5
6
7
8
9

There should not be any surprises in this code snippet. Next, we need to process the form so that it works. Add the following code to the bottom of the controller (src/controller/cb_tutorial_greeting_controller.erl):

此代码段不应有任何意外。接下来,我们需要处理表单以使其起作用。将以下代码添加到控制器的底部(src/controller/cb_tutorial_greeting_controller.erl):

goodbye('POST', []) ->
 boss_db:delete(Req:post_param("greeting_id")),
 {redirect, [{action, "list"}]}.
1
2
3

The function boss_db:delete/1 takes a record ID and deletes the associated record from the database, just like that. Use it with caution!

就像这样,函数boss_db:delete / 1获取记录ID并从数据库中删除关联的记录。请谨慎使用!

With that we have a delete feature. Now you have a form for getting rid of some of the less interesting greetings that you created before.

这样,我们就有了删除功能。现在,您有了一个表单,可以摆脱以前创建的一些不太有趣的问候。

Of course, we are far from finished. Currently, anyone can store any greeting, no matter how long or how short. Next we will add validation code to the greeting model, and return an appropriate error message to the user if validation fails.

当然,我们还远远没有完成。目前,任何人都可以存储任何问候,无论多长或多短。接下来,我们将验证代码添加到问候模型中,如果验证失败,则向用户返回适当的错误消息。

# 4. Simple validation  简单验证

To ensure that greetings are non-empty and tweetable, add the following code to the greeting model (src/model/greeting.erl):

为确保问候语不能为空并且可以在tweetable中使用,请将以下代码添加到问候语模型(src/model/greeting.erl)中:

validation_tests() ->
  [
    {fun() -> length(GreetingText) > 0 end,
      "Greeting must be non-empty!"},
    {fun() -> length(GreetingText) =< 140 end,
      "Greeting must be tweetable"}
  ].
1
2
3
4
5
6
7

validation_tests/0 is an optional function that should return a list of {TestFun, FailureMessage} tuples. If any of the functions returns false, the pending save operation is aborted, and the call to save/0 returns {error, FailureMessageList}.

validation_tests/ 0是一个可选函数,应返回{TestFun,FailureMessage}元组的列表。如果任何函数返回false,则挂起的保存操作将中止,对save/0的调用将返回{error,FailureMessageList}。

Try submitting an empty greeting. You should see something like this:

尝试提交一个空的问候。您应该会看到以下内容:

Error:
{{{{cb_tutorial_greeting_controller,['Req']},create,2},
 {line,{17,25}},
 {match,{error,["Greeting must be non-empty!"]}}},
 [{cb_tutorial_greeting_controller,create,3},
 {boss_web_controller,execute_action,5},
 {boss_web_controller,process_request,5},
 {timer,tc,3},
 {boss_web_controller,handle_request,3},
 {mochiweb_http,headers,5},
 {proc_lib,init_p_do_apply,3}]}
1
2
3
4
5
6
7
8
9
10
11

Following the error message, look at line 17 of the controller (src/ controller/cb_tutorial_greeting_controller.erl). That line is:

在错误消息之后,查看控制器的第17行(src/controller/cb_tutorial_greeting_controller.erl)。该行是:

{ok, SavedGreeting} = NewGreeting:save(),
1

As indicated by the error message, we are getting a match error. The function is returning {error,["Greeting must be non-empty!"]}, but we are trying to match it to {ok, SavedGreeting}, so the process crashes (in the harmless, Erlang sense of the word “crash,” of course).

如错误消息所示,我们收到匹配错误。该函数返回{error,["Greeting must be non-empty!"]},但是我们试图将其与{ok,SavedGreeting}进行匹配,因此该过程将崩溃(在“ Erash”这个无害的意义上讲,“ crash” ,“ 当然)。

Next, instead of crashing, we'll modify this part of the controller to match potential validation errors, and present them to the user in the template. Modify the controller accordingly:

接下来,而不是崩溃,我们将修改控制器的这一部分以匹配潜在的验证错误,并在模板中将其呈现给用户。相应地修改控制器:

create('POST', []) ->
  GreetingText = Req:post_param("greeting_text"),
  NewGreeting = greeting:new(id, GreetingText),
  case NewGreeting:save() of
    {ok, SavedGreeting} ->
      {redirect, [{action, "list"}]};
    {error, ErrorList} ->
      {ok, [{errors, ErrorList}, {new_msg, NewGreeting}]}
  end.
1
2
3
4
5
6
7
8
9

If saving succeeds, we perform a redirect as before. But if validation fails, we send a list of errors to the template along with the greeting that failed to save. Now add an error clause at the the top of the body block in the view (src/ view/greeting/create.html) to show any errors in red. Also modify the textarea body as follows to display the greeting that was previously entered so that the user does not need to retype it.

如果保存成功,我们将像以前一样执行重定向。但是,如果验证失败,我们会将错误列表以及未能保存的问候发送到模板。现在,在视图(src/view/ greeting/create.html)的body 块顶部添加错误子句,以红色显示所有错误。还按如下所示修改textarea主体以显示先前输入的问候语,以便用户无需重新键入它。

{% if errors %}
    <ul>
        {% for error in errors %}
            <li><font color=red>{{ error }}</font>
        {% endfor %}
    </ul>
{% endif %}

<form method="post">
    Enter a new greeting:
    <textarea name="greeting_text">{% if new_msg %}
        {{ new_msg.greeting_text }}{% endif %}</textarea>
    <input type="submit">
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

(Note that the code inside the textarea tag should be on one line if you don’t want the template to insert any spurious whitespace into the form.) Now try entering in an exceedingly long greeting into the form. It can't be saved!

(请注意,如果您不希望模板在表格中插入任何虚假的空格,则textarea标记内的代码应在一行上。)现在,尝试在表格中输入一个冗长的问候语。

# 5. Save hooks 保存钩子

Save hooks can execute a callback before or after a record is created, updated, or deleted. But save hooks can also be used to modify a record before it is actually saved, or to abort the pending save operation after it has passed validation. Just to get a feel for things, we'll write a hook that replaces an obscene word with a clean word in our application before it is saved to the database.

保存挂钩可以在创建,更新或删除记录之前或之后执行回调。但是,保存挂钩也可以用于在实际保存记录之前对其进行修改,或者在通过验证后中止挂起的保存操作。只是为了摸索一下,我们将在应用程序中编写一个钩子,用一个干净的单词替换淫秽单词,然后再将其保存到数据库中。

Open src/model/greeting.erl, and add this function:

打开src/model/greeting.erl,并添加以下函数:

before_create() ->
  ModifiedRecord = set(greeting_text,
    re:replace(GreetingText, "masticate", "chew", [{return, list}])
  ),
  {ok, ModifiedRecord}.
1
2
3
4
5

We are using the (generated) set/2 function of this module to replace the value of the greeting_text attribute with something else. In particular, we are using the re module to replace instances of a certain Latinate word that appear in GreetingText with its Anglo equivalent. (For this and other applications, the re module is a good one to know.)

我们正在使用此模块的(生成的)set/2函数,以其他方式替换greeting_text属性的值。特别是,我们使用re模块将在GreetingText中出现的某个拉丁语单词的实例替换为与其对应的Anglo。 (对于此应用程序和其他应用程序,re模块是一个很好的了解。)

Here we returned {ok, ModifiedRecord}. We also could have returned ok if we wanted to continue with the save as originally planned. Alternatively, if the greeting contained something so dirty that no amount of expurgating could possibly redeem it, we could have aborted the save operation by returning {error, "Do you kiss your mother with that mouth?"}, or something to that effect. Note that save hooks execute after model validation, so it is possible to slip something shady past the validation police using a save hook.

在这里,我们返回了{ok,ModifiedRecord}。如果我们想按原计划继续保存,我们也可以返回确定。或者,如果问候中包含的东西太脏了,以至于无法进行大量的消耗,我们可以返回{error, "Do you kiss your mother with that mouth?"}来放弃保存操作,或者这样做。请注意,在模型验证之后执行保存挂钩,因此可以使用保存挂钩将一些阴影拖入验证策略之外。

Go ahead and try submitting a greeting through your form with the forbidden word. You will see that it is properly sanitized.

继续尝试通过您的表格提交带有禁止字词的问候。您会看到它已正确消毒。

There are other save hooks available, and the curious reader is encouraged to explore the API documentation on them. But the impatient reader will want to get to the fun stuff described in the next section.

还有其他可用的保存钩子,鼓励好奇的读者阅读关于它们的API文档。但是不耐烦的读者会想获得下一部分中描述的有趣的东西。

By now we've covered the basics of programming a database-driven website with Chicago Boss: submitting a form, creating a record, ensuring it is not crazy before saving it to the database, retrieving records for display, deleting a record if it has no redemptive qualities, and restricting privileges based on IP address. These are fairly mundane functions that ought to be familiar to anyone who has done web server programming in any language. In the next section, we're going to leverage Erlang's strengths to implement features that in other languages would be difficult, if not impossible.

到目前为止,我们已经介绍了使用Chicago Boss对数据库驱动的网站进行编程的基础知识:提交表单,创建记录,在将其保存到数据库之前确保它不发疯,检索要显示的记录,删除记录(如果有的话)没有可兑换的品质,并且基于IP地址限制特权。这些功能相当普通,对于以任何语言进行Web服务器编程的任何人都应该熟悉。在下一节中,我们将利用Erlang的优势来实现其他语言将很难甚至不是不可能的功能。

# 6. Getting Fancy 花式

So far we have explored features of Chicago Boss that are available in most MVC web frameworks. In this section, we’ll explore the features that make Chicago Boss unique: the message queue, and the model event notification system. Together, they make it possible to have a web page that updates itself in real-time while consuming very few server resources.

到目前为止,我们已经探索了大多数MVC Web框架中可用的Chicago Boss功能。在本节中,我们将探讨使Chicago Boss变得独一无二的功能:消息队列和模型事件通知系统。它们在一起可以使网页实时更新,同时消耗很少的服务器资源。

# 1. BossMQ 老板MQ

Chicago Boss ships with a message queue service called BossMQ. The service consists of named channels which follow a publish/subscribe architecture; any Erlang process can publish or subscribe to any channel, and Erlang term can be sent as a message. Channels need not be explicitly created or destroyed; they are created on demand for publishers or subscribers, and automatically destroyed after a certain (configurable) amount of time. BossMQ runs in clustered configurations just as well as a single-machine setup.

Chicago Boss附带了一个称为BossMQ的消息队列服务。该服务由遵循发布/订阅体系结构的命名通道组成;任何Erlang进程都可以发布或订阅任何频道,并且Erlang术语可以作为消息发送。通道无需显式创建或销毁;它们是按需要为发布者或订阅者创建的,并在一定时间(可配置)后自动销毁。 BossMQ可以在集群配置中运行,也可以在单机设置中运行。

The two most important functions in the BossMQ API are boss_mq:push/2 (i.e., publish) and boss_mq:pull/2 (i.e., subscribe). push is non-blocking, but pull may block until a message is available on the channel. A non-blocking version of pull is also available; it is called poll.

BossMQ API中两个最重要的功能是boss_mq:push/2(即发布)和boss_mq:pull/2(即订阅)。推是非阻塞的,但是拉可能会阻塞,直到通道上有可用消息为止。拉取的非阻塞版本也可用。这称为民意测验。

To get a feel for the BossMQ API, try this in the server shell:

要体验BossMQ API,请在服务器外壳中尝试以下操作:

> boss_mq:push("test-channel", "Who's there??").
1

That pushes a message onto the channel named “test-channel”. The channel names can be any valid string.

这会将消息推送到名为“ test-channel”的通道上。通道名称可以是任何有效的字符串。

You should see a PROGRESS REPORT in the console that looks like this:

您应该在控制台中看到如下所示的PROGRESS REPORT:

=PROGRESS REPORT==== 1-Dec-2011::16:12:10 ===
 supervisor: {<0.304.0>,bmq_channel_sup}
   started: [{pid,<0.305.0>},
    {name,mq_channel_controller},
    {mfargs,
      {bmq_channel_controller,start_link,
      [[{max_age,60},
          {supervisor,<0.304.0>},
          {channel,"test-channel"}]]}},
    {restart_type,permanent},
    {shutdown,2000},
    {child_type,worker}]
1
2
3
4
5
6
7
8
9
10
11
12

That PROGRESS REPORT just indicates that the “test-channel” channel was automatically created for us.

该进度报告仅表示“测试频道”频道是为我们自动创建的。

Below (or perhaps above) the PROGRESS REPORT is the return value:

PROGRESS REPORT下方(或上方)是返回值:

{ok,1322777530216837}
1

The large number there is a timestamp assigned to the message that we pushed. We’ll see later that message timestamps are an important part of applications designed to use BossMQ so that no messages are missed and that duplicate messages are not received.

较大的数字是为我们推送的消息分配的时间戳。稍后我们将看到消息时间戳是设计为使用BossMQ的应用程序的重要组成部分,因此不会丢失任何消息,也不会收到重复的消息。

Now we can perform a pull to receive the message that we just sent. Try this:

现在,我们可以执行拉取以接收刚刚发送的消息。尝试这个:

> boss_mq:pull("test-channel").
1

You should see a return value like:

您应该看到如下返回值:

{ok,1322778236795192,["Who's there??"]}
1

The second element of the return tuple is another timestamp; this timestamp references the time of the pull. The third element is a list of messages currently on the channel. Here, we have only one message -- the one that we pushed a few moments ago.

返回元组的第二个元素是另一个时间戳。此时间戳引用提取的时间。第三个元素是当前在频道上的消息列表。在这里,我们只有一条消息-我们在几天前推送的一条消息。

Pulls are non-destructive, so many clients can receive the same message. Try performing another pull to see what I mean.

拉取是非破坏性的,因此许多客户端可以接收相同的消息。尝试执行另一次拉动以了解我的意思。

Once you are satisfied that messages aren't being deleted upon receipt, try this:

当您对收到的邮件不会被删除感到满意后,请尝试以下操作:

> boss_mq:pull("test-channel", now).
1

The second argument is a timestamp; any returned messages will have been sent after that timestamp. By passing in now, we are indicating that we only want new messages, that is, we are not interested in the messages currently sitting on the channel.

第二个参数是时间戳;所有返回的消息将在该时间戳之后发送。通过现在传递,我们表示我们只想要新消息,也就是说,我们对当前位于通道上的消息不感兴趣。

We now have a problem. The call to pull/2 blocks until a new message is available. Since it blocks, we can't type anything into the server shell. In order to regain control of the console, that is, in order for pull/2 to return, we need to send a message to the "test-channel" channel from another process.

我们现在有一个问题。调用pull/2块,直到有新消息可用。由于它阻止了,因此我们无法在服务器外壳中键入任何内容。为了重新获得对控制台的控制,也就是为了使pull/2返回,我们需要从另一个进程向“测试通道”通道发送一条消息。

Open src/controller/cb_tutorial_greeting_controller.erl, and add a new action:

打开src/controller/cb_tutorial_greeting_controller.erl,并添加一个新操作:

send_test_message('GET', []) ->
 TestMessage = "Free at last!",
 boss_mq:push("test-channel", TestMessage),
 {output, TestMessage}.
1
2
3
4

Point your browser to http://localhost:8001/greeting/send_test_message. You should see the test message, and instantly regain control of the console.

将浏览器指向http://localhost:8001/greeting/send_test_message (opens new window)。您应该看到测试消息,并立即重新获得对控制台的控制。

Messaging is a powerful tool that allows for complex, real-time interaction among many services. In the next few sections, we'll use messaging with save hooks to create a page that updates itself whenever a new greeting is added to the database.

消息传递是一个强大的工具,可以在许多服务之间进行复杂的实时交互。在接下来的几节中,我们将使用带有保存钩子的消息传递来创建一个页面,该页面将在向数据库添加新问候语时进行自我更新。

# 2. About long-polling 关于长轮询

The strategy that we will use to update a web page is long-polling, also known as Comet. If you are unfamiliar with long-polling, the basic strategy is this: first, the web browser issues a request to the server asking for new information. But the server doesn’t return a response immediately; instead, it holds on to the request until new information actually becomes available, and only sends a response when it has something interesting.

我们将用于更新网页的策略是长轮询,也称为Comet。如果您不熟悉长轮询,则基本策略是这样的:首先,Web浏览器向服务器发出请求以请求新信息。但是服务器不会立即返回响应;相反,它会一直保留该请求,直到新的信息实际可用为止,并且仅在有一些有趣的内容时才发送响应。

This strategy is in contrast to short-polling, where the client issues a request to the server every few seconds and the server returns an answer immediately every time. There are two disadvantages of short-polling compared to longpolling: it uses more bandwidth and CPU resources (since the server needs to parse more requests), and it introduces a delay in delivering new information.

这种策略与短轮询相反,短轮询在这种情况下,客户端每隔几秒钟就会向服务器发出一次请求,而服务器每次都会立即返回一个答案。与长轮询相比,短轮询有两个缺点:它使用更多的带宽和CPU资源(因为服务器需要解析更多的请求),并且在传递新信息方面存在延迟。

But historically, short-polling has been preferred over long-polling in web programming because long-polling ties up a process while the long-poll request is pending. In a scripting language such as Ruby, the memory overhead can be several megabytes for every connected client. With more than a handful of people connected to the server, the server would quickly run out of resources.

但是从历史上看,在Web编程中,短轮询比长轮询更可取,因为长轮询会在长轮询请求未决时绑定一个进程。在诸如Ruby之类的脚本语言中,每个连接的客户端的内存开销可能是几兆字节。如果有少数几个人连接到服务器,服务器将很快耗尽资源。

It is possible to write long-pollers in C or scripting languages in an asynchronous (event-driven) environment, such as Nginx (C), Twisted (Python), EventMachine (Ruby), or Node.js (JavaScript). However, the difficulty with these frameworks is that a callback function must be provided for every network request that occurs as part of the long-poll. Compared to synchronous code, asynchronous code quickly becomes difficult to understand and maintain.

在异步(事件驱动)环境中,例如Nginx(C),Twisted(Python),EventMachine(Ruby)或Node.js(JavaScript),可以用C或脚本语言编写长轮询程序。但是,这些框架的困难在于,必须为出现在长轮询中的每个网络请求提供回调函数。与同步代码相比,异步代码很快变得难以理解和维护。

Erlang solves this dilemma. It is perfectly suited not just for handling long-poll requests, but for writing, understanding, and maintaining long-poll handlers. Erlang is “the best of both worlds” in this very specific sense: your code will appear to be synchronous, but under the hood, all of the networking is eventdriven and asynchronous. Erlang combines the best aspects of synchronous and asynchronous programming, and there is no other language like it in the world.

Erlang解决了这个难题。它不仅非常适合处理长轮询请求,而且非常适合编写,理解和维护长轮询处理程序。从这个非常特定的意义上来说,Erlang是“两全其美”的:您的代码似乎是同步的,但实际上,所有网络都是事件驱动和异步的。 Erlang结合了同步和异步编程的最佳方面,世界上没有其他语言可以与之匹敌。

With Chicago Boss, we can write long-poll handlers as we would any other handler, no callbacks or other hoop-jumping required. In fact, Chicago Boss treats long-poll requests exactly as it does other requests, so we can put a longpoll endpoint anywhere we like in the application.

使用Chicago Boss,我们可以像编写任何其他处理程序一样编写长轮询处理程序,而无需回调或其他跳跃式处理。实际上,Chicago Boss会像对待其他请求一样对待长轮询请求,因此我们可以将longpoll端点放置在应用程序中我们喜欢的任何位置。

As mentioned, BossMQ will sit in the middle of our long-poll design. The basic strategy will be:

如前所述,BossMQ将位于长轮询设计的中间。基本策略将是

- Whenever activity of interest occurs, a message is placed in a known channel on the message queue

- Sometime later, a client sends an HTTP request

- The server asks BossMQ if there are any messages available in the channel of interest

- If yes, the messages are returned immediately

- If no, the request blocks until a message is put onto the message queue

- As soon as new database activity occurs, a message goes into the message queue. The blocking call returns, the controller action logic                  continues, the server returns a response to the client, and the client updates the page.

-每当发生感兴趣的活动时,都会在消息队列中的已知通道中放置一条消息

-稍后,客户端会发送HTTP请求

-服务器询问BossMQ感兴趣的通道中是否有可用消息

- 如果是,则消息立即返回

- 如果否,则请求将一直阻塞,直到将消息放入消息队列中为止。

- 一旦发生新的数据库活动,消息就会进入消息队列。阻塞调用返回,控制器动作逻辑继续,服务器向客户端返回响应,客户端更新页面。

It sounds complicated, but we will need only a few pieces of code to achieve it. First, we need code to push the update into the message queue; second, we need code to pull updates off of the message queue; and finally, we need JavaScript code to inject those updates into the web page. We will address these parts in order.

听起来很复杂,但是我们只需要一些代码就可以实现它。首先,我们需要代码将更新推送到消息队列中;其次,我们需要代码将更新从消息队列中拉出来;最后,我们需要JavaScript代码才能将这些更新注入到网页中。我们将按顺序处理这些部分。

# 3. Implementing real-time updates with save hooks 使用保存挂钩实现实时更新

First, open up src/model/greeting.erl and add the following save hook:

首先,打开src/model/greeting.erl并添加以下保存钩子:

after_create() ->
 boss_mq:push("new-greetings", THIS).
1
2

In a parameterized module (such as the greeting model), the variable THIS refers to the current module instance; the code above simply pushes a copy of each newly-created greeting to the "new-greetings" channel.

在参数化的模块(例如问候模型)中,变量THIS引用当前模块实例;上面的代码只是将每个新创建的问候语的副本推送到“ new-greetings”频道。

Next, create a new action in the controller (src/controller/cb_tutorial_greeting_controller.erl) to pull messages from the "newgreetings" channel and return them as JSON to the client:

接下来,在控制器中创建一个新动作(src/controller/cb_tutorial_greeting_controller.erl),以从“ newgreetings”通道中提取消息,并将其作为JSON返回给客户端:

pull('GET', [LastTimestamp]) ->
 {ok, Timestamp, Greetings} = boss_mq:pull("new-greetings",
 list_to_integer(LastTimestamp)),
 {json, [{timestamp, Timestamp}, {greetings, Greetings}]}.

1
2
3
4
5

The timestamp is important to the design, because it lets us retrieve older messages in case there is a chronological gap in the client's sequence of longpolls. Note that in line 2 a new timestamp is returned by boss_mq:pull/2 along with the list of messages; this timestamp will be used as an input to the next long-poll executed by the client, so we encode it in the JSON response.

时间戳对设计很重要,因为时间戳可以让我们检索较旧的消息,以防客户端的长轮询序列在时间顺序上出现间隔。请注意,在第2行中,boss_mq:pull/2和消息列表一起返回了新的时间戳;此时间戳将用作客户端执行的下一个长轮询的输入,因此我们将其编码为JSON响应。

To get things going, we will need an initial list of greetings, as well as an initial timestamp, which can be retrieved from boss_mq:now/1. Create a new "live" action in the controller, like this:

为了使事情顺利进行,我们需要一个问候的初始列表以及一个初始时间戳,可以从boss_mq:now/1中获取。在控制器中创建一个新的“实时”操作,如下所示:

live('GET', []) ->
 Greetings = boss_db:find(greeting, []),
 Timestamp = boss_mq:now("new-greetings"),
 {ok, [{greetings, Greetings}, {timestamp, Timestamp}]}.
1
2
3
4

This action will populate a template for a live-updating page; JavaScript in that page will subsequently issue requests to the pull action above. Note that boss_mq:now/1 requires the name of the channel because channels might reside on different Erlang nodes whose clocks could be slightly out of sync.

此操作将为实时更新页面填充模板;该页面中的JavaScript随后将向上述pull操作发出请求。请注意,boss_mq:now/1 需要通道的名称,因为通道可能位于不同的Erlang节点上,这些节点的时钟可能会稍微不同步。

With that, the server code for real-time updates is finished! Now we just need a template with some JavaScript to perform the actual polling and to insert new greetings into the web page. Create a new file src/view/greeting/live.html, and add the following as its contents:

这样,用于实时更新的服务器代码就完成了!现在,我们只需要一个带有JavaScript的模板即可执行实际的轮询并将新的问候语插入到网页中。创建一个新文件src/view/greeting/live.html,并添加以下内容作为其内容:

<html>
<head>
    <title>Fresh hot greetings!</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
    <script>
        function listen_for_events(timestamp) {
            $.ajax("/greeting/pull/" + timestamp, {
                success: function (data, code, xhr) {
                    for (var i = 0; i < data.greetings.length; i++) {
                        var msg = data.greetings[i].greeting_text;
                        $("#greeting_list").append("<li>" + msg);
                    }
                    listen_for_events(data.timestamp);
                }
            });
        }

        $(document).ready(function() {
            listen_for_events({{ timestamp }});
        });
    </script>
</head>
<body>
    <ul id="greeting_list">
        {% for greeting in greetings %}
            <li>{{ greeting.greeting_text }}
                {% empty %}
            <li>No greetings!
        {% endfor %}
    </ul>
</body>
</html>
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

This template creates an initial list of greetings with the provided template variables in the "greeting_list" ul element. When the document is loaded, it executes listen_for_events(), which takes as its only argument the timestamp that will be passed to the pull action. An AJAX request is issued, and when it returns (possibly much later), the DOM is updated with the new greetings, and listen_for_events is called again with the updated timestamp.

该模板使用“ greeting_list” ul元素中提供的模板变量创建问候的初始列表。加载文档后,它将执行 listen_for_events(),它将唯一的参数作为传递给pull操作的时间戳。发出AJAX请求,当它返回(可能要晚得多)时,将使用新的问候语更新DOM,并使用更新的时间戳再次调用 listen_for_events。

Taking these three pieces together (the after_create save hook, the pull action, and the live template), we have a page that stays synchronized with the database as new greetings are created!

将这三个部分(after_create保存钩子,pull操作和实时模板)放在一起,我们将创建一个页面,该页面在创建新问候语时与数据库保持同步!

To test the feature, open a window for http://localhost:8001/greeting/create and a separate window for the new page, http://localhost:8001/greeting/live. For maximum effect, put the windows side by side, invite a few coworkers over to your computer, and dim the lights. Create a greeting in the first window, and BAM! It will appear instantaneously in the second window.

要测试该功能,请打开http://localhost:8001/greeting/create (opens new window)的窗口,并打开新页面http://localhost:8001/greeting/live (opens new window)的单独窗口。为了获得最佳效果,请将窗户并排放置,邀请几个同事到您的计算机上,并调暗灯光。在第一个窗口中创建一个问候,然后BAM!它会立即显示在第二个窗口中。

The implementation here is a good start, but having notification logic in our data model may seem a bit, well, unseemly. In the next few sections, we'll learn about BossNews, a notification system that will let us act on changes to the data model without contaminating our Platonic model code with the temporal world of message queues.

此处的实现是一个良好的开始,但是在我们的数据模型中使用通知逻辑可能看起来有点不合理。在接下来的几节中,我们将学习BossNews,这是一个通知系统,它使我们能够对数据模型进行更改,而不会在消息队列的时间范围内污染柏拉图模型代码。

# 4. BossNews 老板新闻

Chicago Boss introduces model events to database-driven server programming. Model events are something of a foreign subject to most server programmers, whose idea of an event is a file descriptor that is ready for reading or writing. Model events refer to callbacks that are executed when there is a change to application state; they can be used to ensure that some action is taken whenever some data of interest changes, regardless of which code path actually performed the change.

Chicago Boss将模型事件引入数据库驱动的服务器编程中。对于大多数服务器程序员来说,模型事件是一个陌生的话题,他们对事件的想法是准备读取或写入的文件描述符。模型事件是指在应用程序状态发生更改时执行的回调。它们可用于确保每当感兴趣的某些数据发生更改时,便采取某种措施,而与实际执行更改的代码路径无关。

Model events are similar to, but distinct from, save hooks.

模型事件类似于,但不同于保存挂钩。

- Save hooks require you to write code to detect what actually changed in an update. With model events, we specify the particular changes           we are interested in and receive a structured event that describes the change (for example, {"account-1", first_name, "Jon", "John"}).

- Save hooks execute in the caller's thread, and so functions that block in the save hook will block the caller. If we wish to send an email,               for instance, the caller must wait while the email is generated and sent. Model events, on the other hand, execute in a separate process and           are suitable for potentially long-running tasks.

- Save hooks are a static bit of code embedded in the model object. Model events, on the other hand, can be dynamically added or                       deleted anywhere in the application, and can be made to expire after a given amount of time. They are "disposable" and thus inexpensive              from a code-maintenance point of view. For example, it is easy to use model events to implement unimportant features on your website                without ever touching important model code.

-保存挂钩要求您编写代码以检测更新中实际发生的更改。对于模型事件,我们将指定我们感兴趣的特定更改,并接收描述该更改的结构化事件(例如{“           account-1”,first_name,“ Jon”,“ John”})。

-保存挂钩在调用者的线程中执行,因此在保存挂钩中阻止的函数将阻止调用者。例如,如果我们希望发送电子邮件,则呼叫者必须等待电子邮件的生               成 和发送。另一方面,模型事件在单独的过程中执行,适合于可能长时间运行的任务。

-保存挂钩是嵌入在模型对象中的静态代码。另一方面,可以在应用程序中的任何位置动态添加或删除模型事件,并且可以使模型事件在给定的时间后

过期。从代码维护的角度来看,它们是“一次性的”,因此是廉价的。例如,很容易使用模型事件在您的网站上实现不重要的功能,而无需接触重要的模              型代码。

Once you use model events in a server environment, you will feel depressed using a web framework that doesn’t have them.

在服务器环境中使用模型事件后,使用没有事件的网络框架会感到沮丧。

Chicago Boss's model event system is known as BossNews. To get to know the API, try this in the server shell:

Chicago Boss的模型事件系统称为BossNews。要了解API,请在服务器外壳中尝试以下操作:

> {ok, WatchId} = boss_news:watch("greetings", fun(_, _) ->
 error_logger:info_msg("Did you see that??~n") end).

1
2
3

Here we've used the boss_news:watch/2 function to watch a particular topic string (here, "greetings"). The provided callback will be executed whenever a record enters or leaves the “greetings” set, that is, when a greeting is created or deleted. In Chicago Boss lingo, we say that the function “creates a watch”. It returns {ok, WatchId}; the WatchId is an integer which can be used to cancel the watch later on when we are tired of receiving messages.

在这里,我们使用了boss_news:watch/2函数来观看特定的主题字符串(此处为“ greetings”)。每当记录进入或离开“问候”设置时,即创建或删除问候语时,将执行提供的回调。在Chicago Boss术语中,我们说该功能“创建了手表”。返回{ok,WatchId}; WatchId是一个整数,当我们厌倦了接收消息后,可以用来取消监视。

Go ahead and try it out by creating a new greeting in the console:

继续并通过在控制台中创建新的问候语来尝试一下:

> NewGreeting = greeting:new(id, "Test greeting").
{greeting, id, "Test greeting"}.
1
2

Nothing so far. Now save it.

到目前为止没有。现在保存。

> NewGreeting:save().
1
=INFO REPORT==== 28-Nov-2011::17:01:47 === Did you see that?? {ok,{greeting,"greeting-8","Test greeting"}}
1

The greeting is saved, and our callback is executed. Here we have most of the power of save hooks without even touching the code base; we've done everything in the console. As you might guess, watches are highly useful for debugging a system in production.

保存问候语,并执行我们的回调。在这里,我们拥有保存钩子的大部分功能,而无需触及代码库。我们已经完成了控制台中的所有操作。您可能会猜到,手表对于调试生产中的系统非常有用。

Go to the web browser and try creating and deleting greetings. You should see similar INFO REPORT messages in the show up in the console.

转到网络浏览器,然后尝试创建和删除问候语。您应该在控制台的显示中看到类似的INFO REPORT消息。

Before these get too annoying, we can cancel the watch thus:

在这些变得太烦人之前,我们可以这样取消手表:

> boss_news:cancel_watch(WatchId).
1

If you create or delete greetings now, no INFO REPORTS will appear in the console (besides the usual request logging).

如果现在创建或删除问候语,则在控制台中将不会出现“ INFO REPORTS”(常规的请求记录除外)。

The callback function that we provided above took two arguments, but I didn't tell you what those arguments were. Well, you're old enough now. The first argument is the name of the event: created or deleted. The second argument is simply a copy of the record that was created or deleted.

我们上面提供的回调函数有两个参数,但是我没有告诉您这些参数是什么。好吧,你现在够大了。第一个参数是事件的名称:创建或删除。第二个参数只是创建或删除的记录的副本。

Try a more sophisticated callback, such as this:

尝试更复杂的回调,例如:

> {ok, WatchId2} = boss_news:watch("greetings",
 fun(Event, Record) ->
 error_logger:info_msg("Didn't you hear? ~p was ~p~n",
 [Record:id(), Event]) end).
1
2
3
4

(Note that we bind the result to something other than WatchId, since that variable is already bound.)

(请注意,由于该变量已绑定,因此将结果绑定到WatchId以外的其他对象。)

Create and delete records to confirm that the callback works, and cancel the watch (with boss_news:cancel_watch/1) when the novelty has worn off.

创建和删除记录以确认该回调有效,并在新颖性消失时取消手表(使用boss_news:cancel_watch/1)。

BossNews provides a rich API for receiving these and other events; you are encouraged to explore the API docs to see the full power of BossNews for yourself. For now, we are going to put BossNews to work for us on the website we've been building.

BossNews提供了一个丰富的API来接收这些事件和其他事件。建议您浏览API文档,以亲自了解BossNews的全部功能。现在,我们将在我们一直在构建的网站上使用BossNews为我们工作。

# 5. Implementing real-time updates with BossNews 使用BossNews实施实时更新

We'll now replace the save-hook implementation of our real-time updates with BossNews. To start, comment out the save hook in src/model/greeting.erl:

现在,我们将使用BossNews替换实时更新的保存钩实现。首先,在src/model/ greeting.erl中注释掉保存钩子:

% after_create() ->
% boss_mq:push("new-greetings", THIS).
1
2

Now we'll set up a watch directly in the console. Type this in the server shell:

现在,我们将直接在控制台中设置手表。在服务器外壳中键入以下内容:

> {ok, WatchId3} = boss_news:watch("greetings",
 fun(created, NewGreeting) ->
 boss_mq:push("new-greetings", NewGreeting);
 (deleted, OldGreeting) ->
 boss_mq:push("old-greetings", OldGreeting) end).
1
2
3
4
5

That will set up a watch that adds newly created and newly deleted greetings to separate channels on the message queue.

这将设置一个手表,将新创建和新删除的问候语添加到消息队列中的单独通道。

Go ahead and make sure it works by visiting http://localhost:8001/greeting/ live, and then creating a new greeting in the console. We now have a liveupdating system without the use of save hooks.

继续并通过访问http://localhost:8001/greeting/live (opens new window)并在控制台中创建新的问候语来确保它可以正常工作。现在,我们有了一个不使用保存挂钩的实时更新系统。

This is not a very permanent solution, unless we wish to type that command into the console every time the server starts. Instead, we can add it to a server start-up script, priv/init/cb_tutorial_01_news.erl. Change the init/0 function to this:

除非我们希望每次服务器启动时都在控制台中键入该命令,否则这不是一个永久的解决方案。相反,我们可以将其添加到服务器启动脚本priv/init/cb_tutorial_01_news.erl中。将init/0函数更改为此

init() ->
 {ok, WatchId} = boss_news:watch("greetings",
 fun(created, NewGreeting) ->
 boss_mq:push("new-greetings", NewGreeting);
 (deleted, OldGreeting) ->
 boss_mq:push("old-greetings", OldGreeting)
 end),
 {ok, [WatchId]}.
1
2
3
4
5
6
7
8

The init/0 function simply needs to return {ok, ListOfWatchIDs}. This list of created watch IDs is needed so that the stop/1 function knows which watches to cancel when the server shuts down or reloads the script.

init/0函数仅需要返回{ok,ListOfWatchIDs}。需要此创建的监视ID列表,以便stop/1函数知道在服务器关闭或重新加载脚本时要取消的监视。

Now in the console, we can cancel the watch that we previously created in the console, and read in the script so that a new watch is created in its place:

现在,在控制台中,我们可以取消先前在控制台中创建的手表,并读入脚本,以便在其位置创建新的手表:

> boss_news:cancel_watch(WatchId3).
ok

1
2
3
> boss_web:reload_init_scripts().
ok
1
2

With that, the feature is finished. We've taken out the save hook, and the BossNews observer will be set up on server start-up. Try it out by stopping and restarting the server.

这样就完成了功能。我们已经取出保存钩子,并且BossNews观察器将在服务器启动时进行设置。通过停止并重新启动服务器来尝试一下。

> q().
./init-dev.sh
1
2

Since we've been using an in-memory database for this tutorial, restarting the server will lose any existing greetings. But if everything went as planned, new greetings will automatically appear on our live-updating page. For extra credit, see if you can implement a "live delete" where deleted greetings are instantly excised from the browser.

由于我们在本教程中一直使用内存数据库,因此重启服务器将丢失任何现有的问候。但是,如果一切按计划进行,则新的问候语将自动出现在我们的实时更新页面上。要获得额外的信誉,请查看您是否可以实施“实时删除”,从浏览器中立即删除已删除的问候语

Although few persons besides your spouse and mother will be interested in your database of greetings, the implications of the code we’ve written in this section are quite exciting. With BossNews and BossMQ at your disposal (and BossDB and BossRecord at your back), you'll be able to write production-ready features that you may previously have thought were the exclusive province of large Internet companies. If you're writing a social network, you can now implement chat and status updates. If you're writing auction or exchange website, you will be able update the current bids in real-time. If you're writing an internal inventory system, you can be sure that no one in the company ever sees an out-of-date view of the system. All in just a few lines of Erlang.

尽管除了您的配偶和母亲外,很少有人会对您的问候数据库感兴趣,但是我们在本节中编写的代码的含义非常令人兴奋。使用BossNews和BossMQ(以及BossDB和BossRecord),您将能够编写可用于生产环境的功能,这些功能以前可能是大型互联网公司的专有资源。如果您正在编写社交网络,则现在可以实现聊天和状态更新。如果您正在编写拍卖或交易网站,则可以实时更新当前出价。如果您正在编写内部库存系统,则可以确保公司中没有人看到过时的系统视图。只需几行Erlang。

The best part, of course, is that the Erlang/OTP platform is solid as a rock. As long as you do a little capacity planning and follow Erlang best practices, you'll be able to sleep without worrying about the application server crashing (that's what database servers are for, right?). All the features that we have covered here, by the way, will work "out-of-the-box" on one machine or a cluster of a hundred machines.

当然,最好的部分是Erlang / OTP平台坚如磐石。只要您进行一点容量规划并遵循Erlang最佳实践,就可以无需担心应用程序服务器崩溃就可以入睡(这就是数据库服务器的作用,对吧?)。顺便说一下,我们在这里介绍的所有功能都可以在一台机器上或数百台机器上“开箱即用”地工作。

One last feature worth mentioning is that BossNews is deeply integrated with BossDB's cache system; if you turn on caching, there is no need to write cacherelated save hooks or cache-invalidation logic. The cache always stays in sync with the database using model event notifications.

值得一提的最后一个功能是BossNews与BossDB的缓存系统深度集成。如果打开缓存,则无需编写与缓存相关的保存挂钩或缓存无效逻辑。高速缓存始终使用模型事件通知与数据库保持同步。

There is much more to learn about Chicago Boss, but it is best to stop here. If you've had fun so far, check out the wiki, read the API docs, sign up for the mailing list, and get involved in our creative and active developer community. It’s a great time to be an Erlang web programmer.

关于芝加哥老板,还有很多要学习的东西,但是最好到此为止。到目前为止,如果您还很开心,请查看Wiki,阅读API文档,注册邮件列表,并加入我们富有创造力和活跃的开发人员社区。现在是成为Erlang网络程序员的好时机。

# 本文翻译自:http://chicagoboss.org/tutorial.pdf (opens new window)

Last Updated: 1/6/2020, 2:41:34 PM