GraphQL vs REST在Kotlin中:2025年后端开发如何选择

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:REST已死,GraphQL万岁?(剧透:并没有)

\n

如果你正在使用Kotlin开发后端,那么你一定听说过这场圣战:GraphQL vs REST。擂台的一角是经典老将REST,每个初级开发者都熟悉它。另一角是时髦的GraphQL,它承诺“给我想要的东西,但只需一次请求”。

\n

我是一名有Kotlin经验的后端开发者,在过去几年里,我将三个项目从REST迁移到GraphQL,然后又迁移回来。剧透:每种工具都有其适用的场景。本文不会盲目追捧。相反,我们将通过真实的代码示例来分析,何时Kotlin上的REST更胜一筹,何时GraphQL是你的救星。

\n

让我们开始Kotlin中GraphQL与REST的对比,不掺杂水分和营销。

\n\n

1. 核心概念速览:各自是什么

\n

在深入代码之前,我们先统一术语。REST API是一种架构风格,其中每个资源(用户、订单、商品)都有独立的URL和HTTP方法(GET、POST、PUT、DELETE)。Kotlin开发者通常使用Spring Boot或Ktor来构建REST服务。

\n

GraphQL是一种查询语言,只有一个端点(通常是/graphql),客户端自行决定需要哪些字段。在Kotlin中,流行的库有KGraphQL(轻量级)和Spring for GraphQL(适合Spring爱好者)。

\n

关键区别:REST是“服务器决定返回什么”,GraphQL是“客户端只请求所需内容”。

\n\n

2. Kotlin REST API示例(Ktor)

\n

从经典开始。假设我们有一个电商网站,需要获取用户信息及其最近的订单。

\n

以下是使用Ktor(Kotlin轻量级框架)的典型REST控制器:

\n\n
// REST端点:GET /api/users/{id}\nfun Application.userModule() {\n    routing {\n        get("/api/users/{id}") {\n            val userId = call.parameters["id"]?.toIntOrNull() ?: return@get call.respondText("Bad Request", status = HttpStatusCode.BadRequest)\n            val user = userService.getUserById(userId) ?: return@get call.respondText("Not Found", status = HttpStatusCode.NotFound)\n            call.respond(user)\n        }\n    }\n}\n\n// 响应的数据类\n@Serializable\ndata class UserResponse(\n    val id: Int,\n    val name: String,\n    val email: String,\n    val lastOrders: List<Order>\n)
\n\n

问题:如果客户端(移动应用)只需要用户的email,服务器仍然会返回完整的UserResponse对象,包括订单信息。这就是过度获取(over-fetching)——传输了多余的数据。在弱移动网络环境下,这很痛苦。

\n

解决方案之一是创建多个细粒度端点:GET /api/users/{id}/email。但这会导致请求数量增加(欠获取(under-fetching))。

\n\n

3. Kotlin GraphQL示例(Spring for GraphQL)

\n

现在,同样的场景,但使用GraphQL。以下是模式定义(schema.graphqls):

\n\n
type Query {\n    user(id: ID!): User\n}\n\ntype User {\n    id: ID!\n    name: String\n    email: String\n    lastOrders: [Order]\n}\n\ntype Order {\n    id: ID!\n    total: Float\n    date: String\n}
\n\n

这是使用Spring for GraphQL(注解方式)的Kotlin代码:

\n\n
@Controller\nclass UserController(private val userService: UserService) {\n\n    @QueryMapping\n    fun user(@Argument id: Int): User {\n        return userService.getUserById(id) ?: throw UserNotFoundException("User not found")\n    }\n}\n\n// 数据类,无多余字段\ndata class User(val id: Int, val name: String, val email: String, val lastOrders: List<Order>)
\n\n

现在,客户端可以只请求所需内容:

\n\n
query {\n    user(id: 42) {\n        email\n    }\n}

博客

书籍推荐