GraphQL 有哪些高级概念和架构设计模式
GraphQL 高级概念与架构设计GraphQL 不仅仅是查询语言,它还包含许多高级概念和架构设计模式,掌握这些对于构建大规模 GraphQL 应用至关重要。1. 联合类型(Union Types)定义联合类型union SearchResult = User | Post | Commenttype Query { search(query: String!): [SearchResult!]!}实现联合类型 Resolverconst 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]; } }};使用联合类型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)定义接口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}实现接口 Resolverconst 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)创建自定义指令directive @auth(requires: Role) on FIELD_DEFINITIONdirective @cache(ttl: Int) on FIELD_DEFINITIONdirective @transform(type: String) on FIELD_DEFINITIONenum Role { USER ADMIN}实现指令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)定义订阅type Subscription { postCreated: Post! postUpdated(postId: ID!): Post! commentAdded(postId: ID!): Comment!}实现订阅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)批量加载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 中使用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 拆分# user.graphqltype 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}# post.graphqltype 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 组合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网关模式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}`);});联邦模式# users serviceextend type Post @key(fields: "id") { id: ID! @external author: User}type User @key(fields: "id") { id: ID! name: String! email: String!}# posts servicetype 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. 错误处理与重试自定义错误类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'); }}错误处理中间件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 单元测试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'); }); });});集成测试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 模式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; } }};事件溯源模式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 组合 | 模块化设计 | 可维护性、团队协作 || 微服务 | 分布式架构 | 可扩展性、独立部署 || 错误处理 | 统一错误管理 | 一致性、可调试性 || 测试策略 | 质量保证 | 可靠性、可维护性 || 架构模式 | 解决复杂问题 | 可扩展性、灵活性 |