Skip to content

Latest commit

 

History

History
988 lines (619 loc) · 44.5 KB

02.md

File metadata and controls

988 lines (619 loc) · 44.5 KB

二、同构 Go 工具链

在前一章中,我们介绍了同构 web 应用体系结构提供的许多好处,以及使用 Go 编程语言构建同构 web 应用的优势。现在,是时候探索使同构 Go web 应用成为可能所需的基本要素了。

在本章中,我们将向您介绍同构 Go工具链。我们将研究组成工具链 Go、GopherJS、同构 Go 工具包和 UX 工具包的关键技术。一旦我们确定了如何获得并准备好这些工具,我们将安装 IGWEB 演示,这是我们将在本书中实现的同构 Go web 应用。稍后,我们将深入剖析 IGWEB 演示,检查项目结构和代码组织。

我们还将向您介绍本书中将使用的一些有用且高效的技术,例如在服务器端实现自定义数据存储以满足 web 应用的数据持久性需求,以及利用依赖注入在整个 web 应用中提供常用功能。最后,我们将提供 IGWEB 应用的项目路线图,以规划我们构建同构 Go web 应用的过程。

在本章中,我们将介绍以下主题:

  • 安装同构 Go 工具链
  • 设置 IGWEB 演示
  • IGWEB 演示简介
  • Project structure and code organization

安装同构 Go 工具链

在本节中,我们将指导您完成安装和配置同构 Go 工具链的过程,这套技术允许我们创建同构 Go web 应用。以下是我们将介绍的关键技术:

  • 地鼠
  • 同构围棋工具箱
  • 用户体验工具包

We will utilize Go as the server-side and client-side programming language for our web application. Go allows us to create reliable and efficient software using a simple and understandable syntax. It's a modern programming language that is designed in an age of multicore processors, networked systems, massive computation clusters, and the World Wide Web. Since Go is a general purpose programming language, it makes for an ideal technology to create isomorphic web applications.

GopherJS允许我们通过将 Go 代码转换为纯 JavaScript 代码,将 Go 带到客户端,该代码可以在所有主要的 web 浏览器中运行。GopherJS 绑定可用于常见的 JavaScript 功能,包括 DOM API、XHR、内置 JavaScript 函数/运算符和 WebSocket API。

同构 Go 工具包为我们提供了构建同构 Go web 应用所需的技术。使用此项目提供的工具,我们可以实现同构 web 应用中所需的常见功能,例如客户端路由、同构模板呈现和创建同构 web 表单。

UX 工具包为我们提供了在 Go 中创建可重用组件的能力,称为cogs。您可以将它们视为促进可重用性的自包含用户界面小部件。COG 可以实现为纯 Go COG 或混合 COG,可以利用现有 JavaScript 功能。COG 在服务器端注册,并在客户端部署。

图 2.1描述了我们将用作维恩图的技术堆栈,清楚地指出了技术组件将驻留的环境:

图 2.1:同构 Go 工具链:Go、GopherJS、同构 Go 工具包和 UX 工具包

现在我们已经确定了构成技术堆栈的关键组件,让我们继续安装/配置它们。

如果您是新来的围棋爱好者,那么花时间进行围棋之旅是值得的,可在找到 https://tour.golang.org

在继续之前,您需要在系统上安装 Go。在本节中,我们将提供安装 Go 和设置 Go 工作区的高级概述。如果您需要进一步的帮助,您可以访问上安装 Go 的详细说明 https://golang.org/doc/install

让我们前往 Go 网站,网址为https://golang.org

图 2.2:Go 网站

点击下载 Go 链接,如图 2.2所示,进入下载页面(https://golang.org/dl/ ),如图 2.3所示:

图 2.3:Go 网站上的下载页面

正如您所看到的,Go 适用于所有主要的操作系统。我们将在引导您完成安装和配置过程的同时使用 Mac。有关为其他操作系统安装 Go 的信息,请参见 Go 网站上的入门文档,网址为https://golang.org/doc/install

在下载页面上,单击链接下载适用于您的操作系统的 Go 发行版。我点击链接下载苹果 macOS 安装程序

使用 Go 启动并运行您的系统将包括以下步骤:

  1. 安装围棋
  2. 设置 Go 工作区
  3. 构建和运行程序

安装围棋

下载完成后,继续并启动安装程序。Go 安装程序如图 2.4所示:

图 2.4:Go 安装程序

按照安装程序的屏幕提示进行操作,如果安装程序要求您为系统上的所有用户提供 Go,请确保您选择为系统的所有用户安装 Go。还可能会提示您输入系统凭据(以便您可以为系统上的所有用户安装 Go)。同样,请继续并提供您的系统凭据。

安装程序完成后,您应该从 Go 安装程序获得以下确认:

图 2.5:Go 安装程序报告安装成功

安装完成后,让我们打开命令提示符并检查安装程序在何处安装了文件:

$ which go
/usr/local/go/bin/go

在 macOS 系统上,Go 发行版安装到/usr/local/go目录,Go 发行版附带的二进制文件安装在/usr/local/go/bin目录中。

如果您是 Go 工具链的新手,应使用go help命令熟悉 Go 随附的各种命令:

$ go help
Go is a tool for managing Go source code.

Usage:

 go command [arguments]

The commands are:

 build      compile packages and dependencies
 clean      remove object files
 doc        show documentation for package or symbol
 env        print Go environment information
 bug        start a bug report
 fix        run go tool fix on packages
 fmt        run gofmt on package sources
 generate   generate Go files by processing source
 get        download and install packages and dependencies
 install    compile and install packages and dependencies
 list       list packages
 run        compile and run Go program
 test       test packages
 tool       run specified go tool
 version    print Go version
 vet        run go tool vet on packages

Use "go help [command]" for more information about a command.

Additional help topics:
 c           calling between Go and C
 buildmode   description of build modes
 filetype    file types
 gopath      GOPATH environment variable
 environment environment variables
 importpath  import path syntax
 packages    description of package lists
 testflag    description of testing flags
 testfunc    description of testing functions
Use "go help [topic]" for more information about that topic.

要确定系统上安装的 Go 版本,可以使用go version命令:

$ go version
go version go1.9.1 darwin/amd64

您的系统上应该安装最新版本的 Go,并且在继续之前,您需要有一个正确配置的 Go 工作区。

设置 Go 工作区

现在您已经成功地在系统上安装了 Go,您需要有一个正确配置的 Go 工作区,然后才能继续。我们将提供关于设置围棋工作区的高级概述,如果您需要进一步帮助,您可以阅读围棋网站上关于设置围棋工作区的详细说明:https://golang.org/doc/code.html

使用您喜爱的文本编辑器打开主目录中的.profile文件。如果您使用的是 Linux,则需要打开主目录中的.bashrc文件。

我们将向文件中添加以下行,以添加一些非常重要的环境变量:

export GOROOT=/usr/local/go
export GOPATH=/Users/kamesh/go
export GOBIN=${GOPATH}/bin
export PATH=${PATH}:/usr/local/bin:${GOROOT}/bin:${GOBIN}

我的用户名是kamesh,显然你必须用你的用户名替换它。

$GOROOT is an environment variable used to specify where the Go distribution is installed on the system.

$GOPATH是一个环境变量,用于指定包含所有 Go 项目源代码的顶级目录。该目录称为我们的 Go 工作区。我已经在go文件夹/Users/kamesh/go中的我的主目录中创建了我的工作区。

让我们继续创建 go 工作区,其中包含三个重要目录:

$ mkdir go
$ mkdir go/src
$ mkdir go/pkg
$ mkdir go/bin

go/src目录将包含 Go 源文件。go/pkg目录将包含已编译的 Go 包。最后,go/bin目录将包含已编译的 Go 二进制文件。

$GOBIN是一个环境变量,用于指定 Go 安装已编译二进制文件的位置。当我们运行go install命令时,Go 编译我们的源代码,并将新创建的二进制文件存储在$GOBIN指定的目录中。

我们在**$PATH**环境变量$GOROOT/bin$GOBIN目录中增加了两个条目。这告诉我们的 shell 环境在哪里可以找到与 Go 相关的二进制文件。将$GOROOT/bin添加到$PATH可以让 shell 环境知道 Go 发行版的二进制文件的位置。$GOBIN上的附加说明告诉 shell 环境,我们创建的 Go 程序的二进制文件将存在于何处。

Building and running Go programs

让我们创建一个简单的“hello world”程序来检查 Go 设置。

我们首先在 Go 工作区的src目录中为我们的新程序创建一个目录,如下所示:

$ cd $GOPATH/src
$ mkdir hellogopher

现在,使用您最喜欢的文本编辑器,让我们在hellogopher目录中创建一个包含以下内容的hellogopher.go源文件:

package main

import "fmt"

func main() {

  fmt.Println("Hello Gopher!")
}

要一步构建并运行此程序,您可以发出go run命令:

$ go run hellogopher.go
Hello Gopher!

要生成将存在于当前目录中的二进制可执行文件,可以发出go build命令:

$ go build

To build a binary executable and automatically move it to your $GOBIN directory, you can issue the go install command:

$ go install

After issuing the go install command, you simply have to type the following command to run it (provided that $GOBIN is specified in your $PATH):

$ hellogopher
Hello Gopher!

现在,我们已经成功地安装、配置和验证了 Go 安装。现在是时候启动并运行其他工具了,从 GopherJS 开始。

地鼠

GopherJS 是一个将 Go 代码转换为纯 JavaScript 代码的 transpiler。使用 GopherJS,我们可以在 Go 中编写前端代码,该代码将在所有支持 JavaScript 的主要 web 浏览器中运行。这项技术让我们能够在 web 浏览器中释放 Go 的威力,没有它,同构 Go 就不可能实现。

在本章中,我们将向您展示如何安装 GopherJS。我们将在第 3 章中更详细地介绍 GopherJS,在前端介绍 GopherJS

使用 GopherJS 启动和运行包括以下步骤:

  1. 安装 GopherJS
  2. 安装基本的 GopherJS 绑定
  3. 在命令行上熟悉 GopherJS

安装 GopherJS

我们可以通过发出以下go get命令来安装 GopherJS:

$ go get -u github.com/gopherjs/gopherjs

要查找系统上安装的gopherjs的当前版本,请使用gopherjs version命令:

$ gopherjs version
GopherJS 1.9-1</strong>

Go 和 GopherJS 的主要版本必须在您的系统上匹配。在本书中,我们将使用 Go 的 1.9.1 版和 GopherJS 的 1.9-1 版。

您可以键入gopherjs help以熟悉 GopherJS 附带的各种命令:

$ gopherjs
GopherJS is a tool for compiling Go source code to JavaScript.

Usage:
 gopherjs [command]

Available Commands:
 build compile packages and dependencies
 doc display documentation for the requested, package, method or symbol
 get download and install packages and dependencies
 install compile and install packages and dependencies
 run compile and run Go program
 serve compile on-the-fly and serve
 test test packages
 version print GopherJS compiler version

Flags:
 --color colored output (default true)
 --localmap use local paths for sourcemap
 -m, --minify minify generated code
 -q, --quiet suppress non-fatal warnings
 --tags string a list of build tags to consider satisfied during the build
 -v, --verbose print the names of packages as they are compiled
 -w, --watch watch for changes to the source files

Use "gopherjs [command] --help" for more information about a command.

安装基本的 GopherJS 绑定

现在我们已经安装了 GopherJS 并确认其工作正常,我们需要获得以下 GopherJS 绑定,这是我们前端 web 应用开发需要的:

  • 多姆
  • jsbuiltin
  • xhr
  • 网袋

多姆

dom包为我们提供了 JavaScript DOM API 的 GopherJS 绑定。

我们可以通过发出以下命令来安装dom包:

$ go get honnef.co/go/js/dom

jsbuiltin

jsbuiltin包为常见 JavaScript 操作符和函数提供绑定。我们可以通过发出以下命令来安装jsbuiltin包:

$ go get -u -d -tags=js github.com/gopherjs/jsbuiltin

xhr

xhr包为XMLHttpRequest对象提供绑定。我们可以通过发出以下命令来安装xhr包:

$ go get -u honnef.co/go/js/xhr

网袋

The websocket package provides bindings for the web browser's WebSocket API. We can install the websocket package by issuing the following command:

$ go get -u github.com/gopherjs/websocket

在命令行上熟悉 GopherJS

gopherjs命令与go命令非常相似。例如,为了将 Go 程序转换为其 JavaScript 表示形式,我们发出如下的gopherjs build命令:

$ gopherjs build

为了构建 GopherJS 项目并缩小生成的 JavaScript 源文件,我们指定了-m标志和gopherjs build命令:

$ gopherjs build -m

当我们执行构建操作时,GopherJS 将创建一个.js源文件和一个.js.map源文件。

.js.map文件称为源映射。它们帮助我们将缩小的 JavaScript 源文件映射回其未构建状态。当我们使用 web 浏览器控制台查找错误时,此功能非常方便。

GopherJS 生成的 JavaScript 源文件可以使用script标记作为外部 JavaScript 源文件导入到网页中

同构围棋工具箱

同构围棋工具包(http://isomorphicgo.org 为我们提供了实现同构 Go web 应用所需的技术。我们将使用来自同构 Go 工具包的isokit包来实现同构 web 应用:

图 2.6:同构 Go 网站

安装 isokit

来自同构 Go 工具包的isokit包提供了一个通用的同构功能,可以在服务器端或客户端使用。该包提供的一些显著优点包括同构模板呈现、客户端应用路由、自动静态资产绑定以及创建同构 web 表单的能力。

我们可以通过发出以下go get命令来安装isokit包:

$ go get -u github.com/isomorphicgo/isokit

用户体验工具包

用户体验工具包(http://uxtoolkit.io 允许我们实现cogs,这是在 Go 中实现的可重用组件,可以跨组成 IGWEB 的网页使用。我们将在第 9 章Cogs–可重用组件中介绍可重用组件。

安装 cog 包

我们可以通过发出以下go get命令来安装cog包:

$ go get -u github.com/uxtoolkit/cog

现在我们已经安装了同构 Go 工具链,是时候设置 IGWEB 演示了,这是我们将在本书中构建的同构 web 应用。

设置 IGWEB 演示

您可以通过发出以下go get命令获得本书的源代码示例:

$ go get -u github.com/EngineerKamesh/igb

IGWEB 演示网站完整实现的源代码位于igb/igweb文件夹中。在igb/individual文件夹中可以找到各个章节的源代码清单。

设置应用根环境变量

IGWEB 演示依赖于正在定义的应用根环境变量$IGWEB_APP_ROOT。web 应用使用此环境变量来声明其驻留的位置。通过这样做,web 应用可以确定其他资源(如静态资产(图像、css 和 javascript))的位置。

您应该通过在 bash 概要文件中添加以下条目来设置$IGWEB_APP_ROOT环境变量:

export IGWEB_APP_ROOT=${GOPATH}/src/github.com/EngineerKamesh/igb/igweb

要验证环境中是否存在$IGWEB_APP_ROOT环境变量,可以使用echo命令:

$ echo $IGWEB_APP_ROOT
/Users/kamesh/go/src/github.com/EngineerKamesh/igb/igweb

传输客户端应用

现在我们已经设置了$IGWEB_APP_ROOT环境变量,我们可以访问client目录,客户端 web 应用位于该目录:

$ cd $IGWEB_APP_ROOT/client

我们发出以下go get命令来安装客户端应用正常运行所需的任何其他依赖项:

$ go get ./..

最后,我们发出gopherjs build命令来传输 IGWEB 客户端 web 应用:

$ gopherjs build

运行命令后,应生成两个文件-client.jsclient.js.mapclient.js源文件是 IGWEB 客户端 Go 程序的 JavaScript 表示。client.js.map文件是源映射文件,web 浏览器将与client.js一起使用,在 web 控制台中为我们提供详细信息,这在调试问题时非常方便。

现在我们已经为 IGWEB 的客户端应用传输了代码,接下来的逻辑步骤是构建和运行 IGWEB 的服务器端应用。在此之前,我们必须安装并运行本地 Redis 实例,这将在下一节中进行。

建立 Redis

Redis 是一种流行的 NoSQL 内存数据库。因为整个数据库都存在于内存中,所以数据库查询速度非常快。Redis 还提供对各种数据类型的强大支持,是一种多用途工具,可以用作数据库、内存缓存,甚至用作消息代理。

在本书中,我们将使用 Redis 满足 IGWEB 的数据持久性需求。我们将在默认端口 6379 上运行 Redis 实例。

We issue the following commands to download and install Redis:

$ wget http://download.redis.io/releases/redis-4.0.2.tar.gz
$ tar xzf redis-4.0.2.tar.gz
$ cd redis-4.0.2
$ make
$ sudo make install

使用wget命令获取 Redis 的另一种方法是从 Redis 下载页面获取,如*图 2.7,*在中所示 https://redis.io/download

图 2.7:Redis 网站上的下载部分

下载并安装 Redis 后,可以通过发出redis-server命令启动服务器:

$ redis-server

在另一个终端窗口中,我们可以打开 Redis命令行界面CLI),使用redis-cli命令连接到 Redis 服务器实例:

$ redis-cli

我们可以使用set命令设置一个具有bar值的foo键:

127.0.0.1:6379> set foo bar
OK

我们可以使用get命令获取foo键的值:

127.0.0.1:6379> get foo
"bar"

您可以通过访问 Redis 网站的文档部分了解更多关于 Redis 的信息 https://redis.io/documentation. 浏览提供的 Redis 快速入门文档 https://redis.io/topics/quickstart 也很有帮助。现在我们已经安装了本地 Redis 实例,是时候构建并运行 IGWEB 演示了。

运行 IGWEB 演示

您可以通过先将目录更改为$IGWEB_APP_ROOT目录,然后发出go run命令来运行 IGWEB 服务器实例:

$ cd $IGWEB_APP_ROOT
$ go run igweb.go

您可以通过 web 浏览器访问http://localhost:8080/index link访问 IGWEB 网站。您应该可以看到该网站的主页,如图 2.8所示:

图 2.8:IGWEB 主页

安装过程的最后一步是加载带有示例数据集的本地 Redis 实例。

加载示例数据集

提供的示例数据集用于填充产品列表和关于页面的数据。您可以通过访问http://localhost:8080/products访问浏览器中的产品列表页面,您应该会看到如图 2.9 所示的屏幕:

图 2.9:带有加载示例数据集的消息的空产品部分

继续并单击网页上显示的链接以加载示例数据集。当您点击该链接时,您会看到如图 2.10所示的屏幕:

图 2.10:样本数据集已加载的确认

现在如果您回到产品列表页面,您会看到页面上显示的产品,如图 2.11所示:

图 2.11:产品部分,用产品填充

我们现在已经启动并运行了 IGWEB 演示!

每次我们想要对服务器端 Go 应用进行更改时,我们都需要发出一个go build命令并重新启动 web 服务器实例。类似地,每次对客户端 Go 应用进行更改时,我们都必须发出gopherjs build命令。在我们深入开发的同时,不断地发出这些命令,可能会变得单调乏味,效率低下。kick命令为我们提供了提高生产力的手段。

用踢

kick命令是一种轻量级机制,为 Go web 服务器实例提供即时启动。当应用的项目目录(或其任何子目录)中的 Go 源文件发生更改时,会发生即时启动

kick命令为我们提供了一种自动化开发工作流程的方法,只要我们对 Go 源文件进行更改,就可以重新编译 Go 代码并重新启动 web 服务器。

kick提供的工作流程类似于使用动态脚本语言(如 PHP)开发 web 应用,只要对 PHP 源文件进行更改,在浏览器中刷新网页时,更改就会立即反映出来。

在这个问题空间中,kick与其他基于 Go 的解决方案的区别在于,它在执行 instant kickstart时同时考虑了gogopherjs命令。它还考虑了对模板文件所做的更改,使其成为同构 web 应用开发的便捷工具。

安装踢

要安装kick,我们只需发出以下go get命令:

$ go get -u github.com/isomorphicgo/kick

跑腿

要了解如何使用kick,您可以像下面这样发出help命令行标志:

$ kick --help

--appPath标志指定 Go 应用项目的路径。--gopherjsAppPath标志指定 GopherJS 项目的路径。--mainSourceFile标志指定 Go 应用项目目录中包含main功能实现的 Go 源文件的名称。如果您仍在使用终端窗口中的go run命令运行 IGWEB,则是时候退出程序并使用kick运行它了。

要使用kick运行 IGWEB 演示,我们发出以下命令:

$ kick --appPath=$IGWEB_APP_ROOT --gopherjsAppPath=$IGWEB_APP_ROOT/client --mainSourceFile=igweb.go

验证井涌是否正常工作

让我们和网络检查员一起打开关于页面(http://localhost:8080/about。注意 web 控制台中显示 IGWEB 客户端应用的消息,如图 2.12所示:

图 2.12:在 web 控制台中打印的消息

让我们打开位于client目录中的client.go源文件。让我们用下面的一行替换run函数中的第一行:

println("IGWEB Client Application - Kamesh just made an update.")

保存文件并查看正在运行kick的终端窗口,您应该能够看到出现以下消息:

Instant KickStart Applied! (Recompiling and restarting project.)

这是来自kick的确认,它已经检测到文件的更改,并且已经执行了 instant kickStart。现在,让我们重新加载网页,您应该能够看到更新的消息,如图 2.13所示:

图 2.13:修改后的消息在 web 控制台中打印

现在,您已经使用kick在您的机器上成功运行了 IGWEB 演示,接下来将介绍该项目

IGWEB 演示简介

IGWEB 是一家虚构的技术初创公司,由三个虚构的地鼠创建,他们想使用同构 Go 在 web 上构建一个简单的店面演示。这些有进取心的地鼠的想法是,把在车库/院子里出售的普通二手产品放到网上销售。这组 Gopher 选择在同构 Go 中实现 IGWEB 演示,不仅可以提供增强的用户体验,还可以获得更大的搜索引擎发现能力。如果您还没有猜到,IGWEB 只是代表同构的 Go web 应用

从头开始构建 IGWEB

为了理解构建同构 web 应用所涉及的基本概念,我们将在创建 IGWEB 时遵循惯用的 Go 方法。我们将利用从标准库中的包以及第三方包中找到的功能。

如果您有使用 web 框架开发 web 应用的经验,您可能会想知道我们为什么采用这种方法。在撰写本文时,还没有基于 Go 的 web 框架提供现成的功能,以创建符合上一章中介绍的同构 web 应用体系结构的 web 应用。

除此之外,web 框架通常涉及遵循一组特定于框架的规则和约定。我们的重点是概念性的,而不是特定的 web 框架。因此,我们的注意力将集中在创建同构 web 应用所涉及的基本概念上。

IGWEB 路线图

在构建 IGWEB 演示网站的每个部分和功能的过程中,我们将了解有关同构 Go 的更多信息。下面是 IGWEB 主要部分/功能的路线图,以及本书中相应的章节,我们在其中实现了特定的部分或功能。

主页

除了包含特色产品的图像旋转木马和多个实时时钟外,IGWEB 主页还包含一个部分,其中包含独立前端编码示例的链接。

独立示例包括各种前端编程示例、使用 GopherJS 的内联模板渲染示例和本地存储检查器。这些示例将在第 3 章中介绍,与 GopherJS一起进入前端。图像转盘和实时时钟将在第 9 章Cogs–可重复使用组件中介绍。

主页位置:http://localhost:8080/index

关于页面

我们的地鼠团队希望通过在 IGWEB 的“关于”页面上展示,让全世界都能看到。在实现这一点的过程中,我们将学习同构模板渲染以及跨环境共享模板、模板数据和模板函数的能力。

关于页面将在第 4 章同构模板中介绍。

关于页面的位置:http://localhost:8080/about

产品页面

“产品列表”页面显示了将在 IGWEB 网站上出售的可用产品。每个产品都有产品标题、图像缩略图预览、产品价格和简短说明。点击产品图片会将用户带到产品详细信息页面,在那里用户可以了解有关该特定产品的更多信息。通过实现产品列表和产品详细信息页面,我们将了解同构 Go 中的端到端应用路由。

产品页面将在第 5 章端到端路由中介绍。

产品页面位置:http://localhost:8080/products

购物车功能

“产品”页面中显示的每个产品卡都将包含“添加到购物车”按钮。该按钮也将出现在产品的详细信息页面上。我们将学习如何在购物车上执行添加和删除操作时维护购物车的状态。

购物车功能将在第 6 章同构切换中介绍。

地点:http://localhost:8080/shopping-cart

联系人页面

联系页面将提供联系 IGWEB 的地鼠团队的方法。在实现联系人表单的过程中,我们将了解如何实现跨环境共享验证逻辑的同构 web 表单。除此之外,我们还将了解 web 表单如何弹性工作,即使在 web 浏览器中禁用 JavaScript 的情况下也是如此。

联系人页面将在第 7 章同构网络表单中介绍。联系人表单时间敏感性输入字段的日期选择器cog将在第 9 章Cogs–可重用组件中介绍。

联系人页面位置:http://localhost:8080/contact

实时聊天功能

在需要更多用户交互的情况下,网站用户可以使用实时聊天机器人。在构建实时聊天功能的过程中,我们将了解实时 web 应用的功能。直播聊天功能将在第 8 章实时网络应用功能中介绍。

单击网页右上角的 live chat 图标可激活 live chat 功能。

可再用元件

我们将通过实现各种可重用组件(如实时时钟和图像旋转木马)返回主页,这些组件的特点是 IGWEB 上提供的产品。我们还将为 Contact 页面构建一个日期选择器cog,为 About 页面构建一个 time ago cog。时间间隔 cog 将以人类可读的格式表示时间。我们还将介绍如何实现 notify cog,它用于向用户显示通知消息。

可重复使用组件将在第 9 章Cogs–可重复使用组件中介绍。

项目结构和代码组织

IGWEB 项目的代码可以在igweb文件夹中找到,它被组织到以下文件夹中(按字母顺序列出):

botchatclientcarsdemochatcommongopherjsprimerhandlerslocalstoragedemotestscommondatastoreendpointshandlersscriptssharedcogsformsmodelstemplatestemplatedatatemplatefuncsvalidatestaticcssfontsimagesjstemplatessubmissionstests

bot文件夹包含实现实时聊天功能聊天机器人的源文件。

chat文件夹包含实现实时聊天功能聊天服务器的服务器端代码。

client文件夹包含客户端 Go 程序,该程序将使用 GopherJS 传输到 JavaScript 中。

client/carsdemo包含一个独立的示例,演示使用 GopherJS 的内联模板呈现。本例将在第 3 章中介绍,与 GopherJS一起进入前端。

client/chat文件夹包含实现聊天客户端的客户端代码。

client/common文件夹包含实现整个客户端应用中使用的通用功能的客户端代码。

client/gopherjsprimer包含独立的 GopherJS 示例,将在第 3 章中介绍,与 GopherJS一起进入前端。

client/handlers文件夹包含客户端路由/页面处理程序。这些处理程序负责处理客户端上的页面路由,防止整个页面重新加载。他们还负责处理给定网页上发生的所有客户端用户交互。

client/localstoragedemo包含本地存储检查器的实现,将在第 3 章与 GopherJS一起在前端进行介绍。

client/tests文件夹包含端到端测试,用于执行客户端功能。此文件夹由以下三个文件夹组成:client/tests/goclient/tests/jsclient/tests/screenshotsgo子文件夹包含 CasperJS 测试,这是模拟用户与 Go 中实现的网站交互的自动测试。运行scripts文件夹中的build_casper_tests.shbash 脚本,将每个 Go 源文件转换为其等效的 JavaScript 表示形式,并存储在js子文件夹中。运行 CasperJS 测试后,屏幕截图将生成并保存在screenshots子文件夹中。

The common folder contains the server-side code that implements the common functionality used throughout the server-side application.

common/datastore文件夹包含实现 Redis 数据存储以满足应用数据持久性需求的服务器端代码。

endpoints文件夹包含 Rest API 端点的服务器端代码,这些端点负责为 web 客户端发出的 XHR 调用提供服务。

handlers文件夹包含负责维护特定路由的服务器端路由处理程序函数的服务器端代码。这些处理函数的主要职责是将网页响应发送回客户端。它们用于初始 web 页面加载,其中 web 页面响应使用经典 web 应用体系结构在服务器端呈现。

scripts文件夹包含要在命令行上运行的方便的 bashshell 脚本。

shared文件夹包含在服务器和客户端之间共享的同构代码。通过查看此文件夹,我们可以了解可以跨环境共享的所有 Go 代码。

shared/cogs文件夹包含可重用组件(COG),这些组件在服务器端注册并部署在客户端。

shared/forms文件夹包含同构的 web 表单。

shared/models文件夹包含我们用于在同构 web 应用中建模数据的同构类型(结构)。

shared/templates文件夹包含可跨环境渲染的同构模板。

shared/templatedata文件夹包含在渲染时提供给同构模板的同构数据对象。

shared/templatefuncs文件夹包含可跨环境使用的同构模板函数。

shared/validate文件夹包含通用的同构验证逻辑,web 表单可以跨环境使用这些逻辑。

static文件夹包含同构 web 应用的所有静态资产。

static/css文件夹包含 CSS 样式表源文件。

static/fonts文件夹包含 web 应用使用的自定义字体。

static/images文件夹包含 web 应用使用的图像。

static/js文件夹包含 web 应用的 JavaScript 源代码。

submissions文件夹的存在是为了进行说明。该文件夹包含submissions包,其中包含在 web 表单成功清除 web 表单验证过程后调用的逻辑。

tests文件夹包含用于执行服务器端功能的端到端测试

MVC 模式

IGWEB 的项目代码库可以被概念化为以下的模型视图控制MVC模式。MVC 模式在 web 应用的创建中被大量使用,如图 2.14所示:

图 2.14:模型视图控制器模式

基于 MVC 的应用中有三个主要组件:模型、视图和控制器。模型的主要目的是向应用提供数据和业务规则。将模型视为应用数据需求的看门人。IGWEB 的模型可以在shared/models文件夹中找到。

视图负责用户看到的输出。视图的重点是以对用户有意义的方式呈现模型,并将其呈现到用户界面中。IGWEB 中的视图作为模板存在于shared/templates文件夹中。

控制器实现系统的应用逻辑,它们基本上告诉应用它应该如何运行。您可以将控制器概念化为模型和应用视图之间的代理。控制器接受来自视图的用户输入,并且可以访问或修改模型的状态。控制器还可以更改视图当前显示的内容。IGWEB 中的服务器端控制器是在handlers文件夹中找到的路由处理程序。IGWEB 中的客户端控制器是在client/handlers目录中找到的路由/页面处理程序。

在阅读本书中的示例时,请注意,以相对方式提及的所有文件夹都是相对于igweb文件夹的

既然我们已经确定了 IGWEB 项目的代码是如何组织的,我们就可以开始实现构成同构 Go web 应用的各个部分和功能了。

自定义数据存储

IGWEB 演示网站已经实现了自定义数据存储。尽管我们将使用 Redis 作为本书的独家数据库,但事实是,只要您创建一个实现Datastore接口的自定义数据存储,您就可以自由使用您心中想要的任何数据库。

让我们检查一下在common/datastore文件夹中找到的datastore.go源文件中定义Datastore接口的部分:

type Datastore interface {
  CreateGopherTeam(team []*models.Gopher) error
  GetGopherTeam() []*models.Gopher
  CreateProduct(product *models.Product) error
  CreateProductRegistry(products []string) error
  GetProducts() []*models.Product
  GetProductDetail(productTitle string) *models.Product
  GetProductsInShoppingCart(cart *models.ShoppingCart) []*models.Product
  CreateContactRequest(contactRrequest *models.ContactRequest) error
  Close()
}

我们将在各自章节中讨论Datastore接口的各个方法,这些方法涉及使用该方法的特定部分或特性。请注意,实现Datastore接口所需的最终方法是Close方法(以粗体显示)。Close方法确定数据存储如何关闭其连接(或耗尽其连接池)。

检查common/datastore文件夹中的redis.go源文件中的RedisDatastore实现,可以了解如何创建实现Datastore接口的自定义数据存储。

Moving further along in the datastore.go source file, we have defined the NewDatastore function, which is responsible for returning a new datastore:

const (
  REDIS = iota
)

func NewDatastore(datastoreType int, dbConnectionString string) (Datastore, error) {

  switch datastoreType {

 case REDIS:
 return NewRedisDatastore(dbConnectionString)

  default:
    return nil, errors.New("Unrecognized Datastore!")

  }
}

我们的数据存储解决方案是灵活的,因为我们可以将 Redis 数据存储与任何其他数据库交换,只要我们的新自定义数据存储实现了Datastore接口。请注意,我们已经使用iota枚举器在常量分组中定义了REDIS常量(以粗体显示)。检查NewDatastore函数,注意在datastoreType上的switch块中遇到REDIS案例时会返回一个新的RedisDatastore实例(以粗体显示)。

如果我们想添加对另一个数据库(如 MongoDB)的支持,我们只需向常量分组添加一个新的常量条目MONGODB。除此之外,我们还将为 MongoDB 的NewDatastore函数中的switch块添加一个额外的case语句,该语句返回一个NewMongoDataStore实例,将连接字符串提供给 MongoDB 实例作为该函数的输入参数。NewMongoDBDatastore函数将返回自定义数据存储类型MongoDBDataStore的实例,该实例将实现Datastore接口

以这种方式实现自定义数据存储的一个巨大好处是,我们可以防止对特定数据库的特定于数据库驱动程序的调用扰乱我们的 web 应用。使用自定义数据存储,我们的 web 应用对数据库变得不可知,并为我们处理数据访问和数据存储需求提供了更大的灵活性。

GopherFace web 应用来自 Go 视频系列的 web 编程,它实现了 MySQL、MongoDB 和 Redis 的自定义数据存储。使用这些数据库的自定义数据存储示例见https://github.com/EngineerKamesh/gofullstack/tree/master/volume2/section5/gopherfacedb/common/datastore

依赖注入

服务器端应用的主要入口点是igweb.go源文件中定义的main函数。客户端应用的主要入口点是client/client.go源文件中定义的main函数。在这两个主要入口点中,我们利用依赖项注入技术在整个 web 应用中共享一个公共功能。通过这样做,我们避免了使用包级别的全局变量。

在服务器端和客户端,我们在common包中实现了一个自定义Env类型。您可能会认为,ORT T2A.代表从 AutoT3 应用环境 To4T4 访问的公共功能。

以下是服务器端的Env结构声明,可以在common/common.go源文件中找到:

package common

import (
  "github.com/EngineerKamesh/igb/igweb/common/datastore"
  "github.com/gorilla/sessions"
  "github.com/isomorphicgo/isokit"
)

type Env struct {
 DB datastore.Datastore
 TemplateSet *isokit.TemplateSet
}

The DB field will be used to store the custom datastore object.

TemplateSet字段是指向TemplateSet对象的指针。模板集允许我们以灵活的方式跨环境呈现模板,我们将在第 4 章同构模板中详细介绍这些模板。

Store字段是指向sessions.FilesystemStore对象的指针。我们将使用 Gorilla 工具包中的sessions包进行会话管理。

igweb.go源文件的main函数中,我们将声明一个env变量,一个common.Env类型的对象:

  env := common.Env{}

我们分别为env对象的 DB 和 TemplateSet 字段分配一个新创建的RedisDatastore实例和一个新创建的TemplateSet实例(分配以粗体显示)。为了便于说明,我们省略了一些代码,并在此处显示了部分代码列表:

  db, err := datastore.NewDatastore(datastore.REDIS, "localhost:6379")
  ts := isokit.NewTemplateSet()

 env.TemplateSet = ts
 env.DB = db

我们将使用 Gorilla Mux 路由器满足服务器端路由需求。请注意,我们将对env对象的引用作为输入参数(以粗体显示)传递给registerRoutes函数:

func registerRoutes(env *common.Env, r *mux.Router) {

我们通过将对env对象的引用作为我们为特定路由注册的路由处理程序函数的输入参数,将env对象传播到我们的请求处理程序函数,如下所示:

r.Handle("/index", handlers.IndexHandler(env)).Methods("GET")

通过调用 Gorilla Mux 路由器的Handle方法,我们已经注册了/index路由,并且我们已经将handlers包中的IndexHandler函数关联为服务于该路由的函数。我们提供了对env对象的引用,作为该函数的唯一输入参数(以粗体显示)。此时,我们已经成功地传播了RedisDatastoreTemplateSet实例,并将它们提供给IndexHandler函数。

我们来看看handlers/index.go源文件中定义的IndexHandler函数的源代码:

package handlers

import (
  "net/http"

  "github.com/EngineerKamesh/igb/igweb/common"
  "github.com/EngineerKamesh/igb/igweb/shared/templatedata"
  "github.com/isomorphicgo/isokit"
)

func IndexHandler(env *common.Env) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    templateData := templatedata.Index{PageTitle: "IGWEB"}
    env.TemplateSet.Render("index_page", &isokit.RenderParams{Writer: w, Data: templateData})
  })
}

请注意,handler函数的处理程序逻辑被放置在一个闭包中,我们已经对env变量进行了闭包。这允许我们满足处理程序函数应该返回一个http.Handler的要求,同时,我们可以向处理程序函数提供对env对象的访问。

这种方法的好处是,我们可以通过检查函数的输入参数(以粗体显示),明确地看到此处理程序函数需要env对象正常运行,而不是使用包级全局变量。

我们在客户端也遵循类似的依赖注入策略。以下是在client/common/common.go源文件中找到的客户端Env类型的声明:

package common

import (
 "github.com/isomorphicgo/isokit"
 "honnef.co/go/js/dom"
)

type Env struct {
 TemplateSet *isokit.TemplateSet
 Router *isokit.Router
 Window dom.Window
 Document dom.Document
 PrimaryContent dom.Element
 Location *dom.Location
}

我们在客户端声明的Env类型与在服务器端声明的Env类型不同。这是可以理解的,因为我们希望在客户端访问一组不同的通用功能。例如,没有RedisDatastore存在于客户端。

我们以与服务器端相同的方式声明了TemplateSet字段。因为*isokit.TemplateSet类型是同构的,所以它可以同时存在于服务器端和客户端。

Router字段是指向客户端isokit.Router实例的指针。

Window字段是Window对象,Document字段是Document对象。

PrimaryContent字段表示我们将在客户端向其呈现页面内容的div容器。我们将在第 4 章同构模板中更详细地介绍这些字段的作用。

Location字段用于Window对象的Location对象。

client.go源文件中定义的registerRoutes函数中,我们使用isokit.Router来处理客户端路由需求。我们将env对象传播到客户端处理程序函数,如下所示:

  r := isokit.NewRouter()
  r.Handle("/index", handlers.IndexHandler(env))

我们来看看客户端IndexHandler函数的源代码,在client/handlers/index.go源文件中定义:

func IndexHandler(env *common.Env) isokit.Handler {
  return isokit.HandlerFunc(func(ctx context.Context) {
    templateData := templatedata.Index{PageTitle: "IGWEB"}
    env.TemplateSet.Render("index_content", &isokit.RenderParams{Data: templateData, Disposition: isokit.PlacementReplaceInnerContents, Element: env.PrimaryContent, PageTitle: templateData.PageTitle})
  })
}

我们为这个处理函数提供对env对象(以粗体显示)的访问的方式与我们在服务器端的方式相同。handler 函数的 handler 逻辑被放入一个闭包中,我们已经在env变量上完成了闭包。这允许我们满足客户端处理函数返回一个isokit.Handler的要求,同时我们可以提供处理函数对env对象的访问。

我们在这里使用的依赖注入技术受到了 Alex Edwards 关于组织数据库访问的博客文章中阐述的技术的启发 http://www.alexedwards.net/blog/organising-database-access

总结

在本章中,我们向您介绍了安装同构 Go 工具链的过程。我们向您介绍了 IGWEB 项目,这是我们将在本书中实现的同构 web 应用。我们还研究了 IGWEB 代码库的项目结构和代码组织。

我们向您展示了如何设置数据存储并将示例数据集加载到 Redis 实例中。我们演示了如何使用kick执行即时启动以加快 web 应用开发周期。我们还为 IGWEB 项目的特性和功能的实现提供了路线图,并包括将要讨论的各个章节。最后,我们演示了依赖项注入技术,以在整个 web 应用中共享通用功能,包括服务器端和客户端。

现在我们已经准备好了工具,我们需要很好地理解在 web 浏览器中使用 Go。在第 3 章中,我们将在 GopherJS的前端进一步详细探讨 GopherJS,并学习如何使用 GopherJS 执行常见的 DOM 操作。