Schema 设计
根类型与 Schema 定义。
Schema 设计
这一课你将能做到
- 从业务域(课程/用户/提交)出发,独立设计出一份可演进、可治理的 Schema
- 说清 schema 块、根类型、extend 的组织方式及多文件 Schema 的合并规则
- 运用命名、空值、演进三组纪律,避免教科书 Schema 在生产环境翻车
为什么要学
类型系统教你认砖块,这一课教你盖房子。真实项目的 Schema 不是一次写成的:新需求每周加字段、老字段要体面退役、十几个类型散在七八个文件里。设计决策一旦做错——比如把 Schema 当数据库 ER 图照抄——后面每次加需求都要付利息。
人话:Schema 是产品的 API 门面,门面的装修决定了以后十年顾客(客户端)进店的体验。
先建立图像
Schema 的组织结构:
schema { ← 显式声明三个根(可省略走默认命名)
query: Query
mutation: Mutation
subscription: Subscription
}
type Query { ... } ← 所有读入口
type Mutation { ... } ← 所有写入口
type Subscription { ... } ← 所有订阅入口
# 领域类型按业务分组,不在根类型里堆字段
type Lesson / type Track / type Progress / type Submission ...
多文件开发时用 extend 扩展根类型,构建期合并:
# lesson.graphqls
extend type Query {
lesson(slug: String!): Lesson
lessons(first: Int, after: String): LessonConnection!
}
跟着做
步骤 1:显式声明 schema 块
schema {
query: QueryRoot
mutation: MutationRoot
}
如果根类型就叫 Query/Mutation/Subscription,schema 块可省略。但团队规约建议显式声明——可读性和「根类型重命名自由」兼得。
步骤 2:根字段按「读什么」而不是「表是什么」组织
type Query {
"按 slug 查课时"
lesson(slug: String!): Lesson
"课时分页列表(游标分页见最后一课)"
lessons(filter: LessonFilter, first: Int, after: String): LessonConnection!
"当前登录用户"
viewer: User
"全局节点查询(Relay 风格,可选)"
node(id: ID!): Node
}
type Mutation {
markLessonDone(input: MarkLessonDoneInput!): MarkLessonDonePayload!
submitCode(input: SubmitCodeInput!): SubmitCodePayload!
enrollTrack(input: EnrollTrackInput!): EnrollTrackPayload!
}
注意三个细节:viewer 而不是 me(命名从「图」的视角);读返回可空(查无此课时返回 null 而非报错);写统一 input/payload 范式。
步骤 3:领域类型按图遍历设计
问自己「用户在这个对象上还想顺路看什么」:
type Lesson {
id: ID!
slug: String!
title: String!
content: String
durationMinutes: Int!
difficulty: Difficulty!
# 图遍历出口:从课时能走到哪
track: Track
author: User!
submissions(first: Int, after: String): SubmissionConnection!
}
type Track {
id: ID!
slug: String!
title: String!
lessons(first: Int, after: String): LessonConnection!
lessonCount: Int! # 聚合字段直接放类型上,别建 LessonStats 类型
}