在前一章中,我们介绍了同构 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 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 启动并运行您的系统将包括以下步骤:
- 安装围棋
- 设置 Go 工作区
- 构建和运行程序
下载完成后,继续并启动安装程序。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 工作区,然后才能继续。我们将提供关于设置围棋工作区的高级概述,如果您需要进一步帮助,您可以阅读围棋网站上关于设置围棋工作区的详细说明: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 程序的二进制文件将存在于何处。
让我们创建一个简单的“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 启动和运行包括以下步骤:
- 安装 GopherJS
- 安装基本的 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 绑定,这是我们前端 web 应用开发需要的:
- 多姆
- jsbuiltin
- xhr
- 网袋
dom
包为我们提供了 JavaScript DOM API 的 GopherJS 绑定。
我们可以通过发出以下命令来安装dom
包:
$ go get honnef.co/go/js/dom
jsbuiltin
包为常见 JavaScript 操作符和函数提供绑定。我们可以通过发出以下命令来安装jsbuiltin
包:
$ go get -u -d -tags=js github.com/gopherjs/jsbuiltin
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
命令与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 网站
来自同构 Go 工具包的isokit
包提供了一个通用的同构功能,可以在服务器端或客户端使用。该包提供的一些显著优点包括同构模板呈现、客户端应用路由、自动静态资产绑定以及创建同构 web 表单的能力。
我们可以通过发出以下go get
命令来安装isokit
包:
$ go get -u github.com/isomorphicgo/isokit
用户体验工具包(http://uxtoolkit.io 允许我们实现cogs,这是在 Go 中实现的可重用组件,可以跨组成 IGWEB 的网页使用。我们将在第 9 章、Cogs–可重用组件中介绍可重用组件。
我们可以通过发出以下go get
命令来安装cog
包:
$ go get -u github.com/uxtoolkit/cog
现在我们已经安装了同构 Go 工具链,是时候设置 IGWEB 演示了,这是我们将在本书中构建的同构 web 应用。
您可以通过发出以下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.js
和client.js.map
。client.js
源文件是 IGWEB 客户端 Go 程序的 JavaScript 表示。client.js.map
文件是源映射文件,web 浏览器将与client.js
一起使用,在 web 控制台中为我们提供详细信息,这在调试问题时非常方便。
现在我们已经为 IGWEB 的客户端应用传输了代码,接下来的逻辑步骤是构建和运行 IGWEB 的服务器端应用。在此之前,我们必须安装并运行本地 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_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时同时考虑了go
和gopherjs
命令。它还考虑了对模板文件所做的更改,使其成为同构 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 是一家虚构的技术初创公司,由三个虚构的地鼠创建,他们想使用同构 Go 在 web 上构建一个简单的店面演示。这些有进取心的地鼠的想法是,把在车库/院子里出售的普通二手产品放到网上销售。这组 Gopher 选择在同构 Go 中实现 IGWEB 演示,不仅可以提供增强的用户体验,还可以获得更大的搜索引擎发现能力。如果您还没有猜到,IGWEB 只是代表同构的 Go web 应用。
为了理解构建同构 web 应用所涉及的基本概念,我们将在创建 IGWEB 时遵循惯用的 Go 方法。我们将利用从标准库中的包以及第三方包中找到的功能。
如果您有使用 web 框架开发 web 应用的经验,您可能会想知道我们为什么采用这种方法。在撰写本文时,还没有基于 Go 的 web 框架提供现成的功能,以创建符合上一章中介绍的同构 web 应用体系结构的 web 应用。
除此之外,web 框架通常涉及遵循一组特定于框架的规则和约定。我们的重点是概念性的,而不是特定的 web 框架。因此,我们的注意力将集中在创建同构 web 应用所涉及的基本概念上。
在构建 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
文件夹中找到,它被组织到以下文件夹中(按字母顺序列出):
⁃ bot
⁃ chat
⁃ client
⁃ carsdemo
⁃ chat
⁃ common
⁃ gopherjsprimer
⁃ handlers
⁃ localstoragedemo
⁃ tests
⁃ common
⁃ datastore
⁃ endpoints
⁃ handlers
⁃ scripts
⁃ shared
⁃ cogs
⁃ forms
⁃ models
⁃ templates
⁃ templatedata
⁃ templatefuncs
⁃ validate
⁃ static
⁃ css
⁃ fonts
⁃ images
⁃ js
⁃ templates
⁃ submissions
⁃ tests
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/go
、client/tests/js
和client/tests/screenshots
。go
子文件夹包含 CasperJS 测试,这是模拟用户与 Go 中实现的网站交互的自动测试。运行scripts
文件夹中的build_casper_tests.sh
bash 脚本,将每个 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
文件夹包含用于执行服务器端功能的端到端测试
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
对象的引用,作为该函数的唯一输入参数(以粗体显示)。此时,我们已经成功地传播了RedisDatastore
和TemplateSet
实例,并将它们提供给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 操作。