美文网首页面试知识微服务微服务
浅谈三种API设计风格RPC、REST、GraphQL

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

作者: 零壹技术栈 | 来源:发表于2019-02-17 19:56 被阅读217次

    前言

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

    正文

    1. RPC

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

    • RPC形式的API组织形态是类和方法,或者说领域和行为。
    • 因此API的命名往往是一个动词,比如GetUserInfo和CreateUser。
    • 因为URI会非常多而且往往没有一些约定规范,所以需要有详细的文档。
    • 也是因为无拘无束,HTTP方法基本只用GET和POST,设计起来比较简单。

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

    2. REST

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

    • 级别0:定义一个 URI,所有操作是对此 URI 发出的 POST 请求。
    • 级别1:为各个资源单独创建 URI。
    • 级别2:使用 HTTP 方法来定义对资源执行的操作。
    • 级别3:使用超媒体(HATEOAS)。

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

    • REST形式的API组织形态是资源和实体,一切围绕资源(级别1的要点)。设计流程包括:

      • 确定API提供的资源
      • 确定资源之间的关系
      • 根据资源类型和关系确定资源URI结构
      • 确定资源的结构体
    • 会定义一些标准方法(级别2的要点),然后把标准方法映射到实现(比如HTTP Method):

      • GET:获取资源详情或资源列表。对于collection类型的URI(比如/customers)就是获取资源列表,对于item类型的URI(比如/customers/1)就是获取一个资源。

      • POST:创建资源,请求体是新资源的内容。往往POST是用于为集合新增资源。

      • PUT:创建或修改资源,请求体是新资源的内容。往往PUT用于单个资源的新增或修改。实现上必须幂等。

      • PATCH:部分修改资源,请求体是修改的那部分内容。PUT一般要求提交整个资源进行修改,而PATCH用于修改部分内容(比如某个属性)。

      • DELETE:移除资源。和GET一样,对于collection类型的URI(比如/customers)就是删除所有资源,对于item类型的URI(比如/customers/1)就是删除一个资源。

    • 需要考虑资源之间的导航(级别3的要点,比如使用HATEOAS,HATEOAS是Hypertext as the Engine of Application State的缩写)。有了资源导航,客户端甚至可能不需要参阅文档就可以找到更多对自己有用的资源,不过HATEOAS没有固定的标准,比如:
    {
        "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需要有一致的接口来解耦客户端和服务实现,如果基于HTTP那么务必使用HTTP
      • Method来操作资源,而且尽量使用HTTP响应码来处理错误
      • 需要尽量考虑缓存、版本控制、内容协商、部分响应等实现

    可以说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:

    • 偏向内部的API
    • 没有太多的时间考虑API的设计或没有架构师
    • 提供的API很难进行资源、对象抽象
    • 对性能有高要求

    在下列情况考虑REST风格:

    • 偏向外部API
    • 提供的API天生围绕资源、对象、管理展开
    • 不能耦合客户端实现
    • 资源的CRUD是可以对齐的(功能完整的)

    在下列情况考虑GraphQL:

    • 客户端对于数据的需求多变
    • 数据具有图的特点

    参考书籍

    • 周志明,深入理解Java虚拟机

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

    零壹技术栈

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

    相关文章

      网友评论

        本文标题:浅谈三种API设计风格RPC、REST、GraphQL

        本文链接:https://www.haomeiwen.com/subject/wclneqtx.html