微服务架构的灵活性和可扩展性使其成为构建现代应用程序的热门选择。本文中,我们将使用 Go 语言 (Golang) 和 API 网关模式,逐步构建一个简单的在线商店微服务后端。

学习目标

  • 理解并实现 API 网关模式
  • 使用 gRPC 进行高效的服务间通信
  • 利用 Docker 部署多个微服务
  • 将各个组件整合为一个完整的系统

为什么选择微服务、Go 和 API 网关?

  • 微服务: 独立部署、可扩展性强、灵活性高,更易于维护和更新。
  • Go: 语法简洁、性能出色、并发能力强,非常适合构建微服务。
  • API 网关: 为所有客户端请求提供统一入口,简化客户端与后端服务的交互,并提供路由、负载均衡等功能。

1. 环境搭建

2. 项目结构

/online-store
├── api-gateway/
├──── cmd/
├────── main.go
├──── internal/
├────── handler/
├──────── handler.go
├── services/
├──── user-service/
├────── cmd/
├──────── main.go
├────── internal/
├──────── handler/
├────────── handler.go
├────── proto/
├──────── user.proto
├────── Dockerfile
├──── <其他服务>
├── docker-compose.yml
└── README.md

在根目录下克隆 googleapis 仓库,用于 proto 文件:

git clone https://github.com/googleapis/googleapis.git

3. 构建用户服务

3.1 初始化 Go Modules

go mod init user-service

3.2 创建 Proto 文件

user-service/proto 目录下创建 user.proto 文件,定义用户服务:

syntax = "proto3";

package user;

option go_package = ".";

import "google/api/annotations.proto";

service UserService {
    rpc GetUser(GetUserRequest) returns (GetUserResponse) {
        option (google.api.http) = {
            get: "/v1/users/{id}"
        };
    }

    rpc GetUserProfile(GetUserProfileRequest) returns (GetUserProfileResponse) {
        option (google.api.http) = {
            get: "/v1/users/{id}/profile"
        };
    }
}

message GetUserRequest {
    string id = 1;
}

message GetUserResponse {
    string id = 1;
    string name = 2;
    string email = 3;
}

message GetUserProfileRequest {
    string id = 1;
}

message GetUserProfileResponse {
    string id = 1;
    string name = 2;
    string email = 3;
    string phone = 4;
    string address = 5;
}

3.3 生成 gRPC 代码

service/user-service 目录下创建 pb 文件夹,并执行以下命令生成 gRPC 代码:

protoc --proto_path="services/user-service/proto" \
        --go_out="services/user-service/pb" \
        --go-grpc_out="services/user-service/pb" \
        --grpc-gateway_out="services/user-service/pb" \
        "services/user-service/proto/user.proto"

为了在 API 网关中复用 gRPC 代码,我们需要将其复制到 api-gateway/pb 目录。为了避免手动复制,我们在 online-store/scripts 目录下创建 generate-proto.sh 脚本:

#!/bin/bash

# ... (错误处理代码,与原文一致) ...

# 定义 proto 目录和对应 pb 目录的映射关系
declare -A dir_map=(
  ["services/user-service/proto"]="services/user-service/pb"
  # 可以添加其他服务
  # 例如: ["services/order-service/proto"]="services/order-service/pb"
)

# ... (其他代码,与原文一致) ...

执行脚本:

./scripts/generate-proto.sh

安装必要的包:

go get github.com/grpc-ecosystem/grpc-gateway/v2
go get google.golang.org/genproto/googleapis/api
go get google.golang.org/protobuf
go get google.golang.org/grpc

如果遇到导入错误,请执行 go mod tidy

3.4 实现用户服务处理逻辑

services/user-services/internal/handler 目录下创建 user-handler.go 文件,实现用户服务逻辑:

package handler

// ... (导入语句,与原文一致) ...

// ... (server 结构体和 NewServer 函数,与原文一致) ...

// GetUser 获取用户信息
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    // ... (日志记录和业务逻辑,与原文一致) ...
}

// GetUserProfile 获取用户详细信息
func (s *server) GetUserProfile(ctx context.Context, req *pb.GetUserProfileRequest) (*pb.GetUserProfileResponse, error) {
    // ... (日志记录和业务逻辑,与原文一致) ...
}

// RegisterServices 注册 gRPC 服务
func RegisterServices(s *grpc.Server) {
    pb.RegisterUserServiceServer(s, NewServer())
}

3.5 编写用户服务主程序

user-service/cmd/main.go 中编写用户服务主程序:

package main

// ... (导入语句,与原文一致) ...

func main() {
    // ... (创建 gRPC 服务器,与原文一致) ...

    // 注册服务
    handler.RegisterServices(s)

    // 注册反射服务
    reflection.Register(s)

    // ... (监听端口并启动服务,与原文一致) ...
}

安装 google.golang.org/grpc/reflection 包,以便查看服务信息。

3.6 创建 Dockerfile

user-service 目录下创建 Dockerfile 文件:

# ... (Dockerfile 内容,与原文一致) ...

4. 构建 API 网关

4.1 注册服务

api-gateway/internal/handler 目录下创建 service-registry.go 文件,注册用户服务:

package handler

// ... (导入语句,与原文一致) ...

// ... (ServiceConfig 结构体,与原文一致) ...

// RegisterServices 注册所有服务
func RegisterServices(ctx context.Context, mux *runtime.ServeMux, services []ServiceConfig) error {
    // ... (遍历服务配置,与原文一致) ...

        switch svc.Name {
        case "UserService":
            err = pb.RegisterUserServiceHandlerFromEndpoint(ctx, mux, svc.Address, opts)
        // 可以添加其他服务的 case 语句
        // 例如: case "OrderService": ...
        default:
            // ... (处理未知服务,与原文一致) ...
        }

    // ... (处理错误和日志记录,与原文一致) ...
}

api-gateway 目录下安装必要的包:

go get github.com/grpc-ecosystem/grpc-gateway
go get google.golang.org/grpc

4.2 编写 API 网关主程序

api-gateway/cmd/main.go 中编写 API 网关主程序:

package main

// ... (导入语句,与原文一致) ...

func main() {
    // 定义服务配置
    services := []handler.ServiceConfig{
        {Name: "UserService", Address: "user-service:50051"},
        // 可以添加其他服务
        // 例如: {Name: "OrderService", Address: "order-service:50052"},
    }

    // ... (创建上下文和 ServeMux,与原文一致) ...

    // 注册服务
    if err := handler.RegisterServices(ctx, mux, services); err != nil {
        // ... (处理错误,与原文一致) ...
    }

    // ... (启动 HTTP 服务器,与原文一致) ...
}

4.3 创建 Dockerfile

api-gateway 目录下创建 Dockerfile 文件:

# ... (Dockerfile 内容,与原文一致) ...

4.4 创建 docker-compose.yml

online-store 目录下创建 docker-compose.yml 文件:

version: '4.0'
services:
  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    depends_on:
      - user-service
  user-service:
    build: 
      context: ./services/user-service
      dockerfile: Dockerfile
    ports:
      - "50051:50051"
  # 可以添加其他服务
  # 例如:
  # order-service:
  #   ...

5. 启动微服务

确保 Docker 正在运行,执行以下命令启动服务:

docker-compose up --build -d

可以使用以下命令查看正在运行的服务:

docker ps

6. 测试 API

可以使用 Postman 等工具测试用户服务 API:

  • GET http://localhost:8080/v1/users/{id}
  • GET http://localhost:8080/v1/users/{id}/profile

可以使用以下命令查看用户服务的日志:

docker logs --follow <user-service-container-id>

<user-service-container-id> 替换为实际的容器 ID,可以通过 docker ps 命令查看。

总结

本文介绍了如何使用 Go 语言、gRPC 和 Docker 构建一个简单的在线商店微服务后端,并使用 API 网关模式简化客户端与后端服务的交互。