在我多年的服务构建过程中, 一直是我的主要选择。然而,尽管 REST 有其优点,但这并不意味着它是每种用例的最佳方法。多年来,我了解到,有时,在某些情况下可能会有更好的替代方案。仅仅因为我对 REST 充满热情而坚持使用它——当它并不合适时——只会导致技术债务和与产品所有者的紧张关系。 RESTful API RESTful 方法的最大痛点之一是需要发出多个请求来检索业务决策所需的所有必要信息。 举个例子,假设我想要一个客户的 360 度视图。我需要提出以下请求: 提供基本客户信息 GET /customers/{some_token} 提供所需地址 GET /addresses/{some_token} 返回联系信息 GET /contacts/{some_token} 返回关键财务信息 GET /credit/{some_token} 虽然我理解 REST 的根本目标是让响应针对每个资源保持高度集中,但这种情况会让消费者方面做更多的工作。仅仅为了填充一个用户界面,帮助组织做出与客户未来业务相关的决策,消费者就必须进行多次调用 在本文中,我将展示为什么 是比 RESTful API 更受欢迎的方法,并演示如何部署 Apollo Server(和 Apollo Explorer)以快速启动和运行 GraphQL。 GraphQL 我计划用 Node.js 构建我的解决方案并将我的解决方案部署到 Heroku。 何时使用 GraphQL 而不是 REST 有几种常见的用例表明 GraphQL 比 REST 更好: 当你需要 检索数据时:你可以在 中从各种资源获取复杂数据。(我将在本文中深入探讨这条路径。) 灵活地 一个请求 当前端团队需要频繁 UI 时:快速变化的 不会需要后端调整端点并造成阻碍。 改进 数据需求 当您想要 和 :有时 REST 要求您点击多个端点来收集所需的所有数据(获取不足),或者点击单个端点会返回比您实际需要的多得多的数据(过度获取)。 尽量减少过度获取 获取不足时 当您使用复杂系统和微服务时:有时 只需要访问单个 API 层即可获取其数据。GraphQL 可以通过 提供这种灵活性。 多个源 单个 API 调用 当你需要实时数据推送时:GraphQL 具有 功能,可提供实时更新。这在聊天应用程序或实时数据馈送的情况下非常有用。(我将在后续文章中更详细地介绍这一好处。) 订阅 什么是 Apollo 服务器? 由于我对 的技能还不够熟练,因此我决定在本文中使用 。 GraphQL Apollo Server Apollo Server 是一个 GraphQL 服务器,可与任何 GraphQL 模式配合使用。其目标是简化构建 GraphQL API 的过程。底层设计与 Express 或 Koa 等框架集成良好。我将在下一篇文章中探讨利用订阅(通过 库)获取实时数据的能力。 graphql-ws Apollo Server 真正出彩的地方是 Apollo Explorer,这是一个内置的 Web 界面,开发人员可以使用它来探索和测试他们的 GraphQL API。该工作室非常适合我,因为它可以轻松构建查询,并能够以图形格式查看 API 架构。 我的客户 360 用例 对于此示例,我们假设我们需要以下模式来提供客户的 360 度视图: type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int } 我计划重点关注以下 GraphQL 查询: type Query { addresses: [Address] address(customer_token: String): Address contacts: [Contact] contact(customer_token: String): Contact customers: [Customer] customer(token: String): Customer credits: [Credit] credit(customer_token: String): Credit } 消费者将为他们希望查看的客户提供令牌。我们还希望检索适当的地址、联系人和信用对象。目标是避免对所有这些信息进行四次不同的 API 调用,而不是使用一次 API 调用。 Apollo 服务器入门 我首先在本地工作站上创建了一个名为 新文件夹。然后,使用 Apollo Server 文档的 部分,我使用 Typescript 方法执行了第一步和第二步。 graphql-server-customer “入门” 接下来,我定义了架构,并包含了一些静态数据以供测试。通常,我们会连接到数据库,但静态数据对于此演示来说就足够了。 以下是我更新的 文件: index.ts import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; const typeDefs = `#graphql type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int } type Query { addresses: [Address] address(customer_token: String): Address contacts: [Contact] contact(customer_token: String): Contact customers: [Customer] customer(token: String): Customer credits: [Credit] credit(customer_token: String): Credit } `; const resolvers = { Query: { addresses: () => addresses, address: (parent, args, context) => { const customer_token = args.customer_token; return addresses.find(address => address.customer_token === customer_token); }, contacts: () => contacts, contact: (parent, args, context) => { const customer_token = args.customer_token; return contacts.find(contact => contact.customer_token === customer_token); }, customers: () => customers, customer: (parent, args, context) => { const token = args.token; return customers.find(customer => customer.token === token); }, credits: () => credits, credit: (parent, args, context) => { const customer_token = args.customer_token; return credits.find(credit => credit.customer_token === customer_token); } }, }; const server = new ApolloServer({ typeDefs, resolvers, }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`Apollo Server ready at: ${url}`); const customers = [ { token: 'customer-token-1', name: 'Acme Inc.', sic_code: '1234' }, { token: 'customer-token-2', name: 'Widget Co.', sic_code: '5678' } ]; const addresses = [ { token: 'address-token-1', customer_token: 'customer-token-1', address_line1: '123 Main St.', address_line2: '', city: 'Anytown', state: 'CA', postal_code: '12345' }, { token: 'address-token-22', customer_token: 'customer-token-2', address_line1: '456 Elm St.', address_line2: '', city: 'Othertown', state: 'NY', postal_code: '67890' } ]; const contacts = [ { token: 'contact-token-1', customer_token: 'customer-token-1', first_name: 'John', last_name: 'Doe', email: 'jdoe@example.com', phone: '123-456-7890' } ]; const credits = [ { token: 'credit-token-1', customer_token: 'customer-token-1', credit_limit: 10000.00, balance: 2500.00, credit_score: 750 } ]; 一切按预期配置后,我们运行以下命令来启动服务器: $ npm start 当 Apollo 服务器在端口 4000 上运行时,我使用 URL 访问 Apollo Explorer。然后我设置了以下示例查询: http://localhost:4000/ query ExampleQuery { addresses { token } contacts { token } customers { token } } 它在 Apollo Explorer 中的样子如下: 按下 按钮后,我验证了响应负载与我在 中提供的静态数据一致: 示例查询 index.ts { "data": { "addresses": [ { "token": "address-token-1" }, { "token": "address-token-22" } ], "contacts": [ { "token": "contact-token-1" } ], "customers": [ { "token": "customer-token-1" }, { "token": "customer-token-2" } ] } } 在进一步解决我的客户 360 用例之前,我想在云中运行这项服务。 将 Apollo 服务器部署到 Heroku 由于本文是关于做一些新事情,我想看看将我的 Apollo 服务器部署到 Heroku 有多难。 我知道我必须解决本地运行和云中运行之间的端口号差异。我更新了启动服务器的代码,如下所示: const { url } = await startStandaloneServer(server, { listen: { port: Number.parseInt(process.env.PORT) || 4000 }, }); 在此更新中,我们将使用端口 4000,除非环境变量中指定了 PORT 值。 使用 Gitlab,我为这些文件创建了一个新项目,并使用 Heroku 命令行界面(CLI)登录到我的 Heroku 帐户: $ heroku login 您可以使用其 CLI 或 Heroku 仪表板 Web UI 在 Heroku 中创建新应用程序。在本文中,我们将使用 CLI: $ heroku create jvc-graphql-server-customer CLI 命令返回以下响应: Creating ⬢ jvc-graphql-server-customer... done https://jvc-graphql-server-customer-b62b17a2c949.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-customer.git 该命令还自动将 Heroku 使用的存储库添加为远程: $ git remote heroku origin 默认情况下,Apollo Server 在生产环境中禁用 Apollo Explorer。对于我的演示,我想让它在 Heroku 上运行。为此,我需要将 环境变量设置为开发。我可以使用以下 CLI 命令进行设置: NODE_ENV $ heroku config:set NODE_ENV=development CLI 命令返回以下响应: Setting NODE_ENV and restarting ⬢ jvc-graphql-server-customer... done, v3 NODE_ENV: development 现在我们可以将代码部署到 Heroku 了: $ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku 快速查看 Heroku 仪表板显示我的 Apollo 服务器运行正常: 如果您是 Heroku 新手, 将向您展示如何创建新帐户并安装 Heroku CLI。 本指南 满足验收标准:我的客户 360 示例 使用 GraphQL,我可以通过以下查询满足我的客户 360 用例的验收标准: query CustomerData($token: String) { customer(token: $token) { name sic_code token }, address(customer_token: $token) { token customer_token address_line1 address_line2 city state postal_code }, contact(customer_token: $token) { token, customer_token, first_name, last_name, email, phone }, credit(customer_token: $token) { token, customer_token, credit_limit, balance, credit_score } } 我需要做的就是传入一个值为 的客户 变量: customer-token-1 token { "token": "customer-token-1" } 我们可以使用单个 GraphQL API 调用检索所有数据: { "data": { "customer": { "name": "Acme Inc.", "sic_code": "1234", "token": "customer-token-1" }, "address": { "token": "address-token-1", "customer_token": "customer-token-1", "address_line1": "123 Main St.", "address_line2": "", "city": "Anytown", "state": "CA", "postal_code": "12345" }, "contact": { "token": "contact-token-1", "customer_token": "customer-token-1", "first_name": "John", "last_name": "Doe", "email": "jdoe@example.com", "phone": "123-456-7890" }, "credit": { "token": "credit-token-1", "customer_token": "customer-token-1", "credit_limit": 10000, "balance": 2500, "credit_score": 750 } } } 下面是从我的 Heroku 应用程序运行的 Apollo Explorer 的屏幕截图: 结论 我记得在我职业生涯的早期,Java 和 C# 曾相互竞争,争夺开发人员的采用。争论双方的支持者都准备证明他们选择的技术是最佳选择……即使事实并非如此。 在此示例中,我们可以通过多种方式满足我的 Customer 360 用例。使用经过验证的 RESTful API 是可行的,但需要多次 API 调用才能检索所有必要的数据。使用 Apollo Server 和 GraphQL 让我能够通过一次 API 调用实现我的目标。 我也很喜欢只需在终端中输入几个命令即可轻松将 GraphQL 服务器部署到 Heroku。这使我能够专注于实施 — 减轻基础设施负担并将代码运行到受信任的第三方提供商。最重要的是,这完全符合我的个人使命宣言: “将时间集中在提供能够扩展知识产权价值的功能上。其他一切都要利用框架、产品和服务。” – J. Vester 如果你对本文的源代码感兴趣,可以在 上找到。 GitLab 但等一下...还有更多! 在我的后续文章中,我们将进一步构建我们的 GraphQL 服务器,以实现身份验证和通过订阅进行实时数据检索。 祝您度过愉快的一天!