小程序 graphql
by Sacha Greif
由Sacha Greif
GraphQL應用程序中的五個常見問題(以及如何解決) (Five Common Problems in GraphQL Apps (And How to Fix Them))
了解如何釋放GraphQL的強大功能而不會遭受缺點 (Learn to unlock the power of GraphQL without suffering its drawbacks)
GraphQL is all the rage these days, and for good reason: it’s an elegant approach that solves many of the problems associated with traditional REST APIs.
GraphQL如今風靡一時,這是有充分理由的:這是一種優雅的方法,可以解決與傳統REST API相關的許多問題。
Yet I’d be lying if I told you that GraphQL doesn’t come with its own set of issues. And if you’re not careful, these issues might not only lead to a bloated codebase, but even to a dramatically slowed-down app.
但是,如果我告訴您GraphQL沒有它自己的問題,那我會撒謊。 而且,如果您不小心,這些問題可能不僅會導致代碼庫過大,甚至會導致應用程序速度大大降低。
I’m talking about problems such as:
我說的是諸如以下的問題:
Schema duplication
模式復制
Server/client data mismatch
服務器/客戶端數據不匹配
Superfluous database calls
多余的數據庫調用
Poor performance
表現不佳
Boilerplate overdose
樣板過量
I’m willing to bet your app suffers from at least one of them. Good news is, none of them are incurable!
我敢打賭,您的應用至少會遭受其中之一的困擾。 好消息是,沒有一個是無法治愈的!
For each issue, I’ll describe the problem, and then explain how I’m addressing it inside Vulcan, a React/GraphQL open-source framework I’ve been working on over the past year (you should check it out!). But hopefully, you’ll be able to apply the same strategies to your own codebase whether you use Vulcan or not.
對于每個問題,我將描述問題,然后解釋如何在過去一年來一直在致力于的React / GraphQL開源框架Vulcan中解決它(您應該檢查一下!)。 但是希望無論您是否使用Vulcan,您都可以將相同的策略應用于自己的代碼庫。
問題:模式復制 (Problem: Schema Duplication)
One of the first things you realize when coding a GraphQL back-end from scratch is that it involves a lot of similar-but-not-quite-identical code, especially when it comes to schemas.
從頭開始編寫GraphQL后端時,您會意識到的第一件事是,它涉及許多相似但不完全相同的代碼,尤其是涉及架構時。
Namely, you need one schema for your database, and another one for your GraphQL endpoint. Not only is it frustrating to have to write more or less the same thing twice, but you now have two independent sources of truths that you need to constantly keep in sync.
即,您需要一個數據庫架構,另一個需要GraphQL端點。 不得不多寫或少寫兩次相同的東西不僅令人沮喪,而且您現在擁有兩個獨立的真理來源,需要不斷保持同步。
解決方案:GraphQL模式生成 (Solution: GraphQL Schema Generation)
A number of solutions to this problem have emerged in the GraphQL ecosystem. For example, PostGraphile generates a GraphQL schema from your PostgreSQL database, and Prisma will also help you generate types for your queries and mutations.
GraphQL生態系統中出現了許多解決此問題的方法。 例如, PostGraphile從PostgreSQL數據庫生成GraphQL模式, Prisma也將幫助您生成查詢和變異的類型。
I also remember hearing Laney Zamore & Adam Kramer from the GraphQL team describe how they directly generated their GraphQL schema from their PHP type definitions.
我還記得GraphQL團隊的Laney Zamore和Adam Kramer講述了他們如何直接從PHP類型定義中生成GraphQL模式。
For Vulcan, I independently stumbled on a very similar solution. I was using SimpleSchema to describe my schemas as JavaScript objects, and I started simply by converting JavaScript’s String
type into a GraphQL String
, Number
into Int
or Float
, and so on.
對于Vulcan,我獨立地偶然發現了一個非常相似的解決方案。 我當時使用SimpleSchema將模式描述為JavaScript對象,然后開始將JavaScript的String
類型轉換為GraphQL String
,將Number
轉換為Int
或Float
,等等。
So this JavaScript field:
所以這個JavaScript字段:
title: { type: String}
Would become this GraphQL field:
將成為此GraphQL字段:
title: String
But of course, a GraphQL schema can also have custom types: User
, Comment
, Event
, and so on.
但是,當然,GraphQL架構也可以具有自定義類型: User
, Comment
, Event
等等。
I didn’t want to add too much magic to the schema generation step, so I came up with field resolvers, a simple way to let you specify these custom types. So that this JavaScript field:
我不想在架構生成步驟中添加太多的魔力,所以我想出了字段解析器 ,這是一種讓您指定這些自定義類型的簡單方法。 這樣這個JavaScript字段:
userId{ type: String, resolveAs: { fieldName: 'user', type: 'User', resolver: document => { return Users.findOne(document.userId) } }}
Becomes:
成為:
user: User
As you can see, we’re defining the actual resolver function on the field as well, since it’s also directly related to the GraphQL field.
如您所見,由于它也與GraphQL字段直接相關,因此我們也在該字段上定義了實際的解析程序功能。
So whether you use something like PostGraphile or write your own schema generation code, I encourage you to avoid schema duplication in your own app.
因此,無論您使用PostGraphile之類的東西還是編寫自己的模式生成代碼,我都建議您避免在自己的應用程序中重復模式。
Or of course, you can also use a hosted service such as Graphcool to manage your schema using their dashboard and bypass that issue entirely.
或者,當然,您也可以使用諸如Graphcool之類的托管服務通過其儀表板管理您的架構,并完全繞開該問題。
問題:服務器/客戶端數據不匹配 (Problem: Server/Client Data Mismatch)
As we’ve just seen, your database and GraphQL API will have different schemas, which translate into different document shapes.
如我們所見,您的數據庫和GraphQL API將具有不同的架構,這些架構將轉換為不同的文檔形狀。
So while a post
fresh out of the database will have a userId
property, the same post
as fetched through your API will instead have a user
property.
因此,雖然從數據庫中重新post
將具有userId
屬性,但與通過您的API提取的post
相同,將具有一個user
屬性。
This means that getting a post author’s name on the client will look like:
這意味著在客戶端上獲得帖子作者的姓名將類似于:
const getPostNameClient = post => { return post.user.name}
But on the server, it’ll be a different story altogether:
但是在服務器上,這將是完全不同的故事:
const getPostNameServer = post => { const postAuthor = Users.findOne(post.userId) return postAuthor.name}
This can be a problem anytime you’re trying to share code between client and server to simplify your codebase. And even beyond that, it means you’re missing out on GraphQL’s great approach to data querying on the server.
每當您嘗試在客戶端和服務器之間共享代碼以簡化代碼庫時,這都是一個問題。 甚至除此之外,這意味著您會錯過GraphQL在服務器上進行數據查詢的出色方法。
I recently felt that pain when trying to build a system to generate weekly newsletters: each newsletter was composed of multiple posts and comments, along with info about their authors; in other words, a perfect use case for GraphQL. But this newsletter generation was happening on the server, meaning I didn’t have a way to query my GraphQL endpoint…
最近,當我嘗試構建一個每周新聞通訊系統時,我感到非常痛苦:每個新聞通訊由多個帖子和評論以及有關作者的信息組成; 換句話說,是GraphQL的完美用例。 但是,此新聞通訊正在服務器上發生,這意味著我無法查詢GraphQL端點…
解決方案:服務器到服務器的GraphQL查詢 (Solution: Server-to-Server GraphQL Queries)
Or did I? It turns out that you can run server-to-server GraphQL queries just fine! Just pass your GraphQL executable schema to the graphql
function, along with your GraphQL query:
還是我? 事實證明,您可以運行服務器對服務器的GraphQL查詢! 只需將您的GraphQL可執行模式與GraphQL查詢一起傳遞給graphql
函數 :
const result = await graphql(executableSchema, query, {}, context, variables);
In Vulcan, I generalized this pattern into a runQuery
helper, and I also added queryOne
functions to each collection. These act just like MongoDB’s findOne
except they return the document as fetched through the GraphQL API:
在Vulcan中, 我將此模式概括為runQuery
helper ,并且還向每個集合添加了queryOne
函數。 這些行為與MongoDB的findOne
除了它們返回通過GraphQL API獲取的文檔:
const user = await Users.queryOne(userId, { fragmentText: ` fragment UserFragment on User { _id username createdAt posts{ _id title } } `});
Server-to-server GraphQL queries have helped me drastically simplify my code. It let me refactor my newsletter generation call from a mess of successive database calls and loops to a single GraphQL query:
服務器到服務器的GraphQL查詢幫助我極大地簡化了代碼。 它使我可以從一堆連續的數據庫調用和循環中重構我的新聞通訊生成調用,以循環到單個GraphQL查詢:
query NewsletterQuery($terms: JSON){ SiteData{ title } PostsList(terms: $terms){ _id title url pageUrl linkUrl domain htmlBody thumbnailUrl commentsCount postedAtFormatted user{ pageUrl displayName } comments(limit: 3){ user{ displayName avatarUrl pageUrl } htmlBody postedAt } }}
The takeaway here: don’t see GraphQL as just a pure client-server protocol. GraphQL can be used to query data in any situation, including client-to-client with Apollo Link State or even during a static build process with Gatsby.
這里的要點:不要將GraphQL視為純粹的客戶端-服務器協議。 GraphQL可用于在任何情況下查詢數據,包括具有Apollo鏈接狀態的客戶端到客戶端,甚至在使用Gatsby的靜態構建過程中。
問題:多余的數據庫調用 (Problem: Superfluous Database Calls)
Imagine a list of posts, each of which has a user attached to it. You now want to display 10 of these posts, along with the name of their author.
想象一下一個帖子列表,每個帖子都具有一個用戶。 現在,您要顯示其中的10條帖子,以及其作者的姓名。
With a typical implementation, this would mean two database calls. One to get the 10 posts, and one to get the 10 users corresponding to these posts.
對于典型的實現,這將意味著兩個數據庫調用。 一個獲得10個帖子,一個獲得10個與這些帖子對應的用戶。
But what about GraphQL? Assuming our posts have a user
field with its own resolver, we still have one initial database call to get the list of posts. But we now have an extra call to fetch each user per resolver, for a total of 11 database calls!
但是GraphQL呢? 假設我們的帖子有一個帶有自己的解析程序的user
字段,我們仍然有一個初始數據庫調用來獲取帖子列表。 但是我們現在有一個額外的調用來為每個解析器獲取每個用戶,總共有11個數據庫調用!
Now imagine that each post also has 5 comments, each of which has an author. Our number of calls has now ballooned to:
現在想象每個帖子也有5條評論,每個評論都有一位作者。 我們的電話數量現已激增至:
- 1 for the list of posts 帖子列表1
- 10 for the post authors 帖子作者10個
- 10 for each sub-list of 5 comments 每5則評論的子清單10個
- 50 for the comment authors 評論作者50個
For a grand total of 71 database calls to display a single view!
共顯示71個數據庫調用,以顯示單個視圖 !
Nobody wants to have to explain to their boss why the homepage is taking 25 seconds to load. Thankfully, there’s a solution: Dataloader.
沒人愿意向老板解釋為什么首頁要花25秒鐘來加載。 幸運的是,有一個解決方案: Dataloader 。
解決方案:數據加載器 (Solution: Dataloader)
Dataloader will let you batch and cache database calls.
Dataloader使您可以批處理和緩存數據庫調用。
Batching means that if Dataloader figures out that you’re hitting the same database table multiple times, it’ll batch all calls together. In our example, the 10 post authors’ and 50 comment authors’ calls would all be batched into a single call.
批處理意味著,如果Dataloader發現您多次訪問同一數據庫表,則它將所有調用一起批處理。 在我們的示例中,10個帖子作者的呼叫和50個評論作者的呼叫將全部合并為一個呼叫。
Caching means that if Dataloader detects that two posts (or a post and a comment) have the same author, it will reuse the user object it already has in memory instead of making a new database call.
緩存意味著,如果Dataloader檢測到兩個帖子(或一個帖子和一個評論)具有相同的作者,它將重用它已經在內存中擁有的用戶對象,而不是進行新的數據庫調用。
In practice you don’t always achieve perfect caching and batching, but Dataloader is still a huge help.
在實踐中,您并不總是能夠實現完美的緩存和批處理,但是Dataloader仍然是一個巨大的幫助。
And Vulcan makes using Dataloader extra easy. Out of the box, every Vulcan model includes Dataloader functions as alternatives to the “normal” MongoDB query functions.
Vulcan使得使用Dataloader變得異常容易。 開箱即用, 每個Vulcan模型都包含Dataloader函數,以替代“常規” MongoDB查詢函數。
So in addition to collection.findOne
and collection.find
, you can use collection.loader.load
and collection.loader.loadMany
.
因此,除了collection.findOne
和collection.find
,您還可以使用collection.loader.load
和collection.loader.loadMany
。
The one limitation is that Dataloader only works when querying using document IDs. So you can use it to query for a document whose ID is already known, but you’ll still need to hit your database if you want to ask for, say, the most recently created post.
一個限制是Dataloader僅在使用文檔ID查詢時才起作用。 因此,您可以使用它來查詢ID已知的文檔,但是如果您要查詢(例如)最近創建的帖子,仍然需要打數據庫。
問題:性能不佳 (Problem: Poor Performance)
Even with Dataloader enabled, complex views can still trigger multiple database calls, which in turn can make for slow loading times.
即使啟用了Dataloader,復雜的視圖仍然可以觸發多個數據庫調用,這又會導致加載時間變慢。
This can be frustrating: on one hand you want to take full advantage of GraphQL’s graph traversal features (“show me the authors of the comments of the author of the post of…” etc.). But on the other hand, you don’t want your app to become slow and unresponsive.
這可能令人沮喪:一方面,您想充分利用GraphQL的圖遍歷功能(例如,“向我顯示...帖子作者的評論的作者”等)。 但另一方面,您不希望您的應用變慢且無響應。
解決方案:查詢緩存 (Solution: Query Caching)
There is a solution though, which is to cache the entire GraphQL query response. Unlike Dataloader, whose scope is limited to the current request (meaning it will only cache documents within the same query), I’m talking here about caching the whole query for a period of time.
但是,有一個解決方案,就是緩存整個 GraphQL查詢響應。 不同的DataLoader,其范圍僅限于當前請求(這意味著它只會緩存在同一查詢中的文件),我在這里談論緩存整個查詢一段時間。
Apollo Engine is a great way to do just that. It’s a hosted service that provides analytics about your GraphQL queries, but it also has a very useful caching feature.
Apollo Engine是做到這一點的好方法。 它是一項托管服務,可提供有關GraphQL查詢的分析,但它也具有非常有用的緩存功能 。
Vulcan comes with a built-in Engine integration, you just need to add your API key to your settings file. You can then add the enableCache: true
argument to your GraphQL queries to cache them using Engine.
Vulcan帶有內置的Engine集成,您只需要將API密鑰添加到設置文件中即可。 然后,您可以向您的GraphQL查詢添加enableCache: true
參數,以使用Engine對其進行緩存。
Or, using Vulcan’s built-in data-loading higher-order components:
或者,使用Vulcan的內置數據加載高階組件 :
withList({ collection: Posts, enableCache: true})(PostsList)
The beauty of this approach is that you can easily control which queries are cached and which aren’t, even for the same resolver. For example, you might want to cache the list of recent posts featured on your frequently-accessed homepage, but not the full list of posts available on your archives page.
這種方法的優點在于,即使對于同一個解析器,您也可以輕松控制哪些查詢被緩存,哪些不緩存。 例如,您可能想緩存您經常訪問的主頁上的最新帖子列表,而不是存檔頁面上可用帖子的完整列表。
A final note: caching might not always be possible. For example, it’s not advisable for data that changes frequently, or for data that depends on the currently logged-in user.
最后一點:并非總是可能進行緩存。 例如,對于頻繁更改的數據或依賴于當前登錄用戶的數據,建議不要這樣做。
問題:樣板過量 (Problem: Boilerplate Overdose)
This is by no means an issue exclusive to GraphQL apps, but it’s true that they generally require you to write a lot of similar boilerplate code.
這絕不是GraphQL應用程序獨有的問題,但確實,它們通常需要您編寫很多類似的樣板代碼。
Typically, adding a new model (e.g. Comments
) to your app will involve the following steps:
通常,向您的應用添加新模型(例如Comments
)將涉及以下步驟:
- Writing a resolver to get a list of comments. 編寫解析器以獲取評論列表。
- Writing a higher-order component (a.k.a. container) to load that list of comments. 編寫一個高階組件(也稱為容器)以加載該評論列表。
- Optionally, writing a resolver to get a single comment by ID or slug along with the corresponding higher-order component. (可選)編寫解析器以獲取ID或Slug以及相應的高階組件的單個注釋。
- Writing mutations for inserting a new comment, editing a comment, and deleting a comment. 編寫用于插入新注釋,編輯注釋和刪除注釋的變體。
- Adding the corresponding forms and form-handling code. 添加相應的表單和表單處理代碼。
That’s a whole lot of CRUD!
這是很多CRUD!
解決方案:通用解析器,突變和高階組件 (Solution: Generic Resolvers, Mutations, and Higher-Order Components)
Vulcan’s approach is to give you smart, easy-to-use generic options for each of these. You’ll get:
Vulcan的方法是為每個選項提供智能,易于使用的通用選項。 你會得到:
Default resolvers for displaying lists of documents and single documents.
用于顯示文檔列表和單個文檔的默認解析器 。
Pre-made higher-order components for loading a list of documents or a single document.
預制的高級組件,用于加載文檔列表或單個文檔。
Default mutation resolvers for inserting, editing, and removing documents.
用于插入,編輯和刪除文檔的默認突變解析器 。
Generated forms based on your schema that work with all the above.
根據您的架構生成的表單可與以上所有功能一起使用。
These are all written in a generic enough way that they’ll work with any new model out of the box.
這些都以足夠通用的方式編寫,可以與任何新模型一起使用。
To be sure, this “one-size-fits-all” approach is not without its downsides. For example, because queries are generated dynamically by the generic higher-order components, it’s a bit harder to use static queries.
可以肯定的是,這種“千篇一律”的方法并非沒有缺點。 例如,由于查詢是由通用的高階組件動態生成的,因此使用靜態查詢會有點困難。
But this strategy is still a great way to get started, at least until you have time to refactor each part of your app to a more tailor-made solution.
但是,至少在您有時間將應用程序的每個部分重構為更量身定制的解決方案之前,這種策略仍然是入門的好方法。
GraphQL is still relatively new, which means that while everybody is busy extolling its virtues, it’s easy to overlook the very real challenges involved with building GraphQL apps.
GraphQL還是一個??相對較新的概念,這意味著盡管每個人都在忙于贊美自己的優點,但很容易忽略構建GraphQL應用程序所涉及的真正挑戰。
Thankfully these challenges all have solutions, and the more we discuss them (the Vulcan Slack is a great place for that by the way!), the better these solutions will become!
值得慶幸的是,這些挑戰都有解決方案,而我們討論的內容越多(順便說一下, Vulcan Slack是一個絕佳的選擇!),這些解決方案將變得更好!
翻譯自: https://www.freecodecamp.org/news/five-common-problems-in-graphql-apps-and-how-to-fix-them-ac74d37a293c/
小程序 graphql