Qua nhiều năm xây dựng dịch vụ, API RESTful đã trở thành mục tiêu chính của tôi. Tuy nhiên, mặc dù REST có những ưu điểm nhưng điều đó không có nghĩa đó là cách tiếp cận tốt nhất cho mọi trường hợp sử dụng. Qua nhiều năm, tôi đã học được rằng, đôi khi, có thể có những lựa chọn thay thế tốt hơn cho một số tình huống nhất định. Gắn bó với REST chỉ vì tôi đam mê nó—khi nó không phù hợp—chỉ dẫn đến nợ công nghệ và mối quan hệ căng thẳng với chủ sở hữu sản phẩm.
Một trong những điểm khó khăn nhất của phương pháp RESTful là cần phải thực hiện nhiều yêu cầu để truy xuất tất cả thông tin cần thiết cho quyết định kinh doanh.
Ví dụ: giả sử tôi muốn có cái nhìn 360 độ về khách hàng. Tôi sẽ cần thực hiện các yêu cầu sau:
GET /customers/{some_token}
cung cấp thông tin khách hàng cơ sởGET /addresses/{some_token}
cung cấp địa chỉ bắt buộcGET /contacts/{some_token}
trả về thông tin liên hệGET /credit/{some_token}
trả về thông tin tài chính quan trọng
Mặc dù tôi hiểu mục tiêu cơ bản của REST là giữ cho các phản hồi tập trung vào từng tài nguyên, nhưng kịch bản này khiến phía người tiêu dùng phải làm nhiều việc hơn. Chỉ để đưa vào giao diện người dùng giúp tổ chức đưa ra các quyết định liên quan đến hoạt động kinh doanh trong tương lai với khách hàng, người tiêu dùng phải thực hiện nhiều cuộc gọi
Trong bài viết này, tôi sẽ chỉ ra lý do tại sao GraphQL là phương pháp được ưu tiên hơn API RESTful, trình bày cách triển khai Máy chủ Apollo (và Apollo Explorer) để thiết lập và chạy nhanh chóng với GraphQL.
Tôi dự định xây dựng giải pháp của mình với Node.js và triển khai giải pháp của mình cho Heroku.
Có một số trường hợp sử dụng phổ biến khi GraphQL là cách tiếp cận tốt hơn REST:
Vì kỹ năng của tôi với GraphQL chưa được trau dồi nên tôi đã quyết định sử dụng Apollo Server cho bài viết này.
Máy chủ Apollo là máy chủ GraphQL hoạt động với mọi lược đồ GraphQL. Mục tiêu là đơn giản hóa quá trình xây dựng API GraphQL. Thiết kế cơ bản tích hợp tốt với các framework như Express hoặc Koa. Tôi sẽ khám phá khả năng tận dụng đăng ký (thông qua thư viện graphql-ws ) cho dữ liệu thời gian thực trong bài viết tiếp theo của tôi.
Nơi Apollo Server thực sự tỏa sáng là Apollo Explorer, một giao diện web tích hợp mà các nhà phát triển có thể sử dụng để khám phá và thử nghiệm API GraphQL của họ. Studio này sẽ hoàn toàn phù hợp với tôi vì nó cho phép dễ dàng xây dựng các truy vấn và khả năng xem lược đồ API ở định dạng đồ họa.
Trong ví dụ này, giả sử chúng ta cần lược đồ sau để cung cấp cái nhìn 360 độ về khách hàng:
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 }
Tôi dự định tập trung vào các truy vấn GraphQL sau:
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 }
Người tiêu dùng sẽ cung cấp mã thông báo cho Khách hàng mà họ muốn xem. Chúng tôi hy vọng cũng sẽ truy xuất được các đối tượng Địa chỉ, Liên hệ và Tín dụng thích hợp. Mục tiêu là tránh thực hiện bốn lệnh gọi API khác nhau cho tất cả thông tin này thay vì chỉ thực hiện một lệnh gọi API.
Tôi bắt đầu bằng cách tạo một thư mục mới có tên là graphql-server-customer
trên máy trạm cục bộ của mình. Sau đó, bằng cách sử dụng phần Bắt đầu của tài liệu Máy chủ Apollo, tôi đã làm theo các bước một và hai bằng cách sử dụng phương pháp Typescript.
Tiếp theo, tôi xác định lược đồ của mình và cũng bao gồm một số dữ liệu tĩnh để thử nghiệm. Thông thường, chúng tôi sẽ kết nối với cơ sở dữ liệu, nhưng dữ liệu tĩnh sẽ hoạt động tốt cho bản demo này.
Dưới đây là tệp index.ts
được cập nhật của tôi:
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 } ];
Với mọi thứ được định cấu hình như mong đợi, chúng tôi chạy lệnh sau để khởi động máy chủ:
$ npm start
Với máy chủ Apollo chạy trên cổng 4000, tôi đã sử dụng URL http://localhost:4000/ để truy cập Apollo Explorer. Sau đó tôi thiết lập truy vấn mẫu sau:
query ExampleQuery { addresses { token } contacts { token } customers { token } }
Đây là giao diện của nó trong Apollo Explorer:
Nhấn nút Truy vấn mẫu , tôi xác thực rằng tải trọng phản hồi được căn chỉnh với dữ liệu tĩnh mà tôi đã cung cấp trong 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" } ] } }
Trước khi tiếp tục giải quyết trường hợp sử dụng Customer 360 của mình, tôi muốn chạy dịch vụ này trên đám mây.
Vì bài viết này chủ yếu nói về việc làm một điều gì đó mới mẻ nên tôi muốn xem việc triển khai máy chủ Apollo của mình lên Heroku sẽ khó đến mức nào.
Tôi biết mình phải giải quyết sự khác biệt về số cổng giữa chạy cục bộ và chạy ở đâu đó trên đám mây. Tôi đã cập nhật mã của mình để khởi động máy chủ như dưới đây:
const { url } = await startStandaloneServer(server, { listen: { port: Number.parseInt(process.env.PORT) || 4000 }, });
Với bản cập nhật này, chúng tôi sẽ sử dụng cổng 4000 trừ khi có giá trị PORT được chỉ định trong biến môi trường.
Sử dụng Gitlab, tôi đã tạo một dự án mới cho các tệp này và đăng nhập vào tài khoản Heroku của mình bằng giao diện dòng lệnh Heroku (CLI):
$ heroku login
Bạn có thể tạo một ứng dụng mới trong Heroku bằng CLI của họ hoặc giao diện người dùng web bảng điều khiển Heroku. Đối với bài viết này, chúng tôi sẽ sử dụng CLI:
$ heroku create jvc-graphql-server-customer
Lệnh CLI trả về phản hồi sau:
Creating ⬢ jvc-graphql-server-customer... done https://jvc-graphql-server-customer-b62b17a2c949.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-customer.git
Lệnh cũng tự động thêm kho lưu trữ được Heroku sử dụng làm điều khiển từ xa:
$ git remote heroku origin
Theo mặc định, Máy chủ Apollo vô hiệu hóa Apollo Explorer trong môi trường sản xuất. Đối với bản demo của tôi, tôi muốn để nó chạy trên Heroku. Để làm điều này, tôi cần đặt biến môi trường NODE_ENV
thành Development. Tôi có thể thiết lập điều đó bằng lệnh CLI sau:
$ heroku config:set NODE_ENV=development
Lệnh CLI trả về phản hồi sau:
Setting NODE_ENV and restarting ⬢ jvc-graphql-server-customer... done, v3 NODE_ENV: development
Bây giờ chúng ta đã có thể triển khai mã của mình lên Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku
Chế độ xem nhanh Bảng điều khiển Heroku cho thấy Máy chủ Apollo của tôi đang chạy mà không gặp bất kỳ sự cố nào:
Nếu bạn mới làm quen với Heroku, hướng dẫn này sẽ chỉ cho bạn cách tạo tài khoản mới và cài đặt Heroku CLI.
Với GraphQL, tôi có thể đáp ứng các tiêu chí chấp nhận cho trường hợp sử dụng Customer 360 của mình bằng truy vấn sau:
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 } }
Tất cả những gì tôi cần làm là chuyển vào một biến Customer token
duy nhất có giá trị customer-token-1
:
{ "token": "customer-token-1" }
Chúng tôi có thể truy xuất tất cả dữ liệu bằng một lệnh gọi API GraphQL duy nhất:
{ "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 } } }
Dưới đây là ảnh chụp màn hình từ Apollo Explorer chạy từ ứng dụng Heroku của tôi:
Tôi nhớ lại trước đây trong sự nghiệp của mình khi Java và C# cạnh tranh với nhau để giành được sự chấp nhận của nhà phát triển. Những người ủng hộ ở mỗi bên của cuộc tranh luận đã sẵn sàng chứng minh rằng công nghệ họ chọn là lựa chọn tốt nhất… ngay cả khi thực tế không phải vậy.
Trong ví dụ này, chúng tôi có thể đáp ứng trường hợp sử dụng Customer 360 của tôi theo nhiều cách. Việc sử dụng API RESTful đã được chứng minh sẽ có hiệu quả nhưng sẽ yêu cầu nhiều lệnh gọi API để truy xuất tất cả dữ liệu cần thiết. Việc sử dụng Apollo Server và GraphQL cho phép tôi đạt được mục tiêu của mình chỉ bằng một lệnh gọi API.
Tôi cũng thích việc triển khai máy chủ GraphQL của mình lên Heroku dễ dàng như thế nào chỉ bằng một vài lệnh trong thiết bị đầu cuối của mình. Điều này cho phép tôi tập trung vào việc triển khai—giảm bớt gánh nặng về cơ sở hạ tầng và chạy mã của mình cho nhà cung cấp bên thứ ba đáng tin cậy. Quan trọng nhất, điều này phù hợp với tuyên bố sứ mệnh cá nhân của tôi:
“Hãy tập trung thời gian vào việc cung cấp các tính năng/chức năng giúp nâng cao giá trị tài sản trí tuệ của bạn. Tận dụng các khuôn khổ, sản phẩm và dịch vụ cho mọi thứ khác.”
– J. Vester
Nếu bạn quan tâm đến mã nguồn của bài viết này, nó có sẵn trên GitLab .
Nhưng xin chờ chút nữa!
Trong bài đăng tiếp theo, chúng tôi sẽ xây dựng thêm máy chủ GraphQL của mình để triển khai xác thực và truy xuất dữ liệu theo thời gian thực bằng các đăng ký.
Chúc bạn có một ngày thật tuyệt vời!