跳到主要内容

后端服务的架构设计

如何划分 RPC 服务的职责?​

  • 分层目标: 让“RPC 边界、业务规则、存储访问”各自独立,方便测试和替换;
  • Handler 是适配层: 把 IDL 类型翻译成领域调用;
  • Service 是业务核心: 只依赖领域接口,不依赖 Kitex;
  • Repository 是存储边界: 隔离数据库、缓存等实现细节;

如何安排代码目录?​

  • Handler: 校验 RPC 输入、调用 Service、转换响应与业务异常;
  • Service: 业务规则和事务边界; 不依赖 Kitex transport;
  • Repository: 隔离数据库/缓存 SDK; 由 Service 依赖接口;
  • main: 加载配置、创建依赖、启动 Server、关闭资源;
user-service/
├─ cmd/user-server/main.go # 组装与进程生命周期
├─ idl/user.thrift # RPC 契约
├─ kitex_gen/ # 生成代码
└─ internal/user/
├─ handler.go # IDL 类型 ↔ 领域调用
├─ service.go # 业务规则
├─ repository.go # 存储接口
└─ repository_sql.go # 数据库实现

如何通过接口注入业务依赖?​

  • 存储边界: Repository 返回领域对象;Service 和 Repository 不导入 kitex_gen;
  • 组装: main 创建 Repository,注入 Service,再注入 Handler;只有 Handler 负责 IDL 与领域类型的转换;
  • 示例: 以下是依赖结构片段,完整类型和构造代码见 从零实战;
type Repository interface {
FindByID(context.Context, int64) (*User, error)
}
type Service struct { repo Repository }
// 适配层位于独立包,不把网络类型混进领域包。
type Handler struct { service *userdomain.Service }

如何按依赖顺序启动服务?​

  • 第一步: 读取环境变量/配置文件,并校验地址、timeout、数据库 DSN;
  • 第二步: 初始化 logger、tracer、metrics exporter;
  • 第三步: 建立数据库、缓存、Registry 等外部 Client,并执行连通性检查;
  • 第四步: 创建 Repository → Service → Handler;
  • 第五步: 创建 Kitex Server,注入地址、身份、Registry、中间件和退出时间;
  • 第六步: 依赖全部 ready 后再通过健康检查接收流量;