Công cụ giải quyết GraphQL: Thực tiễn tốt nhất

Từ graphql.org

Bài đăng này là phần đầu tiên của một loạt các thực tiễn và quan sát tốt nhất mà chúng tôi đã thực hiện trong khi xây dựng API GraphQL tại PayPal. Trong các bài đăng sắp tới, chúng tôi sẽ chia sẻ suy nghĩ của chúng tôi về: thiết kế lược đồ, xử lý lỗi, khả năng hiển thị sản xuất, tối ưu hóa tích hợp phía máy khách và công cụ cho các nhóm.

Bạn có thể đã thấy bài đăng trước đây của chúng tôi về GraphQL: Câu chuyện thành công cho PayPal Checkout LINE về hành trình PayPal PayPal từ REST đến GraphQL. Bài đăng này đi sâu vào chi tiết một số thực tiễn tốt nhất để xây dựng các bộ giải quyết nhanh, có thể kiểm tra và có khả năng phục hồi theo thời gian.

Những gì một người giải quyết?

Hãy để Lốc bắt đầu ở cùng một đường cơ sở. Những gì một người giải quyết?

Định nghĩa độ phân giải
Mỗi trường trên mọi loại được hỗ trợ bởi một hàm gọi là bộ giải.

Bộ giải quyết là một hàm phân giải giá trị cho một loại hoặc trường trong lược đồ. Bộ giải quyết có thể trả về các đối tượng hoặc vô hướng như Chuỗi, Số, Booleans, v.v ... Nếu một Đối tượng được trả về, việc thực thi tiếp tục đến trường con tiếp theo. Nếu một vô hướng được trả về (thường là tại một nút lá), việc thực thi hoàn thành. Nếu null được trả về, việc thực thi dừng lại và không tiếp tục.

Bộ giải quyết có thể không đồng bộ quá! Họ có thể giải quyết các giá trị từ một API REST, cơ sở dữ liệu, bộ đệm, hằng số, v.v.

Sau đó, chúng ta sẽ đi qua một loạt các ví dụ minh họa cách xây dựng các bộ phân giải nhanh, có thể kiểm tra và có khả năng phục hồi.

Thực hiện truy vấn

Để hiểu rõ hơn về trình phân giải, bạn cần biết cách thực hiện các truy vấn.

Mỗi truy vấn GraphQL đều trải qua ba giai đoạn. Các truy vấn được phân tích cú pháp, xác nhận và thực hiện.

  1. Phân tích cú pháp - Một truy vấn được phân tích cú pháp thành một cây cú pháp trừu tượng (hoặc AST). AST rất mạnh và đứng sau các công cụ như ESLint, babel, v.v. Nếu bạn muốn xem ASTQL AST trông như thế nào, hãy xem astexplorer.net và thay đổi JavaScript thành GraphQL. Bạn sẽ thấy một truy vấn ở bên trái và AST ở bên phải.
  2. Xác thực - AST được xác thực theo lược đồ. Kiểm tra cú pháp truy vấn chính xác và nếu các trường tồn tại.
  3. Thực thi - Thời gian chạy đi qua AST, bắt đầu từ gốc của cây, gọi trình phân giải, thu thập kết quả và phát ra JSON.

Trong ví dụ này, chúng tôi sẽ đề cập đến truy vấn này:

Truy vấn để tham khảo sau

Khi truy vấn này được phân tích cú pháp, nó sẽ chuyển đổi thành AST hoặc cây.

Truy vấn được biểu diễn dưới dạng cây

Kiểu truy vấn gốc là điểm vào cây và chứa hai trường gốc, người dùng và album của chúng tôi. Trình phân giải người dùng và album được thực thi song song (đó là điển hình trong số tất cả các thời gian chạy). Cây được thực hiện trước tiên, nghĩa là người dùng phải được giải quyết trước khi tên và email con của nó được thực thi. Nếu trình phân giải người dùng không đồng bộ, nhánh người dùng sẽ trì hoãn cho đến khi nó được giải quyết. Khi tất cả các nút lá, tên, email, tiêu đề, được giải quyết, thực hiện hoàn tất.

Các trường Truy vấn gốc, như người dùng và album, được thực hiện song song nhưng không theo thứ tự cụ thể. Thông thường, các trường được thực thi theo thứ tự chúng xuất hiện trong truy vấn, nhưng nó không an toàn khi giả định điều đó. Bởi vì các trường được thực thi song song, chúng được coi là nguyên tử, không có giá trị và không có tác dụng phụ.

Nhìn gần hơn vào các bộ giải

Trong một số phần tiếp theo, chúng tôi sẽ sử dụng JavaScript, nhưng máy chủ GraphQL có thể được viết bằng hầu hết mọi ngôn ngữ.

Trình giải quyết với bốn đối số - root, args, bối cảnh, thông tin

Ở dạng này hay dạng khác, mọi trình phân giải trong mọi ngôn ngữ đều nhận được bốn đối số sau:

  • root - Kết quả từ loại trước / cha
  • args - Đối số cung cấp cho lĩnh vực này
  • bối cảnh - một đối tượng Mutable được cung cấp cho tất cả các trình phân giải
  • thông tin - Thông tin cụ thể theo trường liên quan đến truy vấn (hiếm khi được sử dụng)

Bốn đối số này là cốt lõi để hiểu cách dữ liệu chảy giữa các bộ phân giải.

Bộ giải quyết mặc định

Trước khi chúng tôi tiếp tục, điều đáng chú ý là máy chủ GraphQL có các bộ phân giải mặc định tích hợp sẵn, do đó, bạn không phải chỉ định một hàm phân giải cho mọi trường. Một trình phân giải mặc định sẽ tìm trong root để tìm một thuộc tính có cùng tên với trường. Một triển khai có thể trông như thế này:

Thực hiện trình giải quyết mặc định

Tìm nạp dữ liệu trong bộ phân giải

Chúng ta nên lấy dữ liệu ở đâu? Sự đánh đổi với các lựa chọn của chúng tôi là gì?

Trong một vài ví dụ tiếp theo, chúng tôi sẽ quay lại lược đồ này:

Trường sự kiện có đối số id bắt buộc, trả về Sự kiện

Truyền dữ liệu giữa các bộ giải

bối cảnh là một Đối tượng có thể thay đổi được cung cấp cho tất cả các trình phân giải. Nó đã tạo ra và phá hủy giữa mọi yêu cầu. Đây là một nơi tuyệt vời để lưu trữ dữ liệu Auth chung, các mô hình / trình tải xuống chung cho API và cơ sở dữ liệu, v.v. Tại PayPal, chúng tôi là một cửa hàng Node.js lớn được xây dựng trên Express, vì vậy chúng tôi lưu trữ req Express Express ở đó.

Khi bạn lần đầu tiên tìm hiểu về bối cảnh, một suy nghĩ ban đầu có thể là sử dụng bối cảnh làm bộ đệm cho mục đích chung. Điều này không được khuyến khích, nhưng đây là cách triển khai có thể như thế nào.

Truyền dữ liệu giữa các bộ giải quyết sử dụng bối cảnh. Điều này không được khuyến khích!

Khi tiêu đề được gọi, chúng tôi lưu trữ kết quả sự kiện trong bối cảnh. Khi photoUrl được gọi, chúng tôi kéo sự kiện ra khỏi bối cảnh và sử dụng nó. Mã này không đáng tin cậy. Không có gì đảm bảo rằng tiêu đề sẽ được thực thi trước photoUrl.

Chúng tôi có thể sửa cả hai bộ giải để kiểm tra xem sự kiện có tồn tại trong ngữ cảnh hay không. Nếu vậy, sử dụng nó. Mặt khác, chúng tôi lấy nó và lưu trữ nó sau, nhưng ở đó vẫn còn một diện tích bề mặt lớn cho những sai lầm.

Thay vào đó, chúng ta nên tránh bối cảnh đột biến bên trong các bộ giải. Chúng ta nên ngăn chặn kiến ​​thức và mối quan tâm trộn lẫn với nhau, để người giải quyết của chúng ta dễ hiểu, gỡ lỗi và kiểm tra.

Truyền dữ liệu từ cha mẹ sang con

Đối số gốc là để truyền dữ liệu từ trình phân giải cha sang trình phân giải con.

Ví dụ: nếu bạn đang xây dựng loại Sự kiện trong đó tất cả các trường của Sự kiện
phụ thuộc vào cùng một dữ liệu, bạn có thể muốn tìm nạp nó một lần tại trường sự kiện,
thay vì ở mọi lĩnh vực của sự kiện.

Có vẻ như là một ý tưởng tốt, phải không? Đây là một cách nhanh chóng để bắt đầu với việc xây dựng bộ giải quyết nhưng bạn có thể gặp phải một số vấn đề. Hãy để hiểu tại sao.

Đối với các ví dụ bên dưới, chúng tôi sẽ làm việc với loại Sự kiện có hai trường.

Loại sự kiện với hai trường: title và photoUrl

Hầu hết các trường cho Sự kiện có thể được tìm nạp từ API Sự kiện, vì vậy chúng tôi có thể tìm nạp nó ở trình phân giải sự kiện cấp cao nhất và cung cấp kết quả cho trình phân giải tiêu đề và photoUrl của chúng tôi.

Trình phân giải sự kiện cấp cao nhất tìm nạp dữ liệu, cung cấp kết quả cho các trình phân giải trường tiêu đề và photoUrl

Thậm chí tốt hơn, chúng tôi không cần phải xác định hai bộ giải quyết dưới cùng.
Chúng ta có thể sử dụng các trình phân giải mặc định vì Đối tượng được trả về bởi getEvent ()
có một tiêu đề và thuộc tính photoUrl.

id và tiêu đề được giải quyết bằng các trình phân giải mặc định

Điều gì sai trái với điều này?

Có hai kịch bản mà bạn có thể gặp phải khi tải quá mức

Kịch bản # 1: Tìm nạp dữ liệu nhiều lớp

Hãy để nói rằng một số yêu cầu xuất hiện và bạn cần hiển thị một người tham dự sự kiện. Chúng tôi bắt đầu bằng cách thêm một trường tham dự vào Sự kiện.

Loại sự kiện với một trường tham dự bổ sung

Khi bạn tìm nạp thông tin chi tiết về người tham dự, bạn có hai tùy chọn: tìm nạp dữ liệu đó tại trình giải quyết sự kiện hoặc trình giải quyết người tham dự.

Chúng tôi sẽ kiểm tra tùy chọn đầu tiên: thêm nó vào trình giải quyết sự kiện.

trình giải quyết sự kiện gọi hai API, tìm nạp chi tiết sự kiện và chi tiết người tham dự

Nếu khách hàng chỉ truy vấn tiêu đề và photoUrl, nhưng không phải người tham dự. Bây giờ bạn không hiệu quả và đưa ra yêu cầu không cần thiết cho API người tham dự.

Nó không phải lỗi của bạn, đây là cách chúng tôi làm việc. Chúng tôi nhận ra các mẫu và sao chép chúng.
Nếu những người đóng góp thấy rằng việc tìm nạp dữ liệu được thực hiện trong trình giải quyết sự kiện, thì có khả năng họ sẽ
thêm bất kỳ dữ liệu bổ sung nào vào đó mà không suy nghĩ quá nhiều về nó.

Chúng tôi có thêm một lựa chọn để kiểm tra với việc tìm nạp những người tham dự bên trong trình giải quyết của người tham dự.

người tham dự giải quyết lấy thông tin chi tiết về người tham dự từ API người tham dự

Nếu khách hàng của chúng tôi chỉ truy vấn người tham dự, không phải tiêu đề và photoUrl. Chúng tôi vẫn không hiệu quả bằng cách đưa ra yêu cầu không cần thiết cho API Sự kiện.

Kịch bản # 2: Vấn đề N + 1

Vì dữ liệu được tìm nạp ở cấp trường, chúng tôi có nguy cơ tải quá mức. Quá tải và vấn đề N + 1 là một chủ đề phổ biến trong thế giới GraphQL. Shopify có một bài viết tuyệt vời giải thích tốt về N + 1.

Điều đó ảnh hưởng đến chúng ta ở đây như thế nào?

Để minh họa nó tốt hơn, chúng tôi sẽ thêm một trường sự kiện mới trả về tất cả các sự kiện.

Một trường sự kiện trả về tất cả các sự kiện.Truy vấn cho tất cả các sự kiện với tiêu đề và người tham dự của họ

Nếu khách hàng truy vấn tất cả các sự kiện và người tham dự của họ, chúng tôi có nguy cơ quá tải vì người tham dự có thể tham dự nhiều hơn một sự kiện. Chúng tôi có thể thực hiện các yêu cầu trùng lặp cho cùng một người tham dự.

Vấn đề này được khuếch đại trong một tổ chức lớn, nơi các yêu cầu có thể thoát ra và gây ra áp lực không cần thiết cho hệ thống của bạn.

Để giải quyết vấn đề này, chúng tôi cần phải xử lý các yêu cầu hàng loạt và khử trùng!

Trong JavaScript, một số tùy chọn phổ biến là nguồn dữ liệu và nguồn dữ liệu Apollo.

Nếu bạn sử dụng ngôn ngữ khác, thì có khả năng bạn có thể chọn thứ gì đó. Vì vậy, hãy xem xét xung quanh trước khi tự mình giải quyết vấn đề này.

Tại cốt lõi của nó, các thư viện này nằm trên cùng của lớp truy cập dữ liệu của bạn và sẽ lưu trữ và xóa các yêu cầu gửi đi bằng cách sử dụng gỡ lỗi hoặc ghi nhớ. Nếu bạn tò mò về việc ghi nhớ async trông như thế nào, hãy xem bài viết tuyệt vời của Daniel Brain.

Tìm nạp dữ liệu ở cấp trường

Trước đó, chúng tôi đã thấy rằng nó dễ dàng bị đốt cháy bằng cách nạp quá nhiều với các bộ giải quyết cha mẹ và con cái nặng hàng đầu.

Có một lựa chọn tốt hơn?

Hãy để trêu chọc tùy chọn cha mẹ và con một lần nữa. Điều gì xảy ra nếu chúng ta đảo ngược điều đó để các trường con của chúng ta chịu trách nhiệm tìm nạp dữ liệu của riêng chúng?

Các lĩnh vực chịu trách nhiệm cho việc tìm nạp dữ liệu của riêng họ.
Tại sao điều này là một sự thay thế tốt hơn?

Mã này là dễ dàng để lý do về. Bạn biết chính xác nơi một email được lấy. Điều này làm cho việc gỡ lỗi dễ dàng.

Mã này dễ kiểm tra hơn. Bạn không cần phải kiểm tra trình giải quyết sự kiện khi bạn thực sự chỉ muốn kiểm tra trình giải quyết tiêu đề.

Đối với một số người, sự trùng lặp getEvent có thể trông giống như mùi mã. Nhưng, có mã đơn giản, dễ lý luận và dễ kiểm chứng hơn có giá trị một chút trùng lặp.

Nhưng, vẫn còn một vấn đề tiềm năng ở đây. Nếu khách hàng truy vấn tiêu đề và photoUrl, chúng tôi sẽ đưa ra một yêu cầu bổ sung trên API sự kiện của chúng tôi với getEvent. Như chúng ta đã thấy trước đó trong vấn đề N + 1, chúng ta nên hủy các yêu cầu ở cấp độ khung bằng các thư viện như bộ tải dữ liệu và nguồn dữ liệu Apollo.

Nếu chúng tôi tìm nạp dữ liệu ở cấp trường và yêu cầu khấu trừ, chúng tôi có mã dễ gỡ lỗi và kiểm tra hơn và chúng tôi có thể tìm nạp dữ liệu một cách tối ưu mà không cần suy nghĩ về nó.

Thực hành tốt nhất

  • Tìm nạp và truyền dữ liệu từ cha mẹ sang con nên được sử dụng một cách tiết kiệm.
  • Sử dụng các thư viện như dataloader để hủy các yêu cầu xuôi dòng.
  • Hãy nhận biết bất kỳ áp lực nào mà bạn gây ra cho các nguồn dữ liệu của bạn.
  • Don lồng đột biến trong bối cảnh bối cảnh. Đảm bảo nhất quán, ít mã lỗi.
  • Viết các bộ phân giải có thể đọc, duy trì, kiểm tra được. Không quá thông minh.
  • Làm cho độ phân giải của bạn càng mỏng càng tốt. Trích xuất logic tìm nạp dữ liệu cho các hàm async có thể sử dụng lại.

Giữ nguyên!

Suy nghĩ? Chúng tôi rất thích nghe nhóm của bạn LỚP thực hành và học hỏi tốt nhất với các trình giải quyết xây dựng. Đây là một chủ đề thường được thảo luận nhưng rất quan trọng để xây dựng các API GraphQL tồn tại lâu dài.

Trong các bài đăng sắp tới, chúng tôi sẽ chia sẻ suy nghĩ của chúng tôi về: thiết kế lược đồ, xử lý lỗi, khả năng hiển thị sản xuất, tối ưu hóa tích hợp phía máy khách và công cụ cho các nhóm.

Chúng tôi tuyển dụng! Nếu bạn muốn làm việc trên cơ sở hạ tầng mặt trước, GraphQL hoặc React tại PayPal, DM me trên Twitter tại @mark_stuart!