在 Golang Gin 框架(实际上是任何 Go 后端项目)中,为请求体(Request Body)定义结构体时,CreateUserRequest
和 UserCreateRequest
两种命名方式都有人使用,但其中一种在可维护性和可扩展性方面通常被认为是更优的选择。
结论先行
总的来说,UserCreateRequest
(实体-动作-类型) 是更被推荐的命名方式,尤其是在中大型、长期维护的项目中。它的优势在于代码的组织性、可发现性和可扩展性。
下面我们来详细分解。
1. 两种命名方式的分析
CreateUserRequest
(动词-宾语-类型 / Verb-Object-Type)
这种命名方式将动作 (Create) 放在最前面。
UserCreateRequest
(实体-动作-类型 / Object-Verb-Type)
这种命名方式将领域实体 (User) 放在最前面。
2.对比总结
特性 / 方面 | CreateUserRequest (动词-宾语) | UserCreateRequest (实体-动词) | 推荐度 |
---|
可读性 (Readability) | 意图直接,像一个指令 | 逻辑清晰,像一个名词短语 | 两者皆可 |
代码组织 (Organization) | 按操作分组,实体分散 | 按实体分组,逻辑内聚 | UserCreateRequest 胜出 |
可发现性 (Discoverability) | 差,难以找到某实体所有操作 | 优,IDE 自动补全友好 | UserCreateRequest 胜出 |
可扩展性/可维护性 | 差,项目变大后混乱 | 优,易于扩展和长期维护 | UserCreateRequest 胜出 |
与 DDD 的契合度 | 较低 | 高,以领域为中心 | UserCreateRequest 胜出 |
3.代码示例
让我们看一个简单的 Gin Handler 示例,感受一下两种方式的差异。
假设我们有一个 User
的包(package)。
场景: 使用 UserCreateRequest
(推荐)
// file: internal/User/request.go
package User
// 所有 User 相关的请求体都在这里,或者在 request.go 文件里
type UserCreateRequest struct {
Username string `json:"username" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
type UserUpdateRequest struct {
// ...
}
// file: internal/User/handler.go
package User
import "github.com/gin-gonic/gin"
// Handler 结构体也体现了以 User 为中心
type Handler struct {
// ... dependencies
}
func (h *Handler) Create(c *gin.Context) {
var req UserCreateRequest // 非常清晰,这是 User 的 Create 请求
if err := c.ShouldBindJSON(&req); err != nil {
// ... handle error
return
}
// ... call service layer
}
在这个结构下,所有和 User
相关的代码(request.go
, response.go
, service.go
, handler.go
)都内聚在 User
包里,并且请求体本身也通过 User
前缀清晰地表明了其领域归属。
场景: 使用 CreateUserRequest
(不推荐)
如果使用 CreateUserRequest
,当项目变大时,你的 request
目录或文件可能看起来像这样:
// file: internal/dto/requests.go (或者分散在各个包里)
type CreateUserRequest struct {
Username string `json:"username" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
type CreateOrderRequest struct {
// ...
}
// file: internal/User/handler.go
package User
import "github.com/gin-gonic/gin"
type Handler struct {
// ...
}
func (h *Handler) CreateUser(c *gin.Context) { // 函数名和请求体名很对应
var req CreateUserRequest // 名字和函数名很像
if err := c.ShouldBindJSON(&req); err != nil {
// ...
return
}
// ...
}
虽然 handler
函数内部看起来很和谐,但从整个项目的宏观视角来看,维护成本会更高。
结论
虽然 CreateUserRequest
在微观层面(单个函数内)具有一定的可读性,但它牺牲了项目在宏观层面的组织性和可维护性。
UserCreateRequest
的命名方式,将领域实体放在首位,是一种面向未来的、更专业、更具扩展性的实践。它使得代码库随着时间的推移和功能的增加,依然能保持高度的组织性和清晰度。对于任何希望长期发展的项目,这都是一个明智的选择。