Golang中如何选择REST和gRPC进行API开发
Golang中如何选择REST和gRPC进行API开发 我正在尝试使用Go和gRPC构建REST API。这似乎不是构建REST API最简单的方法,但希望是最快的……
这方面的基础内容几乎到处都有涉及,但我还没找到任何清晰的路线图或简单教程。目前我认为可能包含四个部分:
- 网页(基于文本的AJAX-json?)
- 桥接代理(Envoy或Nginx)- 文本到二进制的转换?
- 带端点的Go组件(go-web还是grpc-web?)
- PostgreSQL数据库
举个例子:在不使用Docker的情况下安装Envoy需要高超的技术能力。需要依赖Javascript、Python、Bazel等工具……
非常欢迎提供任何实现此方案的技巧、线索或路线图。
这真的可行吗?
先谢过了
更多关于Golang中如何选择REST和gRPC进行API开发的实战教程也可以访问 https://www.itying.com/category-94-b0.html
iegomez: 它内部会通过网关向gRPC服务器发起调用
那么网关不是服务器吗?那它是什么?
iegomez:
这些内容与我向你说明的问题完全无关。
感谢您的耐心解答。但对我来说,这关乎于理解全貌。您的解释让事情清晰多了。
嗯,但是你的"原始 Go Web 服务器 http://94.237.25.207:8080/"与 gRPC + HTTP 网关有什么关系?据我所知,你可能还运行着 10、20 或 30 个与我所讨论内容完全无关的其他 Go Web 服务器。
我在你的回答中看到了一些误解:Web服务器是客户(什么的客户?)?REST端与Web服务器是分开的?Go作为前端(我猜是使用gopherjs或wasm)?JSON解析速度取决于不相关的协议/技术?我对此不太理解。
iegomez:
现在,这绝不是关于如何实现这一点的指南
谢谢!但是我的SQL查询应该放在哪里?
你可以查看 https://github.com/brocaar/lora-app-server 并检查其结构。前端是一个 React 应用程序,通过 swagger 客户端与 REST API 进行通信,而该客户端又是使用 https://github.com/grpc-ecosystem/grpc-gateway 从 gRPC 生成的。
iegomez:
那么是2个服务器,而不是3个。
我也在使用Go语言作为网页服务器。如果我的网页在一个"网页服务器"上,而"grpc-gateway将创建一个HTTP服务器"。
我数出来是3个服务器:
- 原始的Go网页服务器 http://94.237.25.207:8080/
- 网关服务器(“grpc-gateway将创建一个HTTP服务器”)
- gRPC服务器
还是说网关服务器会取代我原来的服务器?
iegomez 提到: 你可以查看 https://github.com/brocaar/lora-app-server 并检查其结构。
这个拼图中还有很多我不理解的部分。每个组件具体负责什么功能,以及它们的执行顺序是怎样的。
我之前使用过传统的 REST API,它基本上包含两个部分:
浏览器
1. GET users
Go 服务器
2. 连接到 Postgresql
3. 监听并服务(启动服务器)
4. 路由到端点
5. 获取 SQL 数据
6. 将结果解析为 JSON 并返回给浏览器
如何将这些转换为 gRPC?我需要使用哪些组件?执行顺序是怎样的?
提前感谢任何能让这个问题更清晰的线索或链接。
iegomez:
我在你的回答中看到了一些误解:Web服务器是客户(什么的客户)?
这是一个比喻。顾客 > 服务员 > 厨师。这是向完全的新手(我)解释时非常常用的说法。这个比喻指的是:提出问题(下单)> 翻译成二进制 > 从数据库获取。或者 Web > 网关 > gRPC 服务器。
REST 端与 Web 服务器是分开的吗?
我无法理解你的问题……
Go 作为前端(我猜是使用 gopherjs 或 wasm)?
不。目标是使用普通的原生 JavaScript。
JSON 解析速度取决于不相关的协议/技术?我不太理解这一点。
嗯,你是在和一个新手说话。我可能还不太会提出有水平的问题……
好的,现在我明白了,我原以为您说的是一个独立的应用程序。如果您查看我之前展示了一堆示例文件的帖子,其中有这样一个部分用于为React前端提供静态文件服务:
// setup static file server
r.PathPrefix("/").Handler(http.FileServer(&assetfs.AssetFS{
Asset: static.Asset,
AssetDir: static.AssetDir,
AssetInfo: static.AssetInfo,
Prefix: "",
}))
实际上并不一定需要React或其他复杂的前端框架,Go的HTML模板完全够用。您只需要提供模板服务,然后通过JavaScript从这些模板向API端点发起ajax调用。因此,HTTP服务器将扮演两种角色:提供服务器端渲染的HTML,同时为ajax调用暴露REST API。所以没错,这些调用都是指向同一个应用程序的。
这是 grpc-gateway 的第一段描述:
grpc-gateway 是 Google 协议缓冲区编译器 protoc 的一个插件。它读取 protobuf 服务定义,并生成一个反向代理服务器,该服务器将 RESTful JSON API 转换为 gRPC。此服务器根据服务定义中的
google.api.http注解生成。
简而言之,grpc-gateway 将创建一个 HTTP 服务器,当收到任何请求时,该服务器将在底层调用 gRPC(因此得名 gateway)。所以是两个服务器,而不是三个。
如果之前表达有误,我的意思是说,您正在运行的任何其他面向网络的应用程序,无论是用Go、Ruby、Python等编写的,都完全独立于您正在尝试构建的这个特定的面向网络的应用程序,该应用程序恰好在其内部提供gRPC和HTTP服务,或者正如我们所说,实现了两个服务器:一个gRPC服务器,一个HTTP服务器。
回到核心问题,我认为一个好的总结是:
- 检查Go中的项目布局策略,更具体地说是Web项目,以便安排您的内部包(例如存储)。
- 问问自己为什么首先要使用gRPC。典型的Web应用程序有什么问题?您考虑使用gRPC的需求或要求是什么?您只是想学习gRPC吗?那为什么还需要HTTP API转换呢?
- 现在阅读并学习协议缓冲区和gRPC,以便定义和理解proto服务变得自然而然。
- 查看grpc-gateway仓库及其文档,以便熟悉它的功能和工作原理。
- 编写一些HTML模板或使用一些调用您API的前端库/框架。
- 休息。
Sibert:
这是一个比喻。顾客 > 服务员 > 厨师。这是向完全的新手(我)解释时非常常见的说法。它比喻的是:提出问题(下单)> 翻译成二进制 > 从数据库获取数据。或者 Web > 网关 > gRPC 服务器。
我明白了,只是没注意到这两个回答是相关的。无论如何,我仍然认为你有些困惑。其他网络应用程序并不属于整体架构的一部分,因为它们与我们讨论的应用没有任何关系:它们不是"顾客",也不会发出任何请求。请求来自网络客户端,由应用程序中的 HTTP 服务器处理,然后进行 gRPC 调用,依此类推。中间没有中介或其他组件参与。这并不是说不能有(例如,你可能使用了像 nginx 这样的反向代理),但并不是必需的。
作为"新手"并没有问题,我们都曾是新手,只是我对你提出的一些问题感到困惑。例如,你如何在前端运行 Go 并声称它比 Angular 快两倍?在这种情况下,你所说的"前端"是指什么?
再次强调,这只是其中一种实现方式。在这个特定项目中,有一个内部的
api包和另一个storage包,api实现会调用这些包来创建和消费数据(例如数据验证、执行必要查询等)。项目结构有很多种方式,论坛里也讨论过几次(可以搜索项目布局或类似关键词)。
在本论坛搜索"项目布局 grpc"只返回了1个结果。这个问题 🙂
但我有点只见树木不见森林。所以我尝试从宏观视角来理解。以下是我的理解:
根据我的解读,这需要3个服务器?Web服务器、网关服务器,最后是gRPC服务器。对吗?
浏览器(Web服务器)
1. GET users
网关服务器(负责JSON和二进制格式之间的转换)?
6. 将结果解析为JSON并返回给浏览器
gRPC服务器
2. 连接到Postgresql
3. 监听并服务(启动服务器)
4. 路由到端点
5. 存储SQL和获取SQL数据
我猜想网关和gRPC服务器使用的是相同的结构体?(通过proto之类的工具神秘生成的)
我的理解正确吗?如果有误请指正!
Sibert:
在论坛中搜索"项目布局 grpc"只得到1个结果。这个问题 🙂
我指的是通用的Go项目,不是特指gRPC。你看,无论是gRPC还是HTTP API,都不应该改变你抽象和访问数据层的方式,对吧?在这种情况下,如果你去掉其中任何一个,数据模型的internal/storage/...结构都会保持不变。
Sibert:
按照我的理解,这需要3个服务器?Web服务器、网关服务器,最后是gRPC服务器。对吗?
实际上不是,只有2个服务器:gRPC服务器和HTTP服务器。JSON API只是你传递给HTTP服务器的处理程序,它本身不是一个服务器。
Sibert:
我猜想网关和gRPC服务器都使用相同的结构体?
再次说明,网关只是gRPC和JSON HTTP API之间的转换层:当你在HTTP服务器中收到HTTP请求时,它会通过网关内部调用gRPC服务器,获取其响应,然后发送HTTP响应。所以是的,数据结构是"共享的",因为逻辑实际上只实现了一次。
Sibert:
通过proto某种方式神秘地生成
抱歉如果表达有误,我的意思是说您正在运行的任何其他面向网络的应用程序,无论是用Go、Ruby、Python等语言编写的,都完全独立于您正在尝试构建的这个特定面向网络的应用程序——它碰巧同时提供gRPC和HTTP服务,或者像我们所说的,实现了两个服务器:一个gRPC服务器,一个HTTP服务器。
要描述披萨订购的流程,您不能忽略顾客环节。这关乎理解整体架构,并非技术问题。
- 查阅Go项目的布局策略,特别是Web项目,以便合理组织内部包(例如存储包)。
对于"顾客"部分(Web服务器),我很清楚如何组织模板等。但在"REST"方面,如何存储和检索大量SQL查询仍不明确…
- 首先问自己为什么要使用gRPC。传统Web应用有什么问题?您有哪些需求或要求需要考虑?您只是想要学习gRPC吗?那为什么还需要HTTP API转换呢?
这是个很好的问题。我的最终应用将高度依赖与PostgreSQL服务器之间的数据流量。而速度是主要考量因素。我目前还没有足够经验在普通REST服务和gRPC之间做出选择。这两种方式在JSON解析速度上有差异吗?任何建议都将不胜感激。
- 现在阅读并学习协议缓冲区和gRPC,这样定义和理解proto服务就会变得很自然。
我认为协议缓冲区能半自动生成Go结构体,使用gRPC能更轻松地更新结构体变更。如果我说错了请指正。
- 查看grpc-gateway代码库及其文档,以便熟悉它的功能和工作原理。
我会去了解的…
- 编写一些HTML模板或使用某些前端库/框架来调用您的API。
我之前尝试过Angular和Go作为前端。Go的速度至少快两倍。所以在有更深入了解之前,我会坚持使用Go。
iegomez:
我明白了,只是没注意到这两个回答是相关的。无论如何,我仍然认为你有些困惑。
我同意 🙂
其他网络应用程序不属于整体架构的一部分,因为它们与我们讨论的内容无关:它们不是"客户",不会发出任何请求。请求来自网络客户端,由应用程序中的HTTP服务器处理,然后进行gRPC调用等等。
那么网关是集成到网络服务器中的吗?AJAX调用自己的服务器?也就是提供HTML页面的同一个服务器?
作为"新手"并没有问题,我们都是这样开始的,只是我对某些问题感到困惑。
新手不知道如何提出正确的问题。作为专业人士,有时你会认为某些事情是理所当然的。
例如,你如何在前端运行Go,并声称它比Angular快两倍?在这种情况下,你所说的前端是什么意思?
这是我需要询问REST问题的测试前端服务器。基本上提供与Angular相同的内容。速度快两倍。
var tpl *template.Template
func init() {
tpl = template.Must(template.ParseGlob("public/templates/*.html"))
}
func main() {
http.HandleFunc("/", index)
http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./public/img"))))
http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./public/css"))))
http.Handle("/icn/", http.StripPrefix("/icn/", http.FileServer(http.Dir("./public/icn"))))
http.ListenAndServe(":8080", nil)
}
func index(w http.ResponseWriter, r *http.Request) {....
https://developers.google.com/speed/pagespeed/insights/?url=http%3A%2F%2F94.237.25.207%3A8080%2F
如果你查看用户API中的代码,会在Get方法中看到这样的内容:
user, err := storage.GetUser(storage.DB(), req.Id)
这只是调用了storage包的GetUser函数,在这个项目中位于lora-app-server/internal/storage/user.go文件,具体实现如下:
package storage
//一些导入和变量声明
//用户结构体
// User 对外部代码表示一个用户
type User struct {
ID int64 `db:"id"`
Username string `db:"username"`
IsAdmin bool `db:"is_admin"`
IsActive bool `db:"is_active"`
SessionTTL int32 `db:"session_ttl"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
PasswordHash string `db:"password_hash"`
Email string `db:"email"`
Note string `db:"note"`
}
const externalUserFields = "id, username, is_admin, is_active, session_ttl, created_at, updated_at, email, note"
const internalUserFields = "*"
/// GetUser 返回指定id对应的用户
func GetUser(db sqlx.Queryer, id int64) (User, error) {
var user User
err := sqlx.Get(db, &user, "select "+externalUserFields+" from \"user\" where id = $1", id)
if err != nil {
if err == sql.ErrNoRows {
return user, ErrDoesNotExist
}
return user, errors.Wrap(err, "select error")
}
return user, nil
}
再次说明,这只是其中一种实现方式。在这个特定项目中,有一个内部的api包和另一个storage包,api实现会调用storage包来创建和消费数据(即进行数据验证、执行必要查询等)。项目结构有很多种组织方式,论坛里也讨论过几次(可以搜索项目布局或类似关键词)。
在Go语言中同时使用REST和gRPC进行API开发是完全可行的,而且这种架构在现代微服务系统中很常见。让我通过具体示例来说明如何实现。
架构实现方案
1. 定义gRPC服务
首先定义protobuf文件 user_service.proto:
syntax = "proto3";
package user;
service UserService {
rpc GetUser(GetUserRequest) returns (UserResponse);
rpc CreateUser(CreateUserRequest) returns (UserResponse);
}
message GetUserRequest {
string user_id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message UserResponse {
string id = 1;
string name = 2;
string email = 3;
}
生成Go代码:
protoc --go_out=. --go-grpc_out=. user_service.proto
2. 实现gRPC服务端
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "path/to/your/proto"
)
type userServer struct {
pb.UnimplementedUserServiceServer
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
// 数据库查询逻辑
return &pb.UserResponse{
Id: req.UserId,
Name: "John Doe",
Email: "john@example.com",
}, nil
}
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.UserResponse, error) {
// 数据库插入逻辑
return &pb.UserResponse{
Id: "123",
Name: req.Name,
Email: req.Email,
}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, &userServer{})
log.Println("gRPC server listening on :50051")
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
3. 实现REST网关
使用grpc-gateway创建REST到gRPC的转换:
package main
import (
"context"
"log"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "path/to/your/proto"
)
func runRESTGateway() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err := pb.RegisterUserServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
if err != nil {
log.Fatalf("failed to register gateway: %v", err)
}
log.Println("REST gateway listening on :8080")
http.ListenAndServe(":8080", mux)
}
func main() {
go runRESTGateway()
// 同时运行gRPC服务
runGRPCServer()
}
4. 前端调用示例
前端可以通过REST API调用:
// 获取用户
fetch('http://localhost:8080/v1/users/123')
.then(response => response.json())
.then(data => console.log(data));
// 创建用户
fetch('http://localhost:8080/v1/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Jane Doe',
email: 'jane@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));
5. 数据库集成
集成PostgreSQL的示例:
import (
"database/sql"
_ "github.com/lib/pq"
)
type UserRepository struct {
db *sql.DB
}
func (r *UserRepository) GetUserByID(ctx context.Context, id string) (*pb.UserResponse, error) {
var user pb.UserResponse
err := r.db.QueryRowContext(ctx,
"SELECT id, name, email FROM users WHERE id = $1", id).
Scan(&user.Id, &user.Name, &user.Email)
if err != nil {
return nil, err
}
return &user, nil
}
部署方案
使用Nginx作为反向代理
http {
upstream grpc_servers {
server localhost:50051;
}
upstream rest_gateway {
server localhost:8080;
}
server {
listen 80;
location /api/ {
proxy_pass http://rest_gateway/;
}
location / {
# 静态文件服务
root /var/www/html;
}
}
server {
listen 50052 http2;
location / {
grpc_pass grpc://grpc_servers;
}
}
}
这种架构的优势在于:
- 内部服务间使用高效的gRPC通信
- 外部客户端使用熟悉的REST API
- 单一代码库维护两种接口
- 自动生成API文档和客户端代码
整个方案不需要Docker,可以直接在服务器上部署运行。

