Controller仅负责请求接收、Service调用与响应组装,业务逻辑须抽离至Service层;Service需手动注入、导出且复用,Model应分输入/领域/数据库三层;错误须分类返回对应HTTP状态码。

业务逻辑不该塞进Controller方法里
Controller在Iris MVC中只负责接收请求、调用Service、组装响应,不是写SQL或做校验的地方。一旦把密码加密、库存扣减、第三方API调用这些逻辑直接写在GetByID或Create方法里,后续要加缓存、换数据库、补日志,就得改Controller——而它本该是稳定的胶水层。
常见错误现象:一个Create方法里混着参数校验、DB插入、Redis计数器更新、发MQ消息,测试时根本没法单独验证库存逻辑是否正确;上线后发现扣减失败,但日志里找不到具体哪一步挂了。
- 所有数据操作(CRUD)、外部调用(HTTP/GRPC)、复杂计算,必须抽到独立的
service包里 - Controller方法参数只保留必要输入(如
id int64、user UserInput),不传*sql.DB或redis.Client - Service层返回结构体或错误,Controller只做状态码映射(如
err != nil→ctx.StatusCode(iris.StatusInternalServerError))
Service实例怎么注入到Controller
Iris MVC不靠反射自动找Service,得在BeforeActivation里手动注册,否则Controller字段永远是nil。很多人卡在这步,以为“写了svc *UserService字段就会被赋值”,结果运行时报panic: runtime error: invalid memory address。
正确做法是在Controller定义里加BeforeActivation方法,用b.Dependencies().Add()绑定实例:
func (c *UserController) BeforeActivation(b mvc.BeforeActivation) {
// 注入单例Service
b.Dependencies().Add(&UserService{})
// 或注入已初始化的实例(带DB连接等)
b.Dependencies().Add(NewUserService(db, redisClient))
}
- Service类型必须导出(首字母大写),否则依赖注入失败
- 如果Service有依赖(比如需要
*sql.DB),别在BeforeActivation里new(),而是提前构造好再Add() - 多个Controller复用同一个Service时,直接在MVC Application全局注册:
mvcApp.Register(&AuthService{}),避免重复Add
跨域业务逻辑复用时别复制粘贴
订单模块要校验用户是否实名,用户模块已有AuthService.IsRealNameVerified(),这时候最危险的操作是:打开orders/controller.go,把校验代码Ctrl+C/V进去。下次用户中心升级实名接口,你得手动改遍所有粘贴过的地方。
真正解耦的方式是让OrderController也依赖AuthService,并在自己的BeforeActivation里注册同一实例:
func (c *OrderController) BeforeActivation(b mvc.BeforeActivation) {
// 复用用户模块的AuthService实例
b.Dependencies().Add(authServiceInstance)
}
- AuthService实例应该由启动时统一创建(比如从config读取API地址),而不是每个Controller自己new
- 如果Auth和Order部署在不同服务,这里注入的应该是gRPC client或HTTP client封装,而非直接DB操作
- 别为了“省事”在Service里加
if module == "order"分支逻辑——职责污染比多写一个interface更难维护
Model层不是数据库表映射,而是领域对象
别一上来就让User struct字段和MySQL user表字段1:1。Iris MVC的Model层是给Service和Controller用的数据载体,不是ORM实体。比如前端提交的注册数据含Password字段,但存库时你要哈希,那UserInput和UserModel就该是两个struct。
容易踩的坑:把CreatedAt time.Time `db:"created_at"`这种GORM tag直接写在Controller接收参数的struct上,导致JSON序列化时暴露敏感字段或格式错乱。
- 输入模型(如
UserCreateReq)只含前端传的字段,加jsontag控制序列化 - 领域模型(如
User)不含密码、token等敏感字段,也不带数据库tag - 数据库实体(如
UserRow)只在Repository层内部使用,Controller和服务层完全感知不到
iris.StatusBadRequest,而DB连接失败才是500。这个判断不在Controller里做,而应由Service返回带语义的错误类型(如ErrInsufficientStock),Controller再映射。否则团队每人对“什么算客户端错”理解不一致,前端重试策略全乱。


















