本教程面向有一定Go基础但尚未深入微服务架构的开发者,系统讲解如何利用Claude Code CLI完成从单体拆分、gRPC通信、服务发现到Docker编排的全链路Go微服务开发。学完本教程,你将掌握Claude Code + Go的黄金工作流,独立交付一个包含4个微服务的电商订单系统。
适用人群:Go语言中级开发者、后端架构转型工程师、AI辅助编程实践者
预计教学时长:6-8小时(含实战项目)
| 工具 | 版本要求 | 安装命令 |
|---|---|---|
| Go | ≥ 1.22 | curl -sL https://go.dev/dl/go1.22.0.linux-amd64.tar.gz \| sudo tar -C /usr/local -xzf - |
| Claude Code CLI | 最新版 | npm install -g @anthropic-ai/claude-code |
| Docker + Compose | ≥ 24.0 | 参见Docker官方安装文档 |
| protoc + Go插件 | ≥ 3.21 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest |
| Git | ≥ 2.40 | 系统包管理器安装 |
基础知识要求:
- 熟悉Go语法、goroutine、channel
- 了解HTTP REST API开发
- 基本的Docker使用经验
- Claude Code基础操作(claude命令启动对话)
Go语言的工具链天然CLI-first:go build、go test、go vet、go run、air(热重载)、delve(调试器)。Go开发者本身就是终端重度用户,这与Claude Code的终端原生交互模型完美契合。
flowchart LR
A[Go工具链<br/>CLI-first] --> C[Claude Code<br/>终端原生]
B[Go编译型语言<br/>类型安全] --> C
C --> D[AI辅助<br/>完美融合]
D --> E[编译反馈 → 自动修复]
D --> F[测试失败 → 即时修正]
D --> G[vet/lint → 惯用法重构]
核心优势三连:
go build后能立即捕获类型错误、未使用导入等问题,并自动修复——这比解释型语言的反馈精确度高一个量级go vet作为AI护栏:go vet能检测可疑代码结构(死锁、错误格式化字符串等),Claude Code可以将其作为自动质量门禁CLAUDE.md是Claude Code读取项目上下文的核心文件。对于Go微服务项目,一份精准的CLAUDE.md能让Claude Code生成的代码符合Go惯用法、遵循项目架构约定。
在项目根目录创建CLAUDE.md:
# Go微服务电商系统 — 项目规范
## 技术栈
- Go 1.22+, gRPC + Protobuf, PostgreSQL 16, Redis 7
- 服务发现: Consul, 消息队列: NATS, 链路追踪: OpenTelemetry
- 容器编排: Docker Compose (开发), Kubernetes (生产)
## 项目结构(严格遵循)
├── cmd/ # 入口 main.go
│ ├── user-service/
│ ├── order-service/
│ ├── inventory-service/
│ └── api-gateway/
├── internal/ # 私有包,按服务分目录
│ ├── user/
│ ├── order/
│ └── inventory/
├── proto/ # .proto 文件
├── pkg/ # 公共库(可被外部引用)
│ ├── logger/
│ ├── database/
│ └── middleware/
├── deployments/ # Docker Compose / K8s manifests
├── scripts/ # 构建/迁移脚本
└── api/ # OpenAPI specs
## 编码规范
- 遵循 Effective Go 和 Go Code Review Comments
- 错误处理: 使用 fmt.Errorf("functionName: %w", err) 包装
- 命名: 接口名以 -er 后缀结尾(Repository, 不是 Repo)
- 依赖注入: 构造函数模式 NewService(repo Repository) *Service
- 禁止 panic,所有错误通过 return 传递
- context.Context 作为函数第一个参数
## 测试要求
- 单元测试覆盖率 ≥ 70%
- 使用 table-driven tests 模式
- Mock 使用 gomock 或 testify/mock
- 集成测试放在 _test.go 顶部的 //go:build integration 构建标签下
## gRPC规范
- Proto文件使用 buf.yaml 管理
- 每个服务一个 .proto 文件,放在 proto/ 目录
- Versioning: proto包名包含v1/v2后缀
## Docker规范
- 多阶段构建: golang:1.22-alpine 作为builder
- 最终镜像使用 scratch 或 distroless
- 健康检查: /health 端点
关键配置解读:
| 字段 | 作用 | 为什么对AI重要 |
|---|---|---|
项目结构 |
目录树约定 | Claude Code生成文件时知道路径 |
编码规范 |
Go惯用法约束 | 防止生成Java风格的Go代码 |
测试要求 |
测试策略 | Claude Code知道用table-driven模式 |
gRPC规范 |
Proto管理 | 保证proto文件组织一致 |
启动Claude Code进行服务拆分设计:
cd your-project
claude
关键Prompt:
我要将一个电商单体应用拆分为微服务架构。目前有以下模块:
- 用户管理(注册、登录、权限)
- 商品管理(CRUD、分类、搜索)
- 订单管理(创建、支付、发货)
- 库存管理(扣减、预警、补货)
请帮我:
1. 进行领域驱动设计,识别限界上下文
2. 定义服务间通信方式(同步gRPC vs 异步NATS)
3. 设计数据隔离策略
4. 画出服务依赖关系图
Claude Code会输出完整的架构设计方案。核心服务拆分结果:
graph TB
GW[API Gateway<br/>端口 :8080] --> US[User Service<br/>端口 :50001]
GW --> OS[Order Service<br/>端口 :50002]
GW --> IS[Inventory Service<br/>端口 :50003]
OS -->|gRPC: CheckStock| IS
OS -->|gRPC: GetUser| US
OS -->|NATS: OrderCreated| IS
US -->[(PostgreSQL<br/>users_db)]
OS -->[(PostgreSQL<br/>orders_db)]
IS -->[(PostgreSQL<br/>inventory_db)]
US -.->|Redis 缓存| R1[(Redis)]
IS -.->|Redis 缓存| R2[(Redis)]
OT[OpenTelemetry<br/>Collector] -.-> US
OT -.-> OS
OT -.-> IS
style GW fill:#4A90D9,color:#fff
style US fill:#50C878,color:#fff
style OS fill:#FF6B6B,color:#fff
style IS fill:#FFB347,color:#fff
服务间通信策略:
| 场景 | 通信方式 | 原因 |
|---|---|---|
| 查询用户信息 | gRPC同步 | 需要即时响应 |
| 校验库存 | gRPC同步 | 订单创建前必须确认 |
| 订单创建后扣减库存 | NATS异步 | 解耦,允许最终一致性 |
| 库存低于阈值 | NATS异步 | 触发补货流程 |
让Claude Code生成proto文件:
请为 order-service 生成 gRPC proto 定义,包含:
- CreateOrder:创建订单
- GetOrder:查询订单
- ListOrders:订单列表(支持分页)
- CancelOrder:取消订单
使用 Google protobuf well-known types(Empty, Timestamp, FieldMask),
遵循 proto3 语法,包含字段验证注解。
Claude Code生成 proto/order/v1/order.proto:
syntax = "proto3";
package order.v1;
option go_package = "github.com/yourorg/ecomm/proto/order/v1";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
import "validate/validate.proto";
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
rpc CancelOrder(CancelOrderRequest) returns (google.protobuf.Empty);
}
message CreateOrderRequest {
string user_id = 1 [(validate.rules).string.uuid = true];
repeated OrderItem items = 2 [(validate.rules).repeated.min_items = 1];
}
message OrderItem {
string product_id = 1 [(validate.rules).string.uuid = true];
int32 quantity = 2 [(validate.rules).int32.gte = 1];
}
message CreateOrderResponse {
string order_id = 1;
google.protobuf.Timestamp created_at = 2;
}
message GetOrderRequest {
string order_id = 1 [(validate.rules).string.uuid = true];
}
message Order {
string id = 1;
string user_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
google.protobuf.Timestamp created_at = 5;
google.protobuf.Timestamp updated_at = 6;
}
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_PAID = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_CANCELLED = 4;
}
message ListOrdersRequest {
string user_id = 1;
int32 page_size = 2 [(validate.rules).int32 = {gte: 1, lte: 100}];
string page_token = 3;
}
message ListOrdersResponse {
repeated Order orders = 1;
string next_page_token = 2;
}
message CancelOrderRequest {
string order_id = 1 [(validate.rules).string.uuid = true];
string reason = 2;
}
# 安装 buf 工具(比 protoc 更现代)
go install github.com/bufbuild/buf/cmd/buf@latest
# 生成Go代码
buf generate
让Claude Code基于生成的代码构建服务实现:
基于 proto/order/v1/order.proto 生成的Go代码,
请实现 OrderService 的完整服务端骨架,包含:
1. internal/order/repository.go — 数据库仓储接口
2. internal/order/service.go — 业务逻辑层
3. internal/order/handler.go — gRPC处理器
4. cmd/order-service/main.go — 服务入口
5. 使用 wire 或 fx 做依赖注入
遵循Go惯用法,错误通过 domain error type 包装返回。
Claude Code生成的核心文件(internal/order/service.go):
package order
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/yourorg/ecomm/internal/order/domain"
"github.com/yourorg/ecomm/pkg/logger"
)
// OrderRepository 定义订单数据访问接口
type OrderRepository interface {
Create(ctx context.Context, order *domain.Order) error
GetByID(ctx context.Context, id string) (*domain.Order, error)
ListByUser(ctx context.Context, userID string, offset, limit int) ([]*domain.Order, error)
Update(ctx context.Context, order *domain.Order) error
}
// InventoryClient 库存服务gRPC客户端接口
type InventoryClient interface {
CheckAndReserve(ctx context.Context, items []domain.OrderItem) error
Release(ctx context.Context, items []domain.OrderItem) error
}
// Service 订单业务逻辑
type Service struct {
repo OrderRepository
inventory InventoryClient
log *logger.Logger
}
// NewService 构造函数
func NewService(repo OrderRepository, inv InventoryClient, log *logger.Logger) *Service {
return &Service{repo: repo, inventory: inv, log: log}
}
// CreateOrder 创建订单 — 核心业务流程
func (s *Service) CreateOrder(ctx context.Context, userID string, items []domain.OrderItem) (*domain.Order, error) {
// 1. 参数校验
if userID == "" {
return nil, fmt.Errorf("CreateOrder: %w", domain.ErrInvalidInput("user_id is required"))
}
if len(items) == 0 {
return nil, fmt.Errorf("CreateOrder: %w", domain.ErrInvalidInput("items cannot be empty"))
}
// 2. 检查并预留库存(gRPC同步调用)
if err := s.inventory.CheckAndReserve(ctx, items); err != nil {
return nil, fmt.Errorf("CreateOrder: inventory check: %w", err)
}
// 3. 创建订单领域对象
order := &domain.Order{
ID: uuid.New().String(),
UserID: userID,
Items: items,
Status: domain.StatusPending,
CreatedAt: time.Now(),
}
// 4. 持久化
if err := s.repo.Create(ctx, order); err != nil {
// 回滚库存
_ = s.inventory.Release(ctx, items)
return nil, fmt.Errorf("CreateOrder: repo create: %w", err)
}
s.log.Info("order created", "order_id", order.ID, "user_id", userID)
return order, nil
}
| 问题 | 原因 | 解决方案 |
|---|---|---|
protoc-gen-go 未找到 |
PATH缺少Go bin目录 | export PATH=$PATH:$(go env GOPATH)/bin |
| proto import循环 | 服务间proto互相引用 | 抽取公共proto到 proto/common/ |
| 生成的代码不符合项目结构 | go_package配置错误 |
检查proto文件的option go_package |
让Claude Code设计数据库Schema:
为订单微服务设计PostgreSQL数据库Schema:
1. orders表:包含JSONB类型的items字段存储订单项
2. 使用乐观锁(version字段)处理并发更新
3. 创建合适的索引支持常见查询
4. 提供迁移脚本(golang-migrate格式)
Claude Code生成 scripts/migrations/001_create_orders.up.sql:
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','paid','shipped','cancelled')),
items JSONB NOT NULL,
total DECIMAL(12,2) NOT NULL DEFAULT 0,
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 用户订单列表查询(最频繁)
CREATE INDEX idx_orders_user_id_created ON orders(user_id, created_at DESC);
-- 按状态查询(运营后台)
CREATE INDEX idx_orders_status ON orders(status) WHERE status != 'cancelled';
-- JSONB索引:按商品ID查关联订单
CREATE INDEX idx_orders_items_product ON orders USING GIN (items);
对应的Go仓储实现(internal/order/repository.go):
package order
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"github.com/yourorg/ecomm/internal/order/domain"
"github.com/jmoiron/sqlx"
)
type postgresRepository struct {
db *sqlx.DB
}
func NewPostgresRepository(db *sqlx.DB) OrderRepository {
return &postgresRepository{db: db}
}
func (r *postgresRepository) Create(ctx context.Context, order *domain.Order) error {
items, err := json.Marshal(order.Items)
if err != nil {
return fmt.Errorf("marshal items: %w", err)
}
query := `INSERT INTO orders (id, user_id, status, items, total, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)`
_, err = r.db.ExecContext(ctx, query,
order.ID, order.UserID, string(order.Status),
items, order.Total, order.CreatedAt, order.UpdatedAt)
if err != nil {
return fmt.Errorf("insert order: %w", err)
}
return nil
}
func (r *postgresRepository) Update(ctx context.Context, order *domain.Order) error {
// 乐观锁更新
query := `UPDATE orders
SET status = $1, items = $2, total = $3,
version = version + 1, updated_at = NOW()
WHERE id = $4 AND version = $5`
result, err := r.db.ExecContext(ctx, query,
string(order.Status), order.Items, order.Total,
order.ID, order.Version)
if err != nil {
return fmt.Errorf("update order: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return domain.ErrConcurrentUpdate // 乐观锁冲突
}
order.Version++
return nil
}
func (r *postgresRepository) ListByUser(ctx context.Context, userID string, offset, limit int) ([]*domain.Order, error) {
query := `SELECT * FROM orders
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3`
var orders []*domain.Order
err := r.db.SelectContext(ctx, &orders, query, userID, limit, offset)
if err != nil {
return nil, fmt.Errorf("list orders by user: %w", err)
}
return orders, nil
}
让Claude Code生成完整的本地开发环境:
生成 docker-compose.yml,包含:
1. 3个PostgreSQL实例(每个服务独立数据库)
2. 2个Redis实例(用户缓存、库存缓存)
3. Consul(服务发现)
4. NATS(消息队列)
5. Jaeger(链路追踪)
6. 4个微服务容器(使用热重载 air)
7. 所有服务通过自定义网络 ecomm-net 通信
Claude Code生成 deployments/docker-compose.yml:
version: "3.9"
networks:
ecomm-net:
driver: bridge
services:
# ======== 基础设施 ========
postgres-user:
image: postgres:16-alpine
environment:
POSTGRES_DB: users_db
POSTGRES_USER: ecomm
POSTGRES_PASSWORD: ${DB_PASSWORD:-devpass}
ports: ["5432:5432"]
volumes: ["pg-user-data:/var/lib/postgresql/data"]
networks: [ecomm-net]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ecomm"]
interval: 5s
retries: 5
postgres-order:
image: postgres:16-alpine
environment:
POSTGRES_DB: orders_db
POSTGRES_USER: ecomm
POSTGRES_PASSWORD: ${DB_PASSWORD:-devpass}
ports: ["5433:5432"]
volumes: ["pg-order-data:/var/lib/postgresql/data"]
networks: [ecomm-net]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ecomm"]
interval: 5s
retries: 5
postgres-inventory:
image: postgres:16-alpine
environment:
POSTGRES_DB: inventory_db
POSTGRES_USER: ecomm
POSTGRES_PASSWORD: ${DB_PASSWORD:-devpass}
ports: ["5434:5432"]
volumes: ["pg-inv-data:/var/lib/postgresql/data"]
networks: [ecomm-net]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ecomm"]
interval: 5s
retries: 5
redis:
image: redis:7-alpine
ports: ["6379:6379"]
networks: [ecomm-net]
consul:
image: hashicorp/consul:1.18
ports: ["8500:8500"]
networks: [ecomm-net]
command: agent -dev -client=0.0.0.0
nats:
image: nats:2.10-alpine
ports: ["4222:4222", "8222:8222"]
networks: [ecomm-net]
jaeger:
image: jaegertracing/all-in-one:1.54
ports: ["16686:16686", "4317:4317"]
environment:
COLLECTOR_OTLP_ENABLED: true
networks: [ecomm-net]
# ======== 微服务 ========
user-service:
build:
context: ..
dockerfile: deployments/Dockerfile.service
args:
SERVICE_NAME: user-service
ports: ["50001:50001", "2112:2112"]
environment:
DB_HOST: postgres-user
DB_PORT: 5432
DB_NAME: users_db
REDIS_ADDR: redis:6379
CONSUL_ADDR: consul:8500
JAEGER_ENDPOINT: jaeger:4317
depends_on:
postgres-user: { condition: service_healthy }
redis: { condition: service_started }
consul: { condition: service_started }
networks: [ecomm-net]
develop:
watch:
- action: rebuild
path: ../cmd/user-service
- action: rebuild
path: ../internal/user
order-service:
build:
context: ..
dockerfile: deployments/Dockerfile.service
args:
SERVICE_NAME: order-service
ports: ["50002:50002", "2113:2112"]
environment:
DB_HOST: postgres-order
DB_PORT: 5432
DB_NAME: orders_db
CONSUL_ADDR: consul:8500
NATS_ADDR: nats:4222
INVENTORY_SERVICE_ADDR: inventory-service:50003
JAEGER_ENDPOINT: jaeger:4317
depends_on:
postgres-order: { condition: service_healthy }
consul: { condition: service_started }
nats: { condition: service_started }
networks: [ecomm-net]
inventory-service:
build:
context: ..
dockerfile: deployments/Dockerfile.service
args:
SERVICE_NAME: inventory-service
ports: ["50003:50003", "2114:2112"]
environment:
DB_HOST: postgres-inventory
DB_PORT: 5432
DB_NAME: inventory_db
REDIS_ADDR: redis:6379
CONSUL_ADDR: consul:8500
NATS_ADDR: nats:4222
JAEGER_ENDPOINT: jaeger:4317
depends_on:
postgres-inventory: { condition: service_healthy }
redis: { condition: service_started }
consul: { condition: service_started }
networks: [ecomm-net]
api-gateway:
build:
context: ..
dockerfile: deployments/Dockerfile.gateway
ports: ["8080:8080"]
environment:
CONSUL_ADDR: consul:8500
JAEGER_ENDPOINT: jaeger:4317
depends_on:
- consul
networks: [ecomm-net]
volumes:
pg-user-data:
pg-order-data:
pg-inv-data:
多阶段构建Dockerfile(deployments/Dockerfile.service):
ARG SERVICE_NAME
FROM golang:1.22-alpine AS builder
ARG SERVICE_NAME
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app ./cmd/${SERVICE_NAME}
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app /app
EXPOSE 50001
HEALTHCHECK --interval=10s CMD ["/app", "-health"]
ENTRYPOINT ["/app"]
启动全部服务:
cd deployments
docker compose up -d
# 查看服务状态
docker compose ps
# 查看日志
docker compose logs -f order-service
让Claude Code为OrderService.CreateOrder生成测试:
为 internal/order/service.go 的 CreateOrder 方法生成 table-driven 单元测试:
1. 使用 gomock mock 掉 OrderRepository 和 InventoryClient
2. 测试场景:正常创建、空用户ID、空商品列表、库存不足、数据库写入失败
3. 每个场景验证返回值和mock调用次数
Claude Code生成 internal/order/service_test.go:
package order
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/yourorg/ecomm/internal/order/domain"
"github.com/yourorg/ecomm/pkg/logger"
)
func TestService_CreateOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
tests := []struct {
name string
userID string
items []domain.OrderItem
setupMock func(*MockOrderRepository, *MockInventoryClient)
wantErr bool
errContains string
}{
{
name: "正常创建订单",
userID: uuid.New().String(),
items: []domain.OrderItem{
{ProductID: uuid.New().String(), Quantity: 2},
},
setupMock: func(repo *MockOrderRepository, inv *MockInventoryClient) {
inv.EXPECT().CheckAndReserve(gomock.Any(), gomock.Any()).Return(nil)
repo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil)
},
wantErr: false,
},
{
name: "空用户ID返回错误",
userID: "",
items: []domain.OrderItem{{ProductID: uuid.New().String(), Quantity: 1}},
setupMock: func(repo *MockOrderRepository, inv *MockInventoryClient) {},
wantErr: true,
errContains: "user_id is required",
},
{
name: "空商品列表返回错误",
userID: uuid.New().String(),
items: nil,
setupMock: func(repo *MockOrderRepository, inv *MockInventoryClient) {},
wantErr: true,
errContains: "items cannot be empty",
},
{
name: "库存不足返回错误",
userID: uuid.New().String(),
items: []domain.OrderItem{{ProductID: uuid.New().String(), Quantity: 999}},
setupMock: func(repo *MockOrderRepository, inv *MockInventoryClient) {
inv.EXPECT().CheckAndReserve(gomock.Any(), gomock.Any()).
Return(domain.ErrInsufficientStock)
},
wantErr: true,
errContains: "inventory",
},
{
name: "数据库写入失败后回滚库存",
userID: uuid.New().String(),
items: []domain.OrderItem{{ProductID: uuid.New().String(), Quantity: 1}},
setupMock: func(repo *MockOrderRepository, inv *MockInventoryClient) {
inv.EXPECT().CheckAndReserve(gomock.Any(), gomock.Any()).Return(nil)
repo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(sql.ErrConnDone)
inv.EXPECT().Release(gomock.Any(), gomock.Any()).Return(nil) // 回滚
},
wantErr: true,
errContains: "repo create",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := NewMockOrderRepository(ctrl)
inv := NewMockInventoryClient(ctrl)
tt.setupMock(repo, inv)
svc := NewService(repo, inv, logger.NewNop())
order, err := svc.CreateOrder(context.Background(), tt.userID, tt.items)
if (err != nil) != tt.wantErr {
t.Errorf("CreateOrder() error = %v, wantErr %v", err, tt.wantErr)
}
if err != nil && tt.errContains != "" {
if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q should contain %q", err.Error(), tt.errContains)
}
}
if !tt.wantErr && order == nil {
t.Error("expected non-nil order on success")
}
})
}
}
//go:build integration
package order
import (
"context"
"testing"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
)
func TestCreateOrder_Integration(t *testing.T) {
ctx := context.Background()
// 使用 Testcontainers 启动真实PostgreSQL
pg, err := postgres.Run(ctx,
"postgres:16-alpine",
postgres.WithDatabase("orders_test_db"),
postgres.WithUsername("test"),
postgres.WithPassword("test"),
)
if err != nil {
t.Fatalf("start postgres: %v", err)
}
defer pg.Terminate(ctx)
// 运行迁移
connStr, _ := pg.ConnectionString(ctx, "sslmode=disable")
db := setupDatabase(t, connStr)
defer db.Close()
repo := NewPostgresRepository(db)
inv := &stubInventoryClient{}
svc := NewService(repo, inv, logger.NewNop())
// 端到端测试
order, err := svc.CreateOrder(ctx, "user-123", []domain.OrderItem{
{ProductID: "prod-456", Quantity: 2, Price: 29.99},
})
if err != nil {
t.Fatalf("CreateOrder: %v", err)
}
// 验证持久化
got, err := repo.GetByID(ctx, order.ID)
if err != nil {
t.Fatalf("GetByID: %v", err)
}
if got.UserID != "user-123" {
t.Errorf("UserID = %q, want user-123", got.UserID)
}
}
# 单元测试(快速反馈)
go test ./internal/order/ -v -count=1
# 集成测试(需要Docker)
go test ./internal/order/ -v -tags=integration -count=1
# 覆盖率报告
go test ./... -coverprofile=coverage.out -covermode=atomic
go tool cover -html=coverage.out -o coverage.html
订单创建后,通过NATS发布事件触发库存扣减,实现服务解耦。
让Claude Code生成事件发布/订阅代码:
实现基于NATS的订单事件发布订阅:
1. order-service 在创建订单后发布 OrderCreated 事件
2. inventory-service 订阅 OrderCreated 事件并扣减库存
3. 使用 NATS JetStream 保证至少一次投递
4. 事件payload使用 protobuf 编码
订单服务发布事件(internal/order/events/publisher.go):
package events
import (
"context"
"fmt"
"github.com/nats-io/nats.go"
"github.com/yourorg/ecomm/internal/order/domain"
)
type OrderEventPublisher struct {
js nats.JetStreamContext
}
func NewOrderEventPublisher(nc *nats.Conn) (*OrderEventPublisher, error) {
js, err := nc.JetStream()
if err != nil {
return nil, fmt.Errorf("get jetstream context: %w", err)
}
// 确保Stream存在
_, err = js.AddStream(&nats.StreamConfig{
Name: "ORDERS",
Subjects: []string{"orders.>"},
Retention: nats.WorkQueuePolicy,
})
if err != nil {
return nil, fmt.Errorf("add stream: %w", err)
}
return &OrderEventPublisher{js: js}, nil
}
func (p *OrderEventPublisher) PublishOrderCreated(ctx context.Context, order *domain.Order) error {
event := &OrderCreatedEvent{
OrderID: order.ID,
UserID: order.UserID,
Items: order.Items,
}
data, err := event.MarshalVT()
if err != nil {
return fmt.Errorf("marshal event: %w", err)
}
_, err = p.js.Publish("orders.created", data)
if err != nil {
return fmt.Errorf("publish order created: %w", err)
}
return nil
}
库存服务订阅事件(internal/inventory/events/subscriber.go):
package events
import (
"fmt"
"log/slog"
"github.com/nats-io/nats.go"
)
type InventoryEventSubscriber struct {
js nats.JetStreamContext
svc InventoryDeductionService
log *slog.Logger
}
func NewInventoryEventSubscriber(
nc *nats.Conn,
svc InventoryDeductionService,
log *slog.Logger,
) (*InventoryEventSubscriber, error) {
js, err := nc.JetStream()
if err != nil {
return nil, fmt.Errorf("jetstream: %w", err)
}
return &InventoryEventSubscriber{js: js, svc: svc, log: log}, nil
}
func (s *InventoryEventSubscriber) Start() error {
_, err := s.js.Subscribe("orders.created", func(msg *nats.Msg) {
var event OrderCreatedEvent
if err := event.UnmarshalVT(msg.Data); err != nil {
s.log.Error("unmarshal event", "error", err)
msg.Nak()
return
}
ctx := context.Background()
if err := s.svc.DeductStock(ctx, event.Items); err != nil {
s.log.Error("deduct stock failed", "order_id", event.OrderID, "error", err)
msg.Nak() // 重试
return
}
s.log.Info("stock deducted", "order_id", event.OrderID)
msg.Ack()
}, nats.Durable("inventory-deduction"), nats.ManualAck())
return err
}
微服务开发中,Claude Code只能看到当前仓库,无法感知其他服务的代码。这是AI辅助微服务开发的核心痛点。
解决策略:
flowchart TD
A[跨服务上下文挑战] --> B[策略1: 共享Proto仓库]
A --> C[策略2: CLAUDE.md声明依赖]
A --> D[策略3: Subagent并行开发]
B --> B1[git submodule 引用<br/>公共proto定义]
C --> C1[每个服务的CLAUDE.md<br/>声明依赖服务的接口]
D --> D1[Task工具启动多个Agent<br/>并行开发不同服务]
style A fill:#FF6B6B,color:#fff
style B1 fill:#50C878,color:#fff
style C1 fill:#50C878,color:#fff
style D1 fill:#50C878,color:#fff
# 在主仓库中添加proto子模块
git submodule add https://github.com/yourorg/ecomm-proto.git proto
# Claude Code能直接读取proto定义,理解服务间契约
在order-service的CLAUDE.md中添加:
## 依赖服务接口
### Inventory Service (gRPC :50003)
- CheckAndReserve(items) → 确认并预留库存
- Release(items) → 释放预留库存
- DeductStock(items) → 实际扣减库存
### User Service (gRPC :50001)
- GetUser(id) → 获取用户信息
- ValidateToken(token) → 验证JWT令牌
请使用Task工具启动3个并行Subagent:
Agent 1: 实现 user-service 的 gRPC 服务端
- 读取 proto/user/v1/user.proto
- 生成 handler、service、repository 层
- 包含 JWT 认证中间件
Agent 2: 实现 inventory-service 的 gRPC 服务端
- 读取 proto/inventory/v1/inventory.proto
- 包含 NATS 事件订阅处理
- 实现 Redis 缓存层
Agent 3: 实现 api-gateway
- 使用 grpc-gateway 暴露 REST API
- 集成 Consul 服务发现
- 实现 JWT 验证中间件
Claude Code的Task工具会在隔离的worktree中并行开发,互不干扰。
让Claude Code生成GitHub Actions:
生成 .github/workflows/ci.yml,实现:
1. PR触发:lint + 单元测试 + 编译检查
2. main分支触发:构建Docker镜像 + 推送到Registry
3. 使用 go vet + golangci-lint 做代码质量门禁
4. 4个微服务并行构建
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
env:
GO_VERSION: "1.22"
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: "${{ env.GO_VERSION }}" }
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: v1.57
args: --timeout=5m
test:
runs-on: ubuntu-latest
needs: lint
strategy:
matrix:
service: [user-service, order-service, inventory-service, api-gateway]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: "${{ env.GO_VERSION }}" }
- name: Run tests
run: go test ./internal/${{ matrix.service == 'api-gateway' && 'gateway' || matrix.service }}//... -v -race -count=1
build:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
strategy:
matrix:
service: [user-service, order-service, inventory-service, api-gateway]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
file: deployments/Dockerfile.service
build-args: SERVICE_NAME=${{ matrix.service }}
push: true
tags: ghcr.io/${{ github.repository }}/${{ matrix.service }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 1. 创建项目
mkdir ecomm-microservices && cd ecomm-microservices
git init
go mod init github.com/yourorg/ecomm
# 2. 启动Claude Code
claude
# 3. 一键生成项目骨架(Prompt)
"""
请基于项目根目录的 CLAUDE.md 生成完整的电商微服务项目骨架:
1. 4个微服务的 cmd/ 和 internal/ 目录结构
2. proto文件和buf配置
3. Docker Compose编排文件
4. 数据库迁移脚本
5. Makefile包含常用命令(build/test/lint/proto/migrate)
6. golangci-lint配置
项目使用的技术栈:Go 1.22, gRPC, PostgreSQL, Redis, NATS, Consul, OpenTelemetry
"""
# 4. 构建并启动
make proto # 生成gRPC代码
make migrate-up # 运行数据库迁移
docker compose up -d
# 5. 验证
curl http://localhost:8080/health
# 预期: {"status":"ok","services":{"user":"up","order":"up","inventory":"up"}}
flowchart LR
subgraph 开发阶段
A[Proto定义<br/>buf generate] --> B[接口+测试<br/>TDD模式]
B --> C[业务实现<br/>Claude辅助]
C --> D[本地验证<br/>docker compose up]
end
subgraph 验证阶段
D --> E[go vet + lint]
E --> F[单元测试<br/>覆盖率≥70%]
F --> G[集成测试<br/>Testcontainers]
end
subgraph 交付阶段
G --> H[PR提交<br/>CI自动检查]
H --> I[代码审查<br/>Claude辅助review]
I --> J[合并main<br/>自动构建镜像]
end
style A fill:#4A90D9,color:#fff
style D fill:#50C878,color:#fff
style H fill:#FF6B6B,color:#fff
style J fill:#FFB347,color:#fff
# 检查服务是否启动
docker compose ps
# 检查端口映射
docker compose port order-service 50002
# 查看服务日志
docker compose logs order-service --tail 50
根因:Docker Compose的depends_on只等待容器启动,不等待gRPC服务就绪。解决方案是添加健康检查:
order-service:
healthcheck:
test: ["CMD", "grpc_health_probe", "-addr=:50002"]
interval: 5s
retries: 10
使用Buf的managed模式确保生成代码不被手动修改:
# buf.gen.yaml
version: v2
managed:
enabled: true
override:
- file_option: go_package_prefix
value: github.com/yourorg/ecomm/proto
plugins:
- remote: buf.build/protocolbuffers/go
out: proto
- remote: buf.build/grpc/go
out: proto
在CLAUDE.md中强化约束:
## 代码质量门禁(Claude Code必须遵循)
- 运行 `go vet ./...` 无警告
- 运行 `golangci-lint run` 无错误
- 所有 public 函数有 doc comment
- 错误消息小写开头,不含大写
- 使用 `slog` 而非 `log` 或第三方日志库
使用OpenTelemetry + Jaeger进行分布式追踪:
# 访问Jaeger UI
open http://localhost:16686
# 搜索特定订单的完整调用链
# 可以看到:Gateway → OrderService → InventoryService 的完整trace
# 使用 BuildKit 缓存加速
DOCKER_BUILDKIT=1 docker compose build --parallel
# 开发时只启动需要的服务
docker compose up -d postgres-order nats consul order-service
-p模式批量重构当Proto定义变更后,一键更新所有受影响的服务:
claude -p "proto/order/v1/order.proto 新增了 shipping_address 字段。
请更新以下文件以适配新字段:
1. internal/order/domain/model.go
2. internal/order/service.go
3. internal/order/handler.go
4. internal/order/repository.go
5. internal/order/service_test.go
确保所有测试通过。"
/compact管理长对话上下文微服务开发涉及大量文件,对话上下文容易溢出:
/compact
这将压缩对话历史,保留关键上下文(文件路径、架构决策、代码片段)。
# 为每个服务创建独立工作树
claude worktree create user-service # ~/ecomm/.claude/worktrees/user-service
claude worktree create order-service # ~/ecomm/.claude/worktrees/order-service
# 在各自worktree中并行开发
# Claude Code会自动管理隔离
CLAUDE.md分层继承项目根/
├── CLAUDE.md # 全局:技术栈、通用规范
├── internal/order/
│ └── CLAUDE.md # 订单服务专属:领域规则、业务约束
└── deployments/
└── CLAUDE.md # 部署专属:Docker规范、K8s约定
Claude Code会读取当前工作目录向上查找所有CLAUDE.md并合并。
| 训练营类型 | 适用性 | 预计时长 | 学员要求 |
|---|---|---|---|
| Go微服务入门营 | ★★★★★ | 2天(16小时) | Go基础,了解REST API |
| AI辅助编程实战营 | ★★★★☆ | 1天(8小时) | Claude Code基础操作 |
| 后端架构升级营 | ★★★★★ | 3天(24小时) | 1年以上后端开发经验 |
| DevOps工程化训练营 | ★★★☆☆ | 1天(8小时) | Docker基础,CI/CD概念 |
推荐教学路径:第一天完成第一至五步(基础架构+单服务开发),第二天完成第六至九步(编排+异步+测试),第三天完成实战项目+CI/CD。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
