graphql tools_声明式GraphQL:编写更少的代码,并使用graphql-tools完成更多工作
graphql tools
I’ve been working with GraphQL for a few months now, but only recently began using Apollo’s graphql-tools library. After learning a few idioms, I am able to mock up a functional API quickly. This is largely due to its low-code, declarative approach to type definitions.
我已經(jīng)使用GraphQL已有幾個(gè)月了,但是直到最近才開始使用Apollo的graphql-tools庫(kù)。 學(xué)習(xí)了一些習(xí)慣用法后,我便能夠快速模擬出功能性的API。 這主要是由于其對(duì)類型定義的低代碼聲明性方法。
從他們的例子開始 (Starting with their example)
Apollo has an interactive LaunchPad web site, like the ones covered in my Swagger series. There are several example schemas you can use, and for this article I will use their Post and Authors schema. You can download or fork the code.
阿波羅(Apollo)有一個(gè)交互式LaunchPad網(wǎng)站,就像我的Swagger系列文章中介紹的網(wǎng)站一樣。 您可以使用幾種示例模式,在本文中,我將使用其Post and Authors模式 。 您可以下載或分叉代碼。
I will be rearranging the project folders. For this post I’ll download and store it in Github, so I can branch and modify the code through each step. Along the way, I’ll link the branches to this post.
我將重新排列項(xiàng)目文件夾。 對(duì)于這篇文章,我將其下載并存儲(chǔ)在Github中,因此我可以在每個(gè)步驟中分支和修改代碼。 一路上,我將分支鏈接到該帖子。
基礎(chǔ) (The basics)
declaring schema types
聲明架構(gòu)類型
In the Launchpad, you’ll see a typeDefs template literal:
在啟動(dòng)板中,您將看到typeDefs模板文字:
const typeDefs = `type Author {id: Int!firstName: StringlastName: Stringposts: [Post] # the list of Posts by this author}type Post {id: Int!title: Stringauthor: Authorvotes: Int}# the schema allows the following query:type Query {posts: [Post]author(id: Int!): Author}# this schema allows the following mutation:type Mutation {upvotePost (postId: Int!): Post} `;There are two entities defined, Author and Post. In addition, there are two “magic” types: Query and Mutation. The Query type defines the root accessors. In this case, there’s an accessor to fetch all Posts, and another to fetch a single Author by ID.
定義了兩個(gè)實(shí)體 , Author和Post 。 此外,還有兩種“魔術(shù)” 類型 : Query和Mutation 。 查詢類型定義根accessors 。 在這種情況下,有一個(gè)訪問器來獲取所有Posts ,另一個(gè)訪問器是通過ID獲取單個(gè)Author 。
Note there is no way to directly query for a list of authors or for a single post. It is possible to add such queries later.
請(qǐng)注意,無法直接查詢作者列表或單個(gè)帖子。 以后可以添加此類查詢。
declaring resolvers
宣布解析器
Resolvers provide the necessary logic to support the schema. They are written as a JavaScript object with keys that match the types defined in the schema. The resolver shown below operates against static data, which I’ll cover in a moment.
解析程序提供了支持架構(gòu)的必要邏輯。 它們被編寫為具有與模式中定義的類型相匹配的鍵JavaScript對(duì)象。 下面顯示的resolver針對(duì)靜態(tài)數(shù)據(jù)進(jìn)行操作,我將在稍后介紹。
const resolvers = {Query: {posts: () => posts,author: (_, { id }) => find(authors, { id: id }),},Mutation: {upvotePost: (_, { postId }) => {const post = find(posts, { id: postId });if (!post) {throw new Error(`Couldn't find post with id ${postId}`);}post.votes += 1;return post;},},Author: {posts: (author) => filter(posts, { authorId: author.id }),},Post: {author: (post) => find(authors, { id: post.authorId }),}, };To link schema and resolver together, we’ll create an executable schema instance:
要將schema和resolver鏈接在一起,我們將創(chuàng)建一個(gè)可執(zhí)行架構(gòu)實(shí)例:
export const schema = makeExecutableSchema({typeDefs,resolvers, });the data source
數(shù)據(jù)源
For this simple example, the data comes from two arrays of objects defined as constants: authors and posts:
對(duì)于此簡(jiǎn)單示例,數(shù)據(jù)來自定義為常量的兩個(gè)對(duì)象數(shù)組: authors 和posts :
const authors = [{ id: 1, firstName: 'Tom', lastName: 'Coleman' },{ id: 2, firstName: 'Sashko', lastName: 'Stubailo' },{ id: 3, firstName: 'Mikhail', lastName: 'Novikov' }, ];const posts = [{ id: 1, authorId: 1, title: 'Introduction to GraphQL', votes: 2 },{ id: 2, authorId: 2, title: 'Welcome to Meteor', votes: 3 },{ id: 3, authorId: 2, title: 'Advanced GraphQL', votes: 1 },{ id: 4, authorId: 3, title: 'Launchpad is Cool', votes: 7 }, ];the server
服務(wù)器
You can serve up the executable schema through graphql_express, apollo_graphql_express, or graphql-server-express. We see that in this example.
您可以通過graphql_express , apollo_graphql_express或graphql-server-express提供可執(zhí)行模式。 我們?cè)谶@個(gè)例子中看到了。
The important bits are:
重要的位是:
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express'; import { schema, rootValue, context } from './schema';const PORT = 3000; const server = express();server.use('/graphql', bodyParser.json(), graphqlExpress(request => ({schema,rootValue,context: context(request.headers, process.env), })));server.use('/graphiql', graphiqlExpress({endpointURL: '/graphql', }));server.listen(PORT, () => {console.log(`GraphQL Server is now running on http://localhost:${PORT}/graphql`);console.log(`View GraphiQL at http://localhost:${PORT}/graphiql`); });Note that there are two pieces of GraphQL middleware in use:
請(qǐng)注意,有兩個(gè)正在使用的GraphQL中間件:
graphqlExpress
graphqlExpress
the GraphQL server that handles queries and responses
處理查詢和響應(yīng)的GraphQL服務(wù)器
graphiqlExpress
graphiqlExpress
the interactive GraphQL web service that allows interactive queries through an HTML UI
交互式GraphQL Web服務(wù),該服務(wù)允許通過HTML UI進(jìn)行交互式查詢
改組 (Reorganizing)
For large apps, we suggest splitting your GraphQL server code into 4 components: Schema, Resolvers, Models, and Connectors, which each handle a specific part of the work. (http://dev.apollodata.com/tools/graphql-tools/)
對(duì)于大型應(yīng)用程序,我們建議將GraphQL服務(wù)器代碼分成4個(gè)組件:架構(gòu),解析器,模型和連接器,它們分別處理工作的特定部分。 ( http://dev.apollodata.com/tools/graphql-tools/ )
Putting each type of component in its own file makes sense. I’ll go one better and put each set of components in a its own “domain” folder.
將每種類型的組件放在其自己的文件中是有意義的。 我會(huì)做得更好,并將每組組件放在一個(gè)自己的“域”文件夾中。
為什么要域名? (Why domains?)
Domains are a convenient way to split up a large system into areas of operation. Within each domain there may be subdomains. In general, subdomains have a bounded context. Within a bounded context the entity names, properties, and processes have precise meaning.
域是將大型系統(tǒng)劃分為多個(gè)操作區(qū)域的便捷方法。 在每個(gè)域中可能有子域。 通常,子域具有有限的上下文。 在有限的上下文中,實(shí)體名稱,屬性和過程具有精確的含義。
I find bounded contexts to be helpful during analysis, especially when talking to domain experts.
我發(fā)現(xiàn)有限的上下文在分析過程中會(huì)有所幫助,特別是在與領(lǐng)域?qū)<医徽剷r(shí)。
The fly in the ointment is that GraphQL types occupy a single namespace, so naming conflicts can exist. More on that later.
美中不足的是,GraphQL類型僅占用一個(gè)名稱空間,因此可能存在命名沖突。 以后再說。
I’ll call this domain authorposts, and put the related components in the authorposts folder. Within that, I’ll create a file each for datasource, resolvers, and schema. Let’s also toss in an index.js file to simplify importing. The original schema and server files will remain in the root folder, but the schema.js code will be skeletal. The find and filter methods imported from lodash will be removed in favor of synonymous native ES6 methods. The resulting source is here.
我將這個(gè)域稱為authorposts ,并將相關(guān)組件放入authorposts folder authorposts folder authorposts folder 。 在其中,我將分別為datasource , resolvers和schema創(chuàng)建一個(gè)文件。 讓我們也將index.js文件折騰以簡(jiǎn)化導(dǎo)入。 原始模式和服務(wù)器文件將保留在根文件夾中,但是schema.js代碼將是骨架的。 find 從lodash導(dǎo)入的filter方法將被刪除,以支持同義的本機(jī)ES6方法。 結(jié)果來源在這里 。
The main schema file has become simpler. It provides skeletal structure for further extension by schemas in our domains.
主模式文件變得更加簡(jiǎn)單。 它為我們的領(lǐng)域中的架構(gòu)提供了進(jìn)一步擴(kuò)展的骨架結(jié)構(gòu)。
A domain schema is imported on lines 7–8, and the base schema on lines 11–23. You’ll note there is a domain property. This is arbitrary but GraphQL, or graphql-tools, insists that one property be defined.
domain模式在第7–8行導(dǎo)入, base模式在第11–23行導(dǎo)入。 您會(huì)注意到有一個(gè)域屬性。 這是任意的,但是GraphQL或graphql-tools堅(jiān)持要定義一個(gè)屬性。
The complete schema is constructed on line 26, and an executableSchema instance is created given the schema and resolvers defined so far on lines 28–33. This is what is imported by the server.js code, which is largely unchanged from the original.
完整的架構(gòu)在第26行上構(gòu)建,并根據(jù)第28–33行到目前為止定義的schema和resolvers創(chuàng)建了一個(gè)executableSchema實(shí)例。 這就是server.js代碼導(dǎo)入的內(nèi)容,與原始代碼基本沒有變化。
There is a trick to splitting up a schema this way. Let’s take a look:
有這樣一種技巧可以拆分模式。 讓我們來看看:
The first listing, authorpostResolvers.js, is pretty much a cut’n’paste job from the original schema.js source from Apollo’s example. Yet in the authorpostSchema.js code, we extend the Query and Mutator definitions that are declared in the the base schema. If you don’t use the extend keyword, the executable schema builder will complain about two Query definitions.
第一個(gè)清單authorpostResolvers.js是Apollo示例中原始schema.js源代碼中的一個(gè)“剪切”粘貼工作。 但是,在authorpostSchema.js代碼中,我們擴(kuò)展了在基礎(chǔ)架構(gòu)中聲明的Query和Mutator定義。 如果不使用extend關(guān)鍵字,則可執(zhí)行模式構(gòu)建器將抱怨兩個(gè)查詢定義。
繼續(xù)… (Continuing…)
This is a good start for organizing several schemas, one for each domain of interest (so long as you're mindful of the global namespace for types), but a complete schema, even for a single domain, can get huge. Fortunately, you can break down each schema even further, right down to the entity level, if necessary.
這是組織多個(gè)模式的一個(gè)良好的開始,一個(gè)模式用于每個(gè)感興趣的域(只要您注意類型的全局名稱空間),但是即使是單個(gè)域,一個(gè)完整的模式也會(huì)變得龐大。 幸運(yùn)的是,您可以根據(jù)需要甚至進(jìn)一步細(xì)分每個(gè)架構(gòu),直至實(shí)體級(jí)別 。
Here’s a modified directory structure, and listings of the new contents:
這是修改后的目錄結(jié)構(gòu),并列出了新內(nèi)容:
We can achieve granularity by defining two component files, then importing them into a domain schema.
我們可以通過定義兩個(gè)組件文件,然后將它們導(dǎo)入域模式來實(shí)現(xiàn)粒度。
You don’t have to do one component per file. But you do want to be sure that the schema exports those components along with the schema itself as shown on line 20 of schema.js. Otherwise you’ll likely wind up missing a dependency further down the inclusion chain.
您不必為每個(gè)文件做一個(gè)組件。 但是您確實(shí)要確保該模式將這些組件與模式本身一起導(dǎo)出,如schema.js的第20行所示。 否則,您很可能最終會(huì)在包含鏈的下方錯(cuò)過一個(gè)依賴項(xiàng)。
多個(gè)架構(gòu)和解析器 (Multiple schemas and resolvers)
Adding a new schema for a new domain is simple. Create a new domain folder and add dataSource, resolvers, schema, and index.js files. You can also add an optional component folder with component type definitions.
為新域添加新架構(gòu)很簡(jiǎn)單。 創(chuàng)建一個(gè)新的域文件夾,并添加dataSource,解析器,架構(gòu)和index.js文件。 您還可以添加帶有組件類型定義的可選組件文件夾。
Finally, the root schema.js file must combine the schemas and resolvers from both domains:
最后,根schema.js文件必須結(jié)合兩個(gè)域中的模式和解析器:
//... import {schema as myLittleTypoSchema,resolvers as myLittleTypeResolvers } from './myLittleDomain';import {merge } from 'lodash'; //... const schema = [...baseSchema, ...authorpostsSchema, ...myLittleTypoSchema]const options = {typeDefs: schema,resolvers: merge(authorpostsResolvers, myLittleTypeResolvers) }Note that I had to include lodash merge here because of the need for a deep merge of the two resolvers imports.
請(qǐng)注意,由于必須深度合并兩個(gè)解析器 ,因此我必須在此處包括lodash 合并 進(jìn)口。
處理命名空間沖突 (Dealing with Namespace Collisions)
If you are on a large project, you will encounter type name collisions. You might think that Account in one domain would mean the same as Account in another. Yet even if they do mean more or less similar things, chances are the properties and relationships will be different. So technically they are not the same type.
如果您在大型項(xiàng)目中,則會(huì)遇到類型名稱沖突。 您可能會(huì)認(rèn)為一個(gè)域中的帳戶與另一個(gè)域中的帳戶含義相同。 然而,即使它們確實(shí)或多或少地意味著相似的事物,屬性和關(guān)系也有可能會(huì)不同。 因此,從技術(shù)上講,它們不是同一類型。
At the time of this writing, GraphQL uses a single namespace for types.
在撰寫本文時(shí),GraphQL對(duì)類型使用單個(gè)名稱空間。
How to work around this? Facebook apparently uses a naming convention for their 10,000 types. As awkward as that seems, it works for them.
如何解決這個(gè)問題? Facebook顯然為其10,000種類型使用命名約定 。 看起來很尷尬,但對(duì)他們有用。
The Apollo graphql-tools stack appears to catch type name duplications. So you should be good there.
Apollo graphql-tools堆棧似乎捕獲類型名稱重復(fù)項(xiàng)。 所以你在那里應(yīng)該很好。
There is an ongoing discussion on whether to include namespaces in GraphQL. It isn’t a simple decision . I remember the complexities caused by the introduction of XML Namespaces 10 years ago.
關(guān)于是否在GraphQL中包括名稱空間的討論正在進(jìn)行中。 這不是一個(gè)簡(jiǎn)單的決定。 我記得10年前引入XML命名空間引起的復(fù)雜性。
然后去哪兒? (Where to go from here?)
This post only scratches the surface of how one might organize a large set of GraphQL schemas. The next post will be about mocking GraphQL resolvers, and how it’s possible to mix both real and mocked values in query responses.
這篇文章只是對(duì)如何組織一大套GraphQL模式的表述。 下一篇文章將關(guān)于模擬GraphQL解析器,以及如何在查詢響應(yīng)中混合使用真實(shí)值和模擬值。
翻譯自: https://www.freecodecamp.org/news/declarative-graphql-with-graphql-tools-cd1645f94fc/
graphql tools
總結(jié)
以上是生活随笔為你收集整理的graphql tools_声明式GraphQL:编写更少的代码,并使用graphql-tools完成更多工作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做梦梦到下排牙齿几乎都脱落了
- 下一篇: 惯用过程模型_惯用的Ruby:编写漂亮的