Skip to content

Commit

Permalink
将现有的 API 从 REST 迁移到 GraphQL (#2002)
Browse files Browse the repository at this point in the history
  • Loading branch information
zaraguo authored and linhe0x0 committed Aug 11, 2017
1 parent ab9f124 commit 91738da
Showing 1 changed file with 46 additions and 46 deletions.
92 changes: 46 additions & 46 deletions TODO/moving-existing-api-from-rest-to-graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,56 @@
> * 原文作者:[Roman Krivtsov](https://medium.com/@raxwunter)
> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner)
> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO/moving-existing-api-from-rest-to-graphql.md](https://github.com/xitu/gold-miner/blob/master/TODO/moving-existing-api-from-rest-to-graphql.md)
> * 译者:
> * 译者:[zaraguo](https://github.com/zaraguo)
> * 校对者:
# Moving existing API from REST to GraphQL
# 将现有的 API REST 迁移到 GraphQL

So last 6 months I saw talks about GraphQL at almost every conference related to web development. A lot of posts and articles are issued about it. But all these things are mostly about basics of GraphQL or new features and looked superficially. So I’d like to tell about my personal experience of adopting real big system to GraphQL.
最近的六个月内我发现几乎每一场有关于 Web 开发的大会都谈论到了 GraphQL。也有大量与其相关的文章被发表。但是所有的这些几乎都是在讲 GraphQL 的基础概念或者是新特性,说得很表面。因此我打算谈谈我在真实大型系统中采用 GraphQL 的个人经验。

### What’s wrong with REST
### REST 有什么问题

REST (as well as SOAP) doesn’t separate concerns of transport, security and data logic. It leads to plenty of problems. Let’s consider some of them.
REST(一如 SOAP)没有分离传输、安全和数据逻辑层面。这会带来很多问题。让我们来看看其中的几个。

#### Poor GET query capabilities
#### GET 查询能力的低下

It’s not possible to do deep and complex queries with GET string. Let’s assume, that we need to find users. First case is pretty straightforward:
GET 语句进行复杂深入的查询是不可能的。假设我们需要查询用户。举一个很简单的例子:

**GET** /users/?name=Homer

Then imagine, that we have to find user with names Homer OR Marge. Things become tricky. We can define some separator for this kind of syntax.
然后想象一下,我们需要查找名字是 Homer 或者 Marge 的用户。事情就变得有点棘手了。当然,我们可以为这种需求定义一些分隔符。

**GET** /users/?name=Homer|Marge

Don’t forget to escape characters! And also, please keep in mind, that if some name contains “|” you screwed. More complex example is trying to get union of 2 different fields. And intersection of course as well!
但是,不要忘记转义这些字符!并且牢记,如果有人的名字中包含 “|” 那么你就完蛋啦。如果要结合两个不同的字段那么就更复杂了。更别说是需要同时满足上面两种条件的查询。

So far we were using fields for searching as is. But usually we also need to pass some service data with query. For example for pagination:
目前我们一般都是使用字段来查询对应的内容。但是也时常需要用查询语句来传递一些服务数据。比如页码:

**GET** /users/?name=Homer|Marge&limit=10&offset=20

According to some logic, our query parser on the backend should recognize limit and offset parameters as fields for database, because it’s declared at the same level as “name”.
按逻辑来说,我们后端的查询解析器应该会将 limit offset 识别为数据库的字段,因为他们被声明为和 “name” 字段同级的参数名。

We can invent our own syntax or use POST method (that is wrong, because this is an idempotent request) but it seems as inventing a wheel.
我们可以发明我们自己的语法或是用 POST 方法(这是不对的,因为这是一个幂等请求)但是这看起来像是在造轮子。

#### Problems with data updating
#### 数据更新的问题

The easiest way to update data using REST — is to use PUT method sending whole the object to update. But of course it’s not the most efficient way, when you need to update just one field in 1Mb entity.
使用 PUT 发送整个对象是最简单的 REST 更新数据的方式。但显而易见的是,当你仅仅只需要更新 1 Mb 大小的对象中的一个字段时,这并不是最有效的方式。

HTTP has PATCH method. But there is a problem with it. Algorithm definition *how exactly to update entity* is not easy at all. Furthermore there are several specifications which recommend how you should do it, e.g *RFC 6902, RFC 7396* as well as many custom solutions.
HTTP 还有一个 PATCH 方法。但是它有一个问题。用算法来定义 **如何更新实体** 并不简单。现有多个规范建议你应该如何去做,比如 *RFC 6902RFC 7396* 以及许多自定义解决方案。

#### Naming problem
#### 命名问题

I guess each developer ever dealt with REST knows that feeling, when you don’t know how to name your new route. Not all of business cases can be described in terms of resources. For example we’d like to make a search for products with information of it’s shop.
我猜测每个曾与 REST 打交道的开发者都明白这种感受,当你不知道如何去命名你的新路由时。并非所有的业务实例都可以被描述为资源。例如我们想要搜索带有商店信息的商品。

**GET** /search?product_name=IPhone&shop_name=IStore

What is resource here? Products? Shops? Search?
这里的资源是什么?商品?商店?搜索?

OMG my API is not going to be RESTfull!
天哪,我的 API 不再是 REST 风格了!

Or another canonical example with user login. What is resource here? Spoiler: there is no resources here, it’s just a Remote Procedure Call.
另一个典型的例子便是用户登录。这里的资源又是什么?Spoiler:这里没有资源,这里只是个远程过程调用而已。

#### Handling REST on backend
#### 后端处理 REST

```
app.post((req, res) => {
Expand All @@ -63,67 +63,67 @@ app.post((req, res) => {
});
```

This is an example of Express.js route. Here we are trying to get user_id to be able to query user. Let’s look how usually an API function should look like:
这是一个 Express 路由的例子。这里我们试图获取用户的 ID 来查找用户。让我们看一看 API 函数通常应该是什么样子:

![](https://cdn-images-1.medium.com/max/1200/1*-x82CcGJlLIOOJtRRBRNSg.png)

Function accepts arguments, that it needs to perform the certain action and return the certain result.
函数接收参数,进行特定的处理并返回特定的结果。

What do we get as arguments in case of Express route? A huge messy object *req* and somewhere inside of it needed peace of data.
在这个 Express 路由的例子中我们的参数是什么?一个巨大杂乱的 *req* 对象,而我们仅需要其中很小的一部分数据。

Of course it’s a problem of Express as well (Node HTTP module to be precisely), but such interface was evolved also driving by HTTP realization — request parameters can be anywhere and it’s not possible to know exactly where if you are not aware of it personally or you don’t use well described API docs.
当然,这也是 Express 的一个问题(准确的说是 Node HTTP 模块的问题),但是这样的接口也是因为 HTTP 的实现逐步进化而产生的 - 请求参数可以在任何位置,所以如果你本人不知道它或者没有使用描述良好的文档时想要准确知道参数位置是不可能的。

That’s why using REST without API docs is so painful.
这就是为什么使用没有接口文档的 REST 是如此的痛苦。

### GraphQL

This article assumes, that you are already familiar with GraphQL basics. If not, you can start with the great intro text from Apollo about [GraphQL basics](https://medium.com/apollo-stack/the-basics-of-graphql-in-5-links-9e1dc4cac055#.uyc4ml4jx)
在这里我们假设你早就熟悉 GraphQL 的基础知识。如果没有,你可以从 Apollo 写的关于 [GraphQL 基础知识](https://medium.com/apollo-stack/the-basics-of-graphql-in-5-links-9e1dc4cac055#.uyc4ml4jx)的介绍开始。

So previously we inferred, that REST has design problems. But GraphQL definitely not. Furthermore it has a huge growth potential.
正如我们前面所展示的,REST 存在一些 GraphQL 所没有的设计上的问题。并且 GraphQL 有着巨大的发展潜力。

First of all GraphQL provides a RPC approach, which means that you are not limited with your client-server interaction. GraphQL has it’s own type system, which means that you are not going to have misleading errors and bugs. Also type system means that your client is able to support smart cache for your data on the items level. Together with plenty of web oriented features like connections (cursors and pagination), batching, deferring etc. it **makes your client-server interaction as efficient as possible**.
首先 GraphQL 提供 RPC 访问方式,这意味着你将不受客户端-服务端的交互限制。GraphQL 有它自己的类型系统,这意味不再有令人误解的错误和漏洞。并且类型系统意味着你的客户端可以提供 item 级别的数据智能缓存。还拥有大量像是网络连接(游标和分页)、批处理、延时等的面向 Web 的特性。它 **使你的客户端-服务端交互尽可能的高效**

> But REST is still an industry standard
> 但是 REST 仍然是业内标准
Yes, whether we like it or not but REST is a must be API format for the nearest several years.
是的,无论我们是否喜欢,REST 都是近几年 API 的主流形式。

But we can still use GraphQL for our internal purposes (for some advanced clients for example) and REST for others.
但是我们仍然可以为一些内部需求(比如对接一些高级客户端)去使用 GraphQL,其他的使用 REST

For that we need to wrap REST endpoints to GraphQL types. There are some articles and examples (the most cited is [https://github.com/apollostack/swapi-rest-graphql](https://github.com/apollostack/swapi-rest-graphql)) of moving from REST to GraphQL. But they suggest to use custom resolvers, that is unsatisfactorily for big projects having hundreds of endpoints.
为此,我们需要将 REST 路径包装成 GraphQL 类型。这里有一些文章和例子(被提到最多的是 [swapi-rest-graphql](https://github.com/apollostack/swapi-rest-graphql))关于从 REST 迁移到 GraphQL。但是它们建议使用自定义解析器,这无法满足拥有成百上千路径的大型项目。

At my last 3 projects I used [Swagger](http://swagger.io) to describe the REST API. It’s more or less standard for declarative API description. And I honestly have no clue how some people write huge APIs not having it described.
在我最近的三个项目中我使用 [Swagger](http://swagger.io) 来描述 REST 接口。它或多或少都算是声明式接口描述的标准。坦白说我真的不知道那些编写庞大却毫无描述的接口的人们是如何做到的。

So having Swagger as a standard for declarative REST API from one hand and GraphQL from the another one, we can see that they are pretty similar, Swagger in addition just tries to describe HTTP details as well as business logic. **Both of them describe in types incoming arguments and outgoing response**. It means that we can write adapter between them!
一方面我们把 Swagger 作为声明式 REST 接口的标准,另一方面也可以这么看 GraphQL,我们可以看到它们其实非常相似,只是除此之外 Swagger 还尝试去描述 HTTP 细节和业务逻辑。**它们都描述了传入参数和传出响应的类型**。这意味着我们可以在它们之间写适配器!

![](https://cdn-images-1.medium.com/max/1200/1*R55lFpFRNqkScfMnTXpPfw.png)

That’s how REST endpoint
REST 路径是这样子的

**GET** /user/id

can be adopted to GraphQL type.
可以采用 GraphQL 类型。

So now we just need a library which does it automatically. And here it is!
所以现在我们只需一个库来帮助我们自动转换。下面这个就是!

[https://github.com/yarax/swagger-to-graphql](https://github.com/yarax/swagger-to-graphql)

Swagger2graphQL accepts your Swagger schema and returns GraphQL schema, where resolvers are automatically build HTTP requests to existing REST endpoints.
Swagger2graphQL 接收你的 Swagger schema 然后返回 GraphQL schema,同时解析程序将自动构建 HTTP 请求到已有的 REST 路径上。

It was built as a side project of moving real big system with more than 150 endpoints to GraphQL. We needed to move to GraphQL as fast as possible with minimum effort and problems.
它被构建为一个将拥有超过 150 个路径的真实大型系统迁移到 GraphQL 的副项目。我们需要在做功和问题都最少的情况下尽快地迁移到 GraphQL

So just clone the repository, run
只需要克隆资源库,运行

*npm install && npm start*

and go to [http://localhost:3009/graphql](http://localhost:3009/graphql)
然后访问 [http://localhost:3009/graphql](http://localhost:3009/graphql)

You will see GraphQL API wrapped over [http://petstore.swagger.io/](http://petstore.swagger.io/) Swagger demo API.
你会看到封装在 [http://petstore.swagger.io/](http://petstore.swagger.io/) Swagger 示例接口上的 GraphQL 接口。

Furthermore, having Swagger and GraphQL it’s very convenient to write new endpoints. If you already familiar with GraphQL you probably noticed that sometimes types description look very verbose, because you have to create a lot of implicit types. Swagger2graphQL makes them automatically and you just need to create a new declarative endpoint in Swagger schema, which usually is very easy.
而且,有了 Swagger GraphQL 编写新的路径将变得十分方便。如果你早就熟悉 GraphQL,你可能会发现有时候类型描述看起来相当冗长,因为你需要去创建大量的隐式类型。Swagger2graphQL 可以自动完成这些步骤,你只需要在 Swagger schema 中创建一个新的带有声明的路径,通常这很简单。

Please send issues if you come across with any problems or questions!
如果你遇到任何困难或者有疑问请向我提 issue!

Also you can find me in Twitter [http://twitter.com/raxpost](http://twitter.com/raxpost)
同时你也可以在 [Twitter](http://twitter.com/raxpost) 上找到我


---
Expand Down

0 comments on commit 91738da

Please sign in to comment.