在我多年的服务构建过程中, RESTful API一直是我的主要选择。然而,尽管 REST 有其优点,但这并不意味着它是每种用例的最佳方法。多年来,我了解到,有时,在某些情况下可能会有更好的替代方案。仅仅因为我对 REST 充满热情而坚持使用它——当它并不合适时——只会导致技术债务和与产品所有者的紧张关系。
RESTful 方法的最大痛点之一是需要发出多个请求来检索业务决策所需的所有必要信息。
举个例子,假设我想要一个客户的 360 度视图。我需要提出以下请求:
GET /customers/{some_token}
提供基本客户信息GET /addresses/{some_token}
提供所需地址GET /contacts/{some_token}
返回联系信息GET /credit/{some_token}
返回关键财务信息
虽然我理解 REST 的根本目标是让响应针对每个资源保持高度集中,但这种情况会让消费者方面做更多的工作。仅仅为了填充一个用户界面,帮助组织做出与客户未来业务相关的决策,消费者就必须进行多次调用
在本文中,我将展示为什么GraphQL是比 RESTful API 更受欢迎的方法,并演示如何部署 Apollo Server(和 Apollo Explorer)以快速启动和运行 GraphQL。
我计划用 Node.js 构建我的解决方案并将我的解决方案部署到 Heroku。
有几种常见的用例表明 GraphQL 比 REST 更好:
由于我对GraphQL的技能还不够熟练,因此我决定在本文中使用Apollo Server 。
Apollo Server 是一个 GraphQL 服务器,可与任何 GraphQL 模式配合使用。其目标是简化构建 GraphQL API 的过程。底层设计与 Express 或 Koa 等框架集成良好。我将在下一篇文章中探讨利用订阅(通过graphql-ws库)获取实时数据的能力。
Apollo Server 真正出彩的地方是 Apollo Explorer,这是一个内置的 Web 界面,开发人员可以使用它来探索和测试他们的 GraphQL API。该工作室非常适合我,因为它可以轻松构建查询,并能够以图形格式查看 API 架构。
对于此示例,我们假设我们需要以下模式来提供客户的 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 调用。
我首先在本地工作站上创建了一个名为graphql-server-customer
新文件夹。然后,使用 Apollo Server 文档的“入门”部分,我使用 Typescript 方法执行了第一步和第二步。
接下来,我定义了架构,并包含了一些静态数据以供测试。通常,我们会连接到数据库,但静态数据对于此演示来说就足够了。
以下是我更新的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: '[email protected]', 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 上运行时,我使用http://localhost:4000/ URL 访问 Apollo Explorer。然后我设置了以下示例查询:
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 有多难。
我知道我必须解决本地运行和云中运行之间的端口号差异。我更新了启动服务器的代码,如下所示:
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 上运行。为此,我需要将NODE_ENV
环境变量设置为开发。我可以使用以下 CLI 命令进行设置:
$ 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。
使用 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": "[email protected]", "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 服务器,以实现身份验证和通过订阅进行实时数据检索。
祝您度过愉快的一天!