后端思维补齐
前端视角和后端视角的差异
前端更常关注:
- 页面状态如何展示。
- 交互是否顺滑。
- 接口字段是否够用。
- 组件如何复用。
- 用户操作如何被反馈。
后端更常关注:
- 数据是否能长期保持正确。
- 多个请求同时进来是否会冲突。
- 哪些字段必须唯一。
- 哪些操作必须放在事务里。
- 哪些状态允许流转,哪些不允许。
- 失败后能否重试,重试会不会产生重复数据。
这不是谁更高级,而是职责不同。你转后端时,最关键的一步是从“页面字段”切到“业务状态”。
表不是页面表单
错误思路:
txt
页面上有用户、角色、权限三个输入框
→ 建一张 user 表,字段是 name、role、permission更好的思路:
txt
用户是否独立存在?
角色是否独立维护?
权限是否会被多个角色复用?
一个用户是否能有多个角色?
一个角色是否能有多个权限?这样你会自然拆出:
txt
users
roles
permissions
user_roles
role_permissions前端页面是某个时刻的视图,数据库表是业务长期存在的结构。
接口不是函数入口
前端调用接口时,容易把接口理解成“远程函数”:
txt
click button → call API → get result后端要多想几步:
txt
这个 API 是否会被重复调用?
这个 API 修改了哪些表?
这些修改是否必须同时成功?
是否有权限限制?
失败后能不能重试?
重试是否会重复创建数据?
多个用户同时调用是否会互相覆盖?例如“发布文章”不是简单的:
python
article.status = "published"而是状态流转:
txt
draft → reviewing → published
rejected → draft
published 不允许直接回到 reviewing这类规则应该由后端守住,不能只靠前端按钮隐藏。
后端设计的五个固定问题
每次拿到一个需求,都可以按这五个问题拆:
1. 实体是什么
实体是有独立生命周期的业务对象:
- 用户
- 订单
- 地址
- 文档
- 报表任务
- 知识库
- Agent 会话
判断标准:
txt
它是否需要独立新增、修改、删除、查询?
它是否需要自己的 ID?
它是否会被其他对象引用?
它是否需要保存历史?2. 关系是什么
常见关系:
txt
一对一:user - user_profile
一对多:user - address
多对多:user - role前端可能只看到一个“多选框”,但后端要把多选关系落到中间表。
3. 约束是什么
约束是数据库帮你守住底线:
txt
手机号唯一
订单号唯一
库存不能小于 0
状态不能为空
同一个用户不能重复收藏同一篇文章能用数据库唯一索引守住的,不要只靠代码 if exists。
4. 状态如何流转
只要有 status 字段,就要问:
txt
有哪些状态?
初始状态是什么?
哪些状态能互相转换?
谁能触发转换?
是否要记录转换时间和操作者?例如任务:
txt
pending → running → success
pending → running → failed
failed → pending5. 并发会不会冲突
只要接口会改数据,就要问:
txt
重复提交会怎样?
两个用户同时操作会怎样?
数据库更新一半失败会怎样?
消息重试会怎样?这一步就是你从“写接口”进入“写可靠后端”的分界线。
一个小例子:RAG 文档上传
需求:
用户上传 PDF 到知识库,系统异步解析、切块、向量化,最后可检索。
前端看到的是:
txt
上传文件
显示进度
展示成功/失败后端要拆成:
txt
knowledge_bases 知识库
documents 文档元信息
document_parse_jobs 解析任务
document_chunks 切块结果还要考虑:
txt
同一个文件重复上传怎么办?
解析失败能不能重试?
任务是否会被多个 worker 同时执行?
删除文档时 chunk 怎么处理?
向量库写入失败后数据库状态怎么办?这就是后端思维:不是把上传接口写完,而是把整个业务状态生命周期设计清楚。
RESTful API 设计规范
URL 命名
txt
✅ POST /api/v1/users # 创建用户
✅ GET /api/v1/users # 查询用户列表
✅ GET /api/v1/users/{id} # 查询单个用户
✅ PATCH /api/v1/users/{id} # 更新用户部分字段
✅ DELETE /api/v1/users/{id} # 删除用户
✅ GET /api/v1/users/{id}/orders # 用户的订单列表
❌ /api/getUsers
❌ /api/createUser
❌ /api/userList
❌ /api/deleteUser?id=1关键规范:
- 用名词复数,不要用动词
- 版本号放在 URL 路径里(
/api/v1/) - 层级关系用路径表达(
/users/{id}/orders) - 操作通过 HTTP 方法表达,不在 URL 上写动作
Pagination
txt
# 请求
GET /api/v1/users?page=1&page_size=20
# 响应
{
"data": [...],
"pagination": {
"page": 1,
"page_size": 20,
"total": 156,
"total_pages": 8
}
}统一错误响应
json
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"details": null
},
"request_id": "req_abc123"
}HTTP 状态码速查
txt
200 查询或更新成功
201 创建成功
204 删除成功(无响应体)
400 请求参数错误
401 未认证
403 无权限
404 资源不存在
409 冲突(重复创建、状态冲突)
422 校验失败
429 请求过多
500 服务端内部错误单体 vs 微服务
txt
单体应用:所有功能在一个项目里
→ 优点:开发简单、部署简单、调试方便
→ 缺点:项目变大后难以维护、无法独立扩展
微服务:按业务拆分成多个独立服务
→ 优点:独立部署、独立扩展、技术栈灵活
→ 缺点:运维复杂、网络开销大、数据一致性难前端转后端应该怎么选:
- 初期不要微服务,单体足够
- 项目明显变大后再按业务边界拆分
- 微服务要解决的问题是"团队规模和系统复杂度",不是"技术好不好看"
架构设计五原则
1. 解耦原则
txt
"读写分离"的本质不是加一个从库,而是把读和写的职责拆开。
任何"把一个大系统切成几个小系统"的动作,本质都是在解耦。2. 无状态原则
txt
应用实例不保存用户状态(Session 集中存 Redis / Token 自包含)。
这样任何实例都能处理任何请求,才能水平扩展。3. 兜底原则
txt
任何外部依赖都可能挂:数据库、缓存、MQ、第三方 API。
调用外部依赖时,永远做好"它挂了怎么办"的预案。4. 幂等原则
txt
同一个操作执行一次和重复执行多次的效果相同。
创建资源用唯一约束、扣库存用条件更新、MQ 消费用状态机。5. 可观测原则
txt
上线前就想好:怎么查日志、怎么监控、怎么告警。
不只在本地调试时才加日志,线上问题定位全靠它们。面试怎么说
做后端设计时,我习惯先拆实体和关系、定约束、画状态流转、想并发冲突。API 设计遵循 REST 规范,URL 用名词复数、操作通过 HTTP 方法表达、统一分页和错误响应。系统设计优先单体,拆微服务时按业务边界划分。五个原则贯穿始终:解耦、无状态、兜底、幂等、可观测。