面试知识微服务微服务

浅谈三种API设计风格RPC、REST、GraphQL

2019-02-17  本文已影响217人  零壹技术栈

前言

Web API设计其实是一个挺重要的设计话题,许多公司都会有公司层面的Web API设计规范,几乎所有的项目在详细设计阶段都会进行API设计,项目开发后都会有一份API文档供测试和联调。本文尝试根据自己的理解总结一下目前常见的四种API设计风格以及设计考虑点。

正文

1. RPC

这是最常见的方式,RPC说的是本地调用远程的方法,面向的是过程。

这里就不贴例子了,估计超过50%的API是这种风格的。

2. REST

是一种架构风格,有四个级别的成熟度:

级别0其实就是类RPC的风格,级别3是真正的REST,大多数号称REST的API在级别2。REST实现一些要点包括:

{
    "content": [ {
        "price": 499.00,
        "description": "Apple tablet device",
        "name": "iPad",
        "links": [ {
            "rel": "self",
            "href": "http://localhost:8080/product/1"
        } ],
        "attributes": {
            "connector": "socket"
        }
    }, {
        "price": 49.00,
        "description": "Dock for iPhone/iPad",
        "name": "Dock",
        "links": [ {
            "rel": "self",
            "href": "http://localhost:8080/product/3"
        } ],
        "attributes": {
            "connector": "plug"
        }
    } ],
    "links": [ {
        "rel": "product.search",
        "href": "http://localhost:8080/product/search"
    } ]
}

Spring框架也提供了相应的支持:https://spring.io/projects/spring-hateoas

@RestController
public class GreetingController {

    private static final String TEMPLATE = "Hello, %s!";

    @RequestMapping("/greeting")
    public HttpEntity<Greeting> greeting(
            @RequestParam(value = "name", required = false, defaultValue = "World") String name) {

        Greeting greeting = new Greeting(String.format(TEMPLATE, name));
        greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());

        return new ResponseEntity<>(greeting, HttpStatus.OK);
    }
}

产生如下的结果:

可以说REST的API设计是需要设计感的,需要仔细来思考API的资源,资源之间的关系和导航,URI的定义等等。对于一套设计精良的REST API,其实客户端只要知道可用资源清单,往往就可以轻易根据约定俗成的规范以及导航探索出大部分API。比较讽刺的是,有很多网站给前端和客户端的接口是REST的,爬虫开发者可以轻易探索到所有接口,甚至一些内部接口,毕竟猜一下REST的接口比RPC的接口容易的多。

作为补充,下面再列几个有关REST API设计大家争议讨论纠结的比较多的几个方面。

3. GraphQL

如果说RPC面向过程,REST面向资源,那么GraphQL就是面向数据查询了。GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

采用GraphQL,甚至不需要有任何的接口文档,在定义了Schema之后,服务端实现Schema,客户端可以查看Schema,然后构建出自己需要的查询请求来获得自己需要的数据。

image
#
# Schemas must have at least a query root type
#
schema {
    query: Query
}

type Query {
    characters(
        episode: Episode
    ) : [Character]

    human(
        # The id of the human you are interested in
        id : ID!
    ) : Human

    droid(
        # The non null id of the droid you are interested in
        id: ID!
    ): Droid
}

# One of the films in the Star Wars Trilogy
enum Episode {
    # Released in 1977
    NEWHOPE
    # Released in 1980.
    EMPIRE
    # Released in 1983.
    JEDI
}

# A character in the Star Wars Trilogy
interface Character {
    # The id of the character.
    id: ID!
    # The name of the character.
    name: String!
    # The friends of the character, or an empty list if they
    # have none.
    friends: [Character]
    # Which movies they appear in.
    appearsIn: [Episode]!
    # All secrets about their past.
    secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}

# A humanoid creature in the Star Wars universe.
type Human implements Character {
    # The id of the human.
    id: ID!
    # The name of the human.
    name: String!
    # The friends of the human, or an empty list if they have none.
    friends: [Character]
    # Which movies they appear in.
    appearsIn: [Episode]!
    # The home planet of the human, or null if unknown.
    homePlanet: String
    # Where are they from and how they came to be who they are.
    secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}

# A mechanical creature in the Star Wars universe.
type Droid implements Character {
    # The id of the droid.
    id: ID!
    # The name of the droid.
    name: String!
    # The friends of the droid, or an empty list if they have none.
    friends: [Character]
    # Which movies they appear in.
    appearsIn: [Episode]!
    # The primary function of the droid.
    primaryFunction: String
    # Construction date and the name of the designer.
    secretBackstory : String @deprecated(reason : "We have decided that this is not canon")
}

采用GraphQL Playground(https://github.com/prisma/graphql-playground

其实就是__schema:

然后我们可以根据客户端的UI需要自己来定义查询请求,服务端会根据客户端给的结构来返回数据:

再来看看Github提供的GraphQL(更多参考https://developer.github.com/v4/guides/):

查询出了最后的三个repo:

GraphQL就是通过Schema来明确数据的能力,服务端提供统一的唯一的API入口,然后客户端来告诉服务端我要的具体数据结构(基本可以说不需要有API文档),有点客户端驱动服务端的意思。虽然客户端灵活了,但是GraphQL服务端的实现比较复杂和痛苦的,GraphQL不能替代其它几种设计风格,并不是传说中的REST 2.0。

小结

在下列情况考虑RPC风格的API或说是RPC:

在下列情况考虑REST风格:

在下列情况考虑GraphQL:

参考书籍


欢迎关注技术公众号: 零壹技术栈

零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

上一篇下一篇

猜你喜欢

热点阅读