我希望支持JavaScript GraphQL实现的API

The GraphQL schema language is great! It is certainly the best way to communicate anything about a GraphQL service. No wonder all documentations now use it!

GraphQL 模式语言很棒! 当然,这是传达与GraphQL服务有关的任何东西的最佳方法。 难怪所有文档现在都在使用它!

模式语言 (The Schema Language)

Imagine that you’re building a blog app (with GraphQL) that has "Articles" and "Comments" . You can start thinking about its API schema by basing it on what you plan for its UI. For example, the main page will probably have a list of articles and an item on that list might display a title, subtitle, author’s name, publishing date, length (in reading minutes), and a featured image. A simplified version of Medium itself if you may:

想象一下,您正在构建一个具有“ Articles”和“ Comments”的博客应用程序(使用GraphQL)。 您可以根据其UI计划来开始考虑其API模式。 例如,主页可能会包含文章列表,并且该列表上的项目可能会显示标题,副标题,作者姓名,出版日期,时长(以分钟为单位)和特色图片。 如果可以的话,Medium本身的简化版本:

We can use the schema-language to plan what you need so far for that main page. A basic schema might look like:

我们可以使用模式语言来计划到目前为止该主页所需的内容。 基本模式可能如下所示:

type Query {
  articleList: [Article!]!
}
type Article {
  id: ID!
  title: String!
  subTitle: String
  featuredImageUrl: String
  readingMinutes: Int!
  publishedAt: String!
  author: Author!
}
type Author {
  name: String!
}

When a user navigates to an article, they’ll see the details of that article. We’ll need the API to support a way to retrieve an Article object by its id. Let’s say an article can also have rich UI elements like headers and code snippets. We would need to support a rich-text formatting language like Markdown. We can make the API return an article’s content in either Markdown or HTML through a field argument (format: HTML). Let’s also plan to display a "likes" counter in that view.

当用户导航到文章时,他们将看到该文章的详细信息。 我们将需要API支持一种通过ID检索Article对象的方法。 假设文章还可以包含丰富的UI元素,例如标题和代码段。 我们需要支持诸如Markdown之类的富文本格式语言。 我们可以通过字段参数( format :HTML)使API以Markdown或HTML format返回文章的内容。 我们还计划在该视图中显示一个“点赞”计数器。

Put all these ideas on paper! The schema language is the most concise structured way to describe them:

将所有这些想法写在纸上! 模式语言是描述它们的最简洁的结构化方式:

type Query {
  # ...
  article(id: String!): Article!
}
enum ContentFormat {
  HTML
  MARKDOWN
}
type Article {
  # ...
  content(format: ContentFormat): String!
  likes: Int!
}

The one article’s UI view will also display the list of comments available on an article. Let’s keep the comment UI view simple and plan it to have a text content and an author name fields:

一篇文章的UI视图还将显示文章上可用的评论列表。 让我们保持注释UI视图简单,并计划它具有文本内容和作者姓名字段:

type Article {
  # ...
  commentList: [Comment!]!
}
type Comment {
  id: ID!
  content: String!
  author: Author!
}

Let’s focus on just these features. This is a good starting point that’s non-trivial. To offer these capabilities we’ll need to implement custom resolving logic for computed fields like content(format: HTML) and readingMinutes. We’ll also need to implement 1–1 and 1-many db relationships.

让我们仅关注这些功能。 这是一个重要的起点。 为了提供这些功能,我们需要为诸如content(format: HTML)readingMinutes类的计算字段实现自定义解析逻辑。 我们还需要实现1-1和1-db关系。

Did you notice how I came up with the whole schema description so far just by thinking in terms of the UI. How cool is that? You can give this simple schema language text to the front-end developers on your team and they can start building the front-end app right away! They don’t need to wait for your server implementation. They can even use some of the great tools out there to have a mock GraphQL server that resolves these types with random test data.

您是否注意到到目前为止,仅通过UI方面的考虑,我是如何想到整个架构描述的? 多么酷啊? 您可以将此简单的架构语言文本提供给团队中的前端开发人员,他们可以立即开始构建前端应用程序! 他们不需要等待您的服务器实施。 他们甚至可以使用一些出色的工具来拥有一个模拟GraphQL服务器,该服务器可以使用随机测试数据来解析这些类型。

The schema is often compared to a contract. You always start with a contract.
通常将模式与合同进行比较。 您总是从合同开始。

建立一个GraphQL模式 (Building a GraphQL Schema)

When you’re ready to start implementing your GraphQL service, you have 2 main options (in JavaScript) today:

准备开始实施GraphQL服务时,今天有2个主要选项(在JavaScript中):

  1. You can "build" a non-executable schema using the full schema language text that we have and then attach a set of resolver functions to make that schema executable. You can do that with GraphQL.js itself or with Apollo Server. Both support this method which is commonly known as "schema-first" or "SDL-first". I’ll refer to it here as the "full-schema-string method".

    您可以使用我们拥有的完整模式语言文本“构建”一个不可执行的模式,然后附加一组解析器函数以使该模式可执行。 您可以使用GraphQL.js本身或Apollo Server来实现 。 两者都支持这种方法,通常称为“模式优先”或“ SDL优先”。 我在这里将其称为“ 全模式字符串方法 ”。

  2. You can use JavaScript objects instantiated from the various constructor classes that are available in the GraphQL.js API (like GraphQLSchema, GraphQLObjectType, GraphQLUnionType, and many others). In this approach, you don’t use the schema-language text at all. You just create objects. This method is commonly known as "code-first" or "resolvers-first" but I don’t think these names fairly represent it. I’ll refer to it here as the "object-based method".

    您可以使用从GraphQL.js API中可用的各种构造函数类实例化JavaScript对象(例如GraphQLSchemaGraphQLObjectTypeGraphQLUnionType以及许多其他对象)。 在这种方法中,您根本不需要使用架构语言文本。 您只需创建对象。 这种方法通常称为“代码优先”或“解析程序优先”,但我认为这些名称不能公平地代表它。 我在这里将其称为“ 基于对象的方法 ”。

Both approaches have advantages and disadvantages.

两种方法都有优点和缺点。

The schema language is a great programming-language-agnostic way to describe a GraphQL schema. It’s a human-readable format that’s easy to work with. The frontend people on your team will absolutely love it. It enables them to participate in the design of the API and, more importantly, start using a mocked version of it right away. The schema language text can serve as an early version of the API documentation.

模式语言是描述GraphQL模式的一种很棒的与编程语言无关的方式。 这是一种易于阅读的格式。 您团队中的前端人员绝对会喜欢它。 它使他们能够参与API的设计,更重要的是,立即开始使用其模拟版本。 模式语言文本可以用作API文档的早期版本。

However, completely relying on the full schema language text to create a GraphQL schema has a few drawbacks. You’ll have to put in some effort to make the code modularized and clear and you have to rely on coding patterns and tools to keep the schema-language text consistent with the tree of resolvers (AKA resolvers map). These are solvable problems.

但是,完全依赖完整的模式语言文本来创建GraphQL模式有一些缺点。 您必须付出一些努力才能使代码模块化和清晰,并且您必须依靠编码模式和工具来使架构语言文本与解析器树保持一致(AKA解析器映射)。 这些是可解决的问题。

The biggest problem I see with the full-schema-string method is that you lose some flexibility in your code. You don’t have objects associated with types. You just have strings! And although these strings make your types more readable, in many cases you’ll need the flexibility over the readability.

我看到的全模式字符串方法的最大问题是,您的代码失去了灵活性。 您没有与类型关联的对象。 你只有弦! 并且尽管这些字符串使您的类型更具可读性,但在许多情况下,您需要在可读性上具有灵活性。

The object-based method is flexible and easier to extend and manage. It does not suffer from any of the mentioned problems. You have to be modular with it because your schema is a bunch of objects. You also don’t need to merge modules together because these objects are designed and expected to work as a tree.

基于对象的方法非常灵活,并且易于扩展和管理。 它没有任何上述问题。 您必须对其进行模块化,因为架构是一堆对象。 您也不需要将模块合并在一起,因为这些对象经过设计并且可以像树一样工作。

The only problem I see with the object-based method is that you have to deal with a lot more code around what’s important to manage in your modules (types and resolvers). A lot of developers see that as "noise" and you can’t blame them. We’ll work through an example to see that.

我发现基于对象的方法的唯一问题是,您必须处理更多代码,以围绕模块中重要的内容(类型和解析器)进行处理。 许多开发人员将其视为“噪音”,您不能责怪他们。 我们将通过一个示例来说明这一点。

If you’re creating a small-scope and well-defined GraphQL service, using the full-schema-string method is probably okay. However, in bigger and more agile projects I think the more flexible and more powerful object-based method is the way to go.

如果要创建一个范围较小且定义明确的GraphQL服务,则可以使用全模式字符串方法。 但是,在更大,更敏捷的项目中,我认为更灵活,更强大的基于对象的方法是可行的方法。

You should still leverage the schema-language text even if you’re using the object-based method. At jsComplete, we use the object-based method but every time the schema is built we use the graphql.printSchema function to write the complete schema to a file. We commit and track that file in the Git repository of the project and that proved to be a very helpful practice!

即使您使用的是基于对象的方法,您仍然应该使用架构语言文本。 在jsComplete ,我们使用基于对象的方法,但是每次构建架构时,我们都使用graphql.printSchema函数将完整的架构写入文件。 我们在项目的Git存储库中提交并跟踪该文件,事实证明这是非常有用的做法!

To compare the 2 methods, I’ve implemented an executable schema for the blog example we started with using both of them. I’ve omitted some code for brevity but kept what matters for the comparison.

为了比较这两种方法,我为我们从使用这两种方法开始的博客示例实现了一个可执行模式。 为了简洁起见,我省略了一些代码,但保留了重要的比较信息。

全模式字符串方法 (The full-schema-string method)

We start with the schema-language text which defines 3 main custom types (Article, Comment, and Author). The fields under the main Query type are article and articleList which will directly resolve objects from the database. However, since the GraphQL schema we planned has custom features around an article object and since we have relations that we need to resolve as well we’ll need to have custom resolvers for the 3 main custom GraphQL types.

我们从定义3种主要自定义类型( ArticleCommentAuthor )的模式语言文本开始。 主要Query类型下的字段是articlearticleList ,它们将直接从数据库中解析对象。 但是,由于我们计划的GraphQL模式在文章对象周围具有自定义功能,并且由于我们还需要解析关系,因此我们需要为3种主要的自定义GraphQL类型提供自定义解析器。

Here are a few screenshots for the code I wrote to represent the full-schema-string method. I’ve used Apollo Server here but this is also possible with vanilla GraphQL.js (and a bit more code).

这是我编写的代表全模式字符串方法的代码的一些屏幕截图。 我在这里使用过Apollo Server,但香草GraphQL.js(以及更多代码)也可以使用。

Please note that this is just ONE way of implementing the full-schema-string method for this service. There are countless other ways. I am just presenting the simplest modular way here to help us understand the true advantages and disadvantages.
请注意,这只是实现此服务的全模式字符串方法的一种方法。 还有无数其他方法。 我只是在这里介绍最简单的模块化方法,以帮助我们了解真正的优缺点。

This is nice! We can see the types in the schema in one place. It’s clear where the schema starts. We’re able to modularize the code by type/feature.

太好了! 我们可以在一处看到架构中的类型。 很明显,架构从哪里开始。 我们能够按类型/功能对代码进行模块化。

This again is really great! Resolvers are co-located with the types they implement. There is no noise. This file beautifully contains what matters in a very readable format. I love it!

这真的很棒! 解析器与它们实现的类型位于同一位置。 没有噪音。 这个文件精美易懂地包含了重要内容。 我喜欢它!

The modularity here is only possible with Apollo Server. If we’re to do this with vanilla GraphQL.js we will have to monkey with data objects to make them suitable to be a "resolvers tree". The mixing between the data structures and the resolvers graph is not ideal.
这里的模块化仅适用于Apollo Server。 如果我们要使用普通的GraphQL.js进行此操作,则必须使用数据对象,使其适合作为“解析器树”。 数据结构和解析器图之间的混合并不理想。

So what’s the downside here?

那这里有什么缺点呢?

If you use this method then all your types have to be written in that certain way that relies on the schema language text. You have less flexibility. You can’t use constructors to create some types when you need to. You’re locked down to this string-based approach.

如果使用此方法,则必须以依赖于模式语言文本的某种特定方式编写所有类型。 您的灵活性较差。 需要时,不能使用构造函数来创建某些类型。 您只能使用这种基于字符串的方法。

If you’re okay with that then ignore the rest of this article. Just use this method. It is so much cleaner than the alternative.

如果您对此表示满意,请忽略本文的其余部分。 只需使用此方法。 它比替代品干净得多。

基于对象的方法 (The object-based method)

Let’s now look at the object-based approach. Here’s the starting point of an executable schema built using that method:

现在让我们看一下基于对象的方法。 这是使用该方法构建的可执行模式的起点:

We don’t need a separate resolvers object. Resolvers are part of the schema object itself. That makes them easier to maintain. This code is also easier to programmatically extend and analyze!

我们不需要单独的resolvers对象。 解析器是架构对象本身的一部分。 这使它们更易于维护。 此代码也更容易以编程方式扩展和分析!

It’s also so much more code that’s harder to read and reason about! Wait until you see the rest of the code. I couldn’t take the Article type screenshot on the laptop screen. I had to use a bigger screen.

还有太多难以理解的代码! 等到看到其余的代码。 我无法在笔记本电脑屏幕上拍摄“ Article类型的屏幕截图。 我不得不使用更大的屏幕。

No wonder the full-schema-string method is popular! There is certainly a lot of "noise" to deal with here. Types are not clear at first glance. Custom resolvers are mixed in one big configuration object.

难怪全模式字符串方法很流行! 这里肯定要处理很多“噪音”。 乍一看类型不明确。 自定义解析器混合在一个大的配置对象中。

My favorite part is when you need to create a non-null list of non-null items like [Article!]!. Did you see what I had to write?

我最喜欢的部分是需要创建诸如[Article!]!类的非空项目的非空列表时[Article!]! 。 你看到我写的东西了吗?

new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Article))),

new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Article))),

However, while this is indeed a lot more code that’s harder to understand, it is still a better option than having one big string (or multiple strings combined into one) and one big root resolvers object (or multiple resolvers objects combined into one). It’s better than having all the dependencies of your app managed in one single entry point.

但是,尽管确实有很多代码难以理解,但与将一个大字符串(或将多个字符串组合成一个)和一个大根解析器对象(或将多个分解器对象组合成一个)相比,它仍然是一个更好的选择。 这比在单个入口点管理应用程序的所有依赖关系要好。

There is a lot of power in modularizing your code using objects (that may depend on each other). It’s cleaner that way and it also makes writing tests and validations easier. You get more helpful error messages when you debug problems. Modern editors can provide more helpful hints in general. Most importantly, you have a lot more flexibility to do anything with these objects. The GraphQL.js constructors API itself also uses JavaScript objects. There is so much you can do with them.

使用对象(可能相互依赖)将代码模块化的功能非常强大。 这样更清洁,也使编写测试和验证更加容易。 调试问题时,您会获得更多有用的错误消息。 一般而言,现代编辑人员可以提供更多有用的提示。 最重要的是,您具有更大的灵活性来处理这些对象。 GraphQL.js构造函数API本身也使用JavaScript对象。 他们可以为您做很多事情。

But the noise is real too.

但是噪音也是真实的。

无噪声的基于对象的方法 (The object-based method without the noise)

I am sticking with the object-based method but I sure wish the JavaScript GraphQL implementations had a better API that can give us some of the power of the full-schema-string method.

我坚持使用基于对象的方法,但是我希望JavaScript GraphQL实现具有更好的API,可以为我们提供全模式字符串方法的某些功能。

Wouldn’t be nice if we can write the Article type logic exactly as we did in the full-schema-string method but in a way that generates the flexible GraphQLObjectType that we can plug into an object-based schema?

如果我们可以像在全模式字符串方法中那样完全编写Article类型逻辑,但是以一种生成可以插入到基于对象的模式中的灵活GraphQLObjectType的方式GraphQLObjectType ,那会不好吗?

Something like:

就像是:

Wouldn’t that be ideal? We get the benefits of the full-schema-string method for this type but with no lockdown! Other types in the system can be maintained differently. Maybe other types will be dynamically constructed using a different maker logic!

那不是理想吗? 对于这种类型,我们可以使用全模式字符串方法的好处,但是没有锁定! 系统中的其他类型可以不同地维护。 也许其他类型将使用不同的制造商逻辑动态构建!

All we need to make this happen is a magical typeMakerMethod to take the parts that matter and transform them into the complete GraphQLObjectType for Article.

我们要做的就是使用一个神奇的typeMakerMethod来处理重要的部分,并将其转换为Article的完整GraphQLObjectType

The typeMakerMethod will need to parse a string into an AST, use that to build a GraphQLObjectType, then merge the set of custom resolver functions with the fieldsconfiguration that’ll be parsed from the typeDef string.

typeMakerMethod将需要将字符串解析为AST,使用该字符串来构建GraphQLObjectType ,然后将自定义解析器功能集与将从typeDef字符串解析的fields配置typeDef

I like a challenge so I dug a little bit deeper to see how hard would it be to implement the typeMakerMethod. I knew I couldn’t use the graphql.buildSchema function because it only parses one full schema string to make a non executable schema object. I needed a lower-level part that parses a string that has exactly ONE type and then attaches custom resolvers to it. So I started reading the source code of GraphQL.js to look for clues. A few cups of coffee later, I found some answers (in 2 places):

我喜欢挑战,所以我更深入地研究了实现typeMakerMethod 。 我知道我不能使用graphql.buildSchema函数,因为它仅解析一个完整的模式字符串以构成一个不可执行的模式对象。 我需要一个较低级的部分来解析一个字符串,该字符串具有一个唯一的类型,然后将自定义解析器附加到该字符串。 因此,我开始阅读GraphQL.js的源代码以寻找线索。 几杯咖啡之后,我在2个地方找到了一些答案:

That’s the core method used in buildSchema to construct ONE type from a type definition node (which we can easily get by parsing the typeDef string).

这是buildSchema用于从类型定义节点构造ONE类型的核心方法(我们可以通过解析typeDef字符串轻松获得该typeDef )。

And:

和:

That’s how easy it is to extend an object type and attach any logic needed in fields and interfaces!

扩展对象类型并附加fieldsinterfaces所需的任何逻辑都是那么容易!

All I needed to do is put a few pieces together and the dream can be true.

我需要做的就是将几部分放在一起,梦想就可以实现。

I did.

我做到了

Ladies and gentlemen. I present to you the magical "typeMakerMethod" (which I named objectType):

女士们先生们。 我向您展示了神奇的“ typeMakerMethod”(我将其命名为objectType ):

That’s it (in its most basic form)! This will take a typeDef string that defines a single GraphQL type, an object of resolvers and a map of dependencies (for that type), and it’ll return a GraphQLObjectType ready to be plugged into your object-based schema as if it was defined normally with the object constructor.

就是这样(以最基本的形式)! 这将使用一个typeDef字符串,该字符串定义一个GraphQL类型,一个解析器对象和一个依赖关系映射(针对该类型),并将返回一个GraphQLObjectType可以将其插入到基于对象的模式中,就像定义了它一样通常使用对象构造函数。

Now you can use the object-based method but you have the option to define SOME types using an approach similar to the full-schema-string method. You have the power.

现在,您可以使用基于对象的方法,但是您可以选择使用类似于全模式字符串方法的方法来定义某些类型。 你有力量。

What do you think of this approach? I’d love to hear your feedback!

您如何看待这种方法? 我希望听到您的反馈!

Please note that the objectType code above is just the basic use case. There are many other use cases that require further code. For example, if the types have circular dependencies (articleauthorarticle) then this version of objectType will not work. We can delay the loading of the circular dependencies until we’re in the fields thunk (which is the current approach to solve this problem in the object-based method). We can also use the "extend" syntax to design the schema in a way that avoids circular dependencies in the first place. I’ve skipped this part to keep the example simple.

请注意,上面的objectType代码只是基本用例 。 还有许多其他用例需要更多代码。 例如,如果类型具有循环依赖关系( articleauthorarticle ),则此版本的objectType将不起作用。 我们可以延迟循环依赖项的加载,直到我们进入thunk fields为止(这是在基于对象的方法中解决此问题的当前方法)。 我们还可以使用“ extend”语法来设计架构,从而避免循环依赖。 为了使示例简单,我已跳过了这一部分。

If you’d like to give it a spin I published a more polished version of objectType and a few other maker functions like it under the graphql-makers npm package.

如果您想尝试一下,我在graphql-makers npm软件包下发布了一个更完善的objectType版本和其他一些maker函数。



Originally published at https://jscomplete.com on June 9, 2019.

最初于 2019年6月9日 发布在 https://jscomplete.com

翻译自: https://www.freecodecamp.org/news/graphql-makers/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值