乐闻世界logo
搜索文章和话题

GraphQL 有哪些高级概念和架构设计模式

2月21日 17:00

GraphQL 高级概念与架构设计

GraphQL 不仅仅是查询语言,它还包含许多高级概念和架构设计模式,掌握这些对于构建大规模 GraphQL 应用至关重要。

1. 联合类型(Union Types)

定义联合类型

graphql
union SearchResult = User | Post | Comment type Query { search(query: String!): [SearchResult!]! }

实现联合类型 Resolver

javascript
const resolvers = { SearchResult: { __resolveType: (obj) => { if (obj.email) { return 'User'; } if (obj.title) { return 'Post'; } if (obj.text) { return 'Comment'; } return null; } }, Query: { search: async (_, { query }) => { const users = await User.search(query); const posts = await Post.search(query); const comments = await Comment.search(query); return [...users, ...posts, ...comments]; } } };

使用联合类型

graphql
query SearchResults($query: String!) { search(query: $query) { ... on User { id name email } ... on Post { id title author { name } } ... on Comment { id text author { name } } } }

2. 接口类型(Interface Types)

定义接口

graphql
interface Node { id: ID! createdAt: DateTime! } type User implements Node { id: ID! createdAt: DateTime! name: String! email: String! } type Post implements Node { id: ID! createdAt: DateTime! title: String! content: String! } type Query { node(id: ID!): Node }

实现接口 Resolver

javascript
const resolvers = { Node: { __resolveType: (obj) => { if (obj.email) { return 'User'; } if (obj.title) { return 'Post'; } return null; } }, Query: { node: async (_, { id }) => { // 尝试从不同数据源获取 const user = await User.findById(id); if (user) return user; const post = await Post.findById(id); if (post) return post; throw new Error('Node not found'); } } };

3. 自定义指令(Custom Directives)

创建自定义指令

graphql
directive @auth(requires: Role) on FIELD_DEFINITION directive @cache(ttl: Int) on FIELD_DEFINITION directive @transform(type: String) on FIELD_DEFINITION enum Role { USER ADMIN }

实现指令

javascript
const { makeExecutableSchema } = require('@graphql-tools/schema'); const { defaultFieldResolver } = require('graphql'); const authDirective = (schema) => { return makeExecutableSchema({ typeDefs: schema, resolvers: { Query: { // ... existing resolvers } }, directiveResolvers: { auth: (next, source, args, context) => { const { requires } = args; if (!context.user || context.user.role !== requires) { throw new Error('Unauthorized'); } return next(); }, cache: async (next, source, args, context) => { const { ttl } = args; const cacheKey = `cache:${JSON.stringify(source)}`; // 尝试从缓存获取 const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); } // 执行 resolver const result = await next(); // 缓存结果 await redis.setex(cacheKey, ttl, JSON.stringify(result)); return result; } } }); };

4. 订阅(Subscriptions)

定义订阅

graphql
type Subscription { postCreated: Post! postUpdated(postId: ID!): Post! commentAdded(postId: ID!): Comment! }

实现订阅

javascript
const { PubSub } = require('graphql-subscriptions'); const RedisPubSub = require('graphql-redis-subscriptions').RedisPubSub; const pubsub = new RedisPubSub({ connection: { host: 'localhost', port: 6379 } }); const POST_CREATED = 'POST_CREATED'; const POST_UPDATED = 'POST_UPDATED'; const COMMENT_ADDED = 'COMMENT_ADDED'; const resolvers = { Subscription: { postCreated: { subscribe: () => pubsub.asyncIterator([POST_CREATED]) }, postUpdated: { subscribe: (_, { postId }) => { const filteredIterator = pubsub.asyncIterator([POST_UPDATED]); return { [Symbol.asyncIterator]() { return (async function* () { for await (const event of filteredIterator) { if (event.postUpdated.id === postId) { yield event; } } })(); } }; } }, commentAdded: { subscribe: (_, { postId }) => { const filteredIterator = pubsub.asyncIterator([COMMENT_ADDED]); return { [Symbol.asyncIterator]() { return (async function* () { for await (const event of filteredIterator) { if (event.commentAdded.postId === postId) { yield event; } } })(); } }; } } }, Mutation: { createPost: async (_, { input }) => { const post = await Post.create(input); pubsub.publish(POST_CREATED, { postCreated: post }); return post; }, updatePost: async (_, { id, input }) => { const post = await Post.update(id, input); pubsub.publish(POST_UPDATED, { postUpdated: post }); return post; }, addComment: async (_, { input }) => { const comment = await Comment.create(input); pubsub.publish(COMMENT_ADDED, { commentAdded: comment }); return comment; } } };

5. 数据加载器模式(DataLoader Pattern)

批量加载

javascript
const DataLoader = require('dataloader'); class DataLoaderContext { constructor() { this.userLoader = new DataLoader(this.batchGetUsers); this.postLoader = new DataLoader(this.batchGetPosts); } async batchGetUsers(userIds) { const users = await User.findAll({ where: { id: userIds } }); return userIds.map(id => users.find(user => user.id === id)); } async batchGetPosts(postIds) { const posts = await Post.findAll({ where: { id: postIds } }); return postIds.map(id => posts.find(post => post.id === id)); } }

在 Resolver 中使用

javascript
const resolvers = { Post: { author: (post, _, context) => { return context.dataLoaders.userLoader.load(post.authorId); }, comments: (post, _, context) => { return context.dataLoaders.commentLoader.load(post.id); } }, Query: { posts: async (_, __, context) => { const posts = await Post.findAll(); return posts; } } };

6. Schema 拆分与组合

Schema 拆分

graphql
# user.graphql type User { id: ID! name: String! email: String! } extend type Query { user(id: ID!): User users: [User!]! } extend type Mutation { createUser(input: CreateUserInput!): User updateUser(id: ID!, input: UpdateUserInput!): User deleteUser(id: ID!): Boolean }
graphql
# post.graphql type Post { id: ID! title: String! content: String! author: User! } extend type Query { post(id: ID!): Post posts: [Post!]! } extend type Mutation { createPost(input: CreatePostInput!): Post updatePost(id: ID!, input: UpdatePostInput!): Post deletePost(id: ID!): Boolean }

Schema 组合

javascript
const { mergeTypeDefs } = require('@graphql-tools/merge'); const { loadFilesSync } = require('@graphql-tools/load-files'); const typeDefsArray = loadFilesSync(path.join(__dirname, './schemas')); const typeDefs = mergeTypeDefs(typeDefsArray);

7. 微服务架构中的 GraphQL

网关模式

javascript
const { ApolloServer } = require('apollo-server'); const { ApolloGateway } = require('@apollo/gateway'); const { readFileSync } = require('fs'); const gateway = new ApolloGateway({ supergraphSdl: readFileSync('./supergraph.graphql').toString(), serviceList: [ { name: 'users', url: 'http://localhost:4001/graphql' }, { name: 'posts', url: 'http://localhost:4002/graphql' }, { name: 'comments', url: 'http://localhost:4003/graphql' } ] }); const server = new ApolloServer({ gateway, subscriptions: false }); server.listen().then(({ url }) => { console.log(`Gateway ready at ${url}`); });

联邦模式

graphql
# users service extend type Post @key(fields: "id") { id: ID! @external author: User } type User @key(fields: "id") { id: ID! name: String! email: String! }
graphql
# posts service type Post @key(fields: "id") { id: ID! title: String! content: String! authorId: ID! author: User } extend type User @key(fields: "id") { id: ID! @external posts: [Post!]! }

8. 错误处理与重试

自定义错误类

javascript
class GraphQLError extends Error { constructor(message, code, extensions = {}) { super(message); this.name = 'GraphQLError'; this.code = code; this.extensions = extensions; } } class ValidationError extends GraphQLError { constructor(message, field) { super(message, 'VALIDATION_ERROR', { field }); } } class AuthenticationError extends GraphQLError { constructor(message = 'Authentication required') { super(message, 'AUTHENTICATION_ERROR'); } } class AuthorizationError extends GraphQLError { constructor(message = 'Not authorized') { super(message, 'AUTHORIZATION_ERROR'); } }

错误处理中间件

javascript
const formatError = (error) => { if (error instanceof GraphQLError) { return { message: error.message, code: error.code, extensions: error.extensions }; } // 生产环境不暴露详细错误 if (process.env.NODE_ENV === 'production') { return { message: 'Internal server error', code: 'INTERNAL_SERVER_ERROR' }; } return error; };

9. 测试策略

Resolver 单元测试

javascript
const { resolvers } = require('./resolvers'); describe('User resolvers', () => { describe('Query.user', () => { it('should return user by id', async () => { const mockUser = { id: '1', name: 'John', email: 'john@example.com' }; User.findById = jest.fn().mockResolvedValue(mockUser); const result = await resolvers.Query.user(null, { id: '1' }); expect(result).toEqual(mockUser); expect(User.findById).toHaveBeenCalledWith('1'); }); it('should throw error if user not found', async () => { User.findById = jest.fn().mockResolvedValue(null); await expect( resolvers.Query.user(null, { id: '1' }) ).rejects.toThrow('User not found'); }); }); });

集成测试

javascript
const { ApolloServer } = require('apollo-server'); const { createTestClient } = require('apollo-server-testing'); const { typeDefs, resolvers } = require('./schema'); describe('GraphQL API', () => { const server = new ApolloServer({ typeDefs, resolvers }); const { query, mutate } = createTestClient(server); describe('Query.users', () => { it('should return all users', async () => { const GET_USERS = ` query GetUsers { users { id name email } } `; const { data, errors } = await query(GET_USERS); expect(errors).toBeUndefined(); expect(data.users).toBeDefined(); expect(Array.isArray(data.users)).toBe(true); }); }); describe('Mutation.createUser', () => { it('should create a new user', async () => { const CREATE_USER = ` mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name email } } `; const variables = { input: { name: 'John Doe', email: 'john@example.com' } }; const { data, errors } = await mutate(CREATE_USER, { variables }); expect(errors).toBeUndefined(); expect(data.createUser).toBeDefined(); expect(data.createUser.name).toBe('John Doe'); }); }); });

10. 高级架构模式

CQRS 模式

javascript
const resolvers = { Query: { // 读操作 - 使用优化的查询 user: async (_, { id }, { readDb }) => { return readDb.User.findById(id); } }, Mutation: { // 写操作 - 使用事件溯源 createUser: async (_, { input }, { writeDb, eventBus }) => { const user = await writeDb.User.create(input); await eventBus.publish('USER_CREATED', { user }); return user; } } };

事件溯源模式

javascript
class EventStore { async saveEvent(aggregateId, eventType, payload) { await Event.create({ aggregateId, eventType, payload: JSON.stringify(payload), timestamp: new Date() }); } async getEvents(aggregateId) { const events = await Event.findAll({ where: { aggregateId }, order: [['timestamp', 'ASC']] }); return events.map(event => ({ ...event, payload: JSON.parse(event.payload) })); } }

11. 高级概念总结

概念用途优势
联合类型处理多种类型的返回结果灵活的数据结构
接口类型定义共享字段代码复用、类型安全
自定义指令声明式功能可重用、可组合
订阅实时数据推送实时性、低延迟
DataLoader批量加载性能优化、减少查询
Schema 组合模块化设计可维护性、团队协作
微服务分布式架构可扩展性、独立部署
错误处理统一错误管理一致性、可调试性
测试策略质量保证可靠性、可维护性
架构模式解决复杂问题可扩展性、灵活性
标签:GraphQL