Go-Zero项目开发8: 构建社交API服务与微服务解耦实践

发布时间:2026/7/20 20:06:10
Go-Zero项目开发8: 构建社交API服务与微服务解耦实践 纲要回顾社交 RPC 服务已就绪需暴露 HTTP 接口构建社交 API 服务编写social.api定义路由与数据结构使用goctl api go生成代码ServiceContext注入social-rpc与user-rpc客户端配置etc/socialapi.yaml及jwt认证业务接口实现好友申请验证登录状态调用社交 RPC好友申请处理透传处理逻辑好友列表聚合用户信息与好友关系从社交 RPC 获取好友 ID 列表批量调用用户 RPC 查询用户详情数据映射与组装好友列表聚合流程Mermaid 图微服务设计反思为什么不共享数据库解耦、灵活性、安全性为什么不在 RPC 服务间相互调用避免循环依赖、复杂度、故障隔离BFF 层的价值聚合、隔离、可观测性测试验证与总结背景前面我们已经完成了社交 RPC 服务实现了好友申请、处理与列表查询的 gRPC 接口。为了让前端能够调用这些功能还需要构建一层 HTTP API 服务social-api。同时好友列表不仅需要关系数据还需要用户详细信息这涉及多服务数据聚合是微服务中典型的 BFFBackend For Frontend场景。本文将基于go-zerolatest完成社交 API 服务的搭建并深入探讨服务间数据聚合的设计哲学。项目结构apps/social/ ├── api/ │ ├── internal/ │ │ ├── config/ │ │ ├── handler/ │ │ ├── logic/ │ │ ├── svc/ │ │ └── types/ │ ├── social.api │ └── socialapi.go └── rpc/ ├── internal/ ├── model/ └── social.proto构建社交 API 服务定义 API 描述文件创建apps/social/api/social.api定义好友相关接口type(FriendApplyReq{FriendIdstringjson:friend_idReasonstringjson:reason,optional}FriendApplyResp{}FriendApplyHandleReq{ApplyIdint64json:apply_idHandleTypeint32json:handle_type// 1:通过 2:拒绝}FriendApplyHandleResp{}FriendListResp{List[]FriendInfojson:list}FriendInfo{UserIdstringjson:user_idNicknamestringjson:nicknameAvatarstringjson:avatarDescstringjson:desc})server(prefix:/v1/social jwt:Auth)service social-api{handler FriendApplyHandler post/friend/apply(FriendApplyReq)returns(FriendApplyResp)handler FriendApplyHandleHandler put/friend/apply/handle(FriendApplyHandleReq)returns(FriendApplyHandleResp)handler FriendListHandler get/friend/list returns(FriendListResp)}注意jwt: Auth指定该服务需要 JWT 验证相关配置会在生成代码后自动添加中间件。执行生成命令$ goctl api go-apiapps/social/api/social.api-dirapps/social/api-stylegoZero配置多 RPC 客户端编辑apps/social/api/internal/config/config.go增加两个 RPC 配置packageconfigimport(github.com/zeromicro/go-zero/restgithub.com/zeromicro/go-zero/zrpc)typeConfigstruct{rest.RestConf SocialRpc zrpc.RpcClientConf UserRpc zrpc.RpcClientConf JWTstruct{AccessSecretstringAccessExpireint64}}etc/socialapi.yaml示例开发环境Name:social-apiHost:0.0.0.0Port:8881SocialRpc:Etcd:Hosts:-127.0.0.1:2379Key:social.rpcUserRpc:Etcd:Hosts:-127.0.0.1:2379Key:user.rpcJWT:AccessSecret:your-secret-keyAccessExpire:86400svc/servicecontext.go创建双客户端代理packagesvcimport(im-system/apps/social/api/internal/configim-system/apps/social/rpc/socialim-system/apps/user/rpc/usergithub.com/zeromicro/go-zero/zrpc)typeServiceContextstruct{Config config.Config SocialRpc social.SocialClient UserRpc user.UserClient}funcNewServiceContext(c config.Config)*ServiceContext{returnServiceContext{Config:c,SocialRpc:social.NewSocialClient(zrpc.MustNewClient(c.SocialRpc).Conn()),UserRpc:user.NewUserClient(zrpc.MustNewClient(c.UserRpc).Conn()),}}业务逻辑实现好友申请从JWT解析出的context中提取当前用户uid然后调用社交 RPC。// internal/logic/friendapplylogic.gofunc(l*FriendApplyLogic)FriendApply(req*types.FriendApplyReq)(*types.FriendApplyResp,error){uid:svc.GetUidFromCtx(l.ctx)// 工具函数从 context 提取 uid_,err:l.svcCtx.SocialRpc.FriendApply(l.ctx,social.FriendApplyRequest{UserId:uid,FriendId:req.FriendId,Reason:req.Reason,})iferr!nil{returnnil,err}returntypes.FriendApplyResp{},nil}好友申请处理同样透传仅需传递apply_id和handle_type。func(l*FriendApplyHandleLogic)FriendApplyHandle(req*types.FriendApplyHandleReq)(*types.FriendApplyHandleResp,error){_,err:l.svcCtx.SocialRpc.FriendApplyHandle(l.ctx,social.FriendApplyHandleRequest{ApplyId:req.ApplyId,HandleType:req.HandleType,})iferr!nil{returnnil,err}returntypes.FriendApplyHandleResp{},nil}好友列表核心聚合好友列表需要返回好友的昵称、头像等信息这些数据存在于用户服务。因此需要调用社交 RPC 获取好友 ID 列表。调用用户 RPC 批量查询用户信息。组装结果。func(l*FriendListLogic)FriendList()(*types.FriendListResp,error){uid:svc.GetUidFromCtx(l.ctx)// 1. 获取好友 ID 列表friendsResp,err:l.svcCtx.SocialRpc.FriendList(l.ctx,social.FriendListRequest{UserId:uid,})iferr!nil{returnnil,err}iflen(friendsResp.FriendIds)0{returntypes.FriendListResp{List:[]types.FriendInfo{}},nil}// 2. 批量获取用户信息usersResp,err:l.svcCtx.UserRpc.FindUser(l.ctx,user.FindUserRequest{Ids:friendsResp.FriendIds,})iferr!nil{returnnil,err}// 3. 构建映射uid → 用户信息userMap:make(map[string]*user.GetUserInfoResponse,len(usersResp.Users))for_,u:rangeusersResp.Users{userMap[u.Uid]u}// 4. 组装好友列表list:make([]types.FriendInfo,0,len(friendsResp.FriendIds))for_,fid:rangefriendsResp.FriendIds{friendInfo:types.FriendInfo{UserId:fid,}ifu,ok:userMap[fid];ok{friendInfo.Nicknameu.Username friendInfo.Avataru.Avatar friendInfo.Desc// 可扩展}listappend(list,friendInfo)}returntypes.FriendListResp{List:list},nil}注意用户服务中的FindUser支持按 ID 集合查询正好用于批量获取。这要求我们在用户 RPC 中实现了FindUser接口之前已实现。聚合流程图示数据库user-rpcsocial-rpcsocial-api客户端数据库user-rpcsocial-rpcsocial-api客户端GET /v1/social/friend/list (JWT)解析 JWT 获取 uidFriendList(uid)查询好友关系表friend_idsfriend_idsFindUser(ids)批量查询用户表用户列表用户详情列表映射组装 FriendInfo{code:0, data:{list:[...]}}微服务设计反思在开发过程中有两个设计决策值得深入讨论为什么不直接在social-rpc中查询用户表从微服务原则分析解耦每个微服务应维护自己独立的数据存储避免数据库成为耦合点。共享数据库会让服务间产生隐式依赖一旦表结构变更影响面广。灵活性如果用户表被多个服务直接访问用户服务的扩展、分库分表都会受限于其他服务的操作模式。安全性数据库权限集中任何服务的漏洞都可能泄露整体用户数据独立数据库可以实施更精细的权限隔离。团队自治不同团队可以独立管理自己的数据库不必协调表结构变更。因此社交服务不直接操作用户表只能通过 RPC 接口获取用户信息。为什么不在social-rpc中直接调用user-rpc而要放在 API 层如果social-rpc调用user-rpc会形成服务间的网状依赖可能引发循环依赖。例如用户服务也可能需要好友关系数据相互调用会造成死锁或级联故障。将聚合逻辑上提到API 层BFF有以下好处职责清晰RPC 服务专注于自身领域的原子操作API 层负责面向前端的业务编排。避免循环依赖RPC 之间保持无依赖或单向依赖如社交 RPC 不依赖用户 RPC系统依赖关系收敛。故障隔离用户服务不可用时聚合层可以降级返回好友 ID 列表而非整体失败RPC 层相互隔离不影响核心逻辑。便于扩展当需要新增前端时可以在 API 层灵活组合已有 RPC无需改动底层服务。当然少量、单向的 RPC 间调用并非绝对禁止但本项目通过 BFF 提前聚合遵循了更加清晰的设计范式。测试验证启动所需服务后使用 API 工具完成测试好友申请登录用户 A向用户 B 发起申请检查数据库新增记录。好友申请处理用户 B 登录批准申请确认好友关系表增加两条记录。好友列表任意已登录用户请求应返回好友的昵称、头像等信息而非仅 ID。通过调试确认多服务协作正常响应格式统一。总结本文完成了社交 API 服务的构建重点展示了以下技术实践使用goctl api快速生成 API 代码并配置双 RPC 客户端。利用 BFF 层聚合社交关系与用户信息避免 RPC 服务间直接调用。结合 Mermaid 时序图直观展示跨服务查询流程。阐述了微服务数据库隔离与服务间调用的设计原则强调了 BFF 在解耦和稳定性方面的价值。至此即时通讯系统的用户与社交两大基础服务已初步形成后续将继续扩展 IM 服务与 WebSocket 通信部分构建完整的聊天核心。