一个好的 schema 是由客户端驱动的、边缘处默认可空的、无需版本即可演进的。它按客户端实际使用的方式对领域建模,而不是按你的数据库表。
可空性
只有当你能始终返回某字段时,才把它设为非空(!)。滥用 ! 是个陷阱:如果一个非空字段出错,它会把整个 parent 对象置空,把一个小故障放大成一大片空白。凡是依赖下游服务的字段,优先设为可空。
graphql
User
ID
String
String
isPublished)。input 类型,并返回一个 payload 类型——你以后可以加字段而不破坏签名。input CreatePostInput { title: String!, body: String! }
type CreatePostPayload { post: Post!, errors: [UserError!]! } # 留有扩展空间
type Mutation { createPost(input: CreatePostInput!): CreatePostPayload! }
GraphQL 避免 /v2 这样的 URL。你以增量方式演进:自由地增加字段,并用 @deprecated 淘汰旧字段,同时让客户端迁移。
type User {
fullName: String!
name: String! @deprecated(reason: "Use fullName") # 在客户端迁移完之前继续提供
}
移除或重命名字段是唯一真正的破坏性变更,所以要这样设计,让你极少需要这么做。同时避免泄露 DB 结构(如果客户端想要单个字段,就暴露 fullName,而非 first_name/last_name)。
这些选择正是能存活多年的 schema 与被客户端处处对抗的 schema 之间的区别。面试官用它来衡量 API 设计成熟度——尤其是可空性/错误的权衡,以及增量演进的思维方式,这些都是 GraphQL 独有的。