GraphQL与REST

2023/05/12

1. 概述

在创建Web服务来支持我们的应用程序时,我们可能会选择使用REST或GraphQL作为通信模式。虽然两者都最有可能通过HTTP使用JSON,但它们各有优缺点。

在本教程中,我们将比较GraphQL和REST。我们将创建一个产品数据库示例,并比较两种解决方案在执行相同的客户端操作时的差异。

2. 示例服务

我们的示例服务将使我们能够:

  • 创建处于草稿状态的产品
  • 更新产品详细信息
  • 获取产品列表
  • 获取单个产品及其订单的详细信息

让我们首先使用REST创建应用程序。

3. REST

REST(Representational State Transfer)是分布式超媒体系统的一种架构风格。REST中的主要数据元素称为资源。在这个例子中,资源是“product”。

3.1 创建产品

要创建产品,我们将在API上使用POST方法:

curl --request POST 'http://localhost:8081/product' \
--header 'Content-Type: application/json' \
--data '{
    "name": "Watch",
    "description": "Special Swiss Watch",
    "status": "Draft",
    "currency": "USD",
    "price": null,
    "imageUrls": null,
    "videoUrls": null,
    "stock": null,
    "averageRating": null
}'

因此,系统将创建一个新产品。

3.2 更新产品

在REST中,我们通常使用PUT方法更新产品:

curl --request PUT 'http://localhost:8081/product/{product-id}' \
--header 'Content-Type: application/json' \
--data '{
    "name": "Watch",
    "description": "Special Swiss Watch",
    "status": "Draft",
    "currency": "USD",
    "price": 1200.0,
    "imageUrls": [
        "https://graphqlvsrest.com/imageurl/product-id"
    ],
    "videoUrls": [
        "https://graphqlvsrest.com/videourl/product-id"
    ],
    "stock": 10,
    "averageRating": 0.0
}'

因此,{product-id}对象将有一个更新。

3.3 获取产品列表

列出产品通常是一个GET操作,我们可以使用查询字符串来指定分页:

curl --request GET 'http://localhost:8081/product?size=10&page=0'

第一个响应对象是:

{
    "id": 1,
    "name": "T-Shirt",
    "description": "Special beach T-Shirt",
    "status": Published,
    "currency": "USD",
    "price": 30.0,
    "imageUrls": ["https://graphqlvsrest.com/imageurl/1"],
    "videoUrls": ["https://graphqlvsrest.com/videourl/1"],
    "stock": 10,
    "averageRating": 3.5
}

3.4 通过订单获取单个产品

要获取产品及其订单,我们通常可能希望使用之前的API获取产品列表,然后调用订单资源以查找相关订单:

curl --request GET 'localhost:8081/order?product-id=1'

第一个响应对象是:

{
    "id": 1,
    "productId": 1,
    "customerId": "de68a771-2fcc-4e6b-a05d-e30a8dd0d756",
    "status": "Delivered",
    "address": "43-F 12th Street",
    "creationDate": "Mon Jan 17 01:00:18 GST 2022"
}

当我们通过product-id查询订单时,将id作为查询参数提供给GET操作是有意义的。但是,我们应该注意,在获取所有产品的原始操作之上,我们需要为我们感兴趣的每个产品执行一次此操作。这与N+1选择问题有关。

4. GraphQL

GraphQL是一种API查询语言,它带有一个框架,可以使用现有的数据服务来完成这些查询。

GraphQL的构建块是queries和mutations。查询负责获取数据,而突变用于创建和更新。

query和mutation都定义了一个schema。该模式定义了可能的客户端请求和响应。

让我们使用GraphQL服务器重新实现我们的示例。

4.1 创建产品

让我们使用一个名为saveProduct的突变:

curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{
    "query": "mutation {saveProduct (
        product: {
            name: \"Bed-Side Lamp\",
            price: 24.0,
            status: \"Draft\",
            currency: \"USD\"
        }){ id name currency price status}
    }"
}'

在此saveProduct函数中,圆括号内的所有内容都是输入类型schema。这之后的大括号描述了服务器要返回的字段

当我们运行突变时,我们应该期望一个包含所选字段的响应:

{
    "data": {
        "saveProduct": {
            "id": "12",
            "name": "Bed-Side Lamp",
            "currency": "USD",
            "price": 24.0,
            "status": "Draft"
        }
    }
}

此请求与我们使用REST版本发出的POST请求非常相似,只是我们现在可以稍微自定义响应。

4.2 更新产品

同样,我们可以使用另一个名为updateProduct的突变来修改产品:

curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{"query": "mutation {updateProduct(
    id: 11
    product: {
        price: 14.0,
        status: \"Publish\"
    }){ id name currency price status }  
  }","variables":{}}'

我们在响应中得到选定的字段:

{
    "data": {
        "updateProduct": {
            "id": "12",
            "name": "Bed-Side Lamp",
            "currency": "USD",
            "price": 14.0,
            "status": "Published"
        }
    }
}

正如我们所看到的,GraphQL提供了响应格式的灵活性

4.3 获取产品列表

要从服务器获取数据,我们将使用查询:

curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{
    "query": "query {products(size:10,page:0){id name status}}"
}'

在这里,我们还描述了我们希望看到的结果页:

{
    "data": {
        "products": [
            {
                "id": "1",
                "name": "T-Shirt",
                "status": "Published"
            },
            ...
        ]
    }
}

4.4 通过订单获取单个产品

使用GraphQL,我们可以要求GraphQL服务器将产品和订单连接在一起:

curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{
    "query": "query {product(id:1){ id name orders{customerId address status creationDate}}}"
}'

在此查询中,我们获取id等于1的产品及其订单。这使我们能够在单个操作中发出请求,让我们检查响应:

{
    "data": {
        "product": {
            "id": "1",
            "name": "T-Shirt",
            "orders": [
                {
                    "customerId": "de68a771-2fcc-4e6b-a05d-e30a8dd0d756",
                    "status": "Delivered",
                    "address": "43-F 12th Street",
                    "creationDate": "Mon Jan 17 01:00:18 GST 2022"
                },
                ...
            ]
        }
    }
}

正如我们在这里看到的,响应包含产品的详细信息及其各自的订单。

为了使用REST实现这一点,我们需要发送几个请求-第一个请求获取产品,第二个请求获取相应的订单。

5. 比较

这些示例展示了GraphQL的使用如何减少客户端和服务器之间的流量,并允许客户端为响应提供一些格式化规则。

值得注意的是,这些API背后的数据源可能仍然需要执行相同的操作来修改或获取数据,但客户端和服务器之间接口的丰富性允许客户端使用GraphQL做更少的工作。

让我们进一步比较这两种方法。

5.1 灵活和动态

GraphQL允许灵活和动态的查询:

  • 客户端应用程序只能请求必填字段
  • 别名可用于请求具有自定义key的字段
  • 客户端可以使用查询来管理结果顺序
  • 客户端可以更好地与API中的任何更改分离,因为没有单一版本的响应对象结构可遵循

5.2 成本较低的操作

每个服务器请求都有往返时间和负载大小的成本

在REST中,我们可能最终会发送多个请求来实现所需的功能。这些多个请求将是一项昂贵的操作。此外,响应有效负载可能包含客户端应用程序可能不需要的不必要数据。

GraphQL倾向于避免昂贵的操作。我们通常可以使用GraphQL在单个请求中获取我们需要的所有数据

5.3 何时使用REST?

GraphQL不是REST的替代品。两者甚至可以共存于同一个应用程序中。托管GraphQL端点的复杂性增加可能是值得的,具体取决于用例。

在以下情况下,我们可能更倾向REST:

  • 我们的应用程序自然是资源驱动的,其中的操作非常直接且完全与各个资源实体相关联
  • 需要网络缓存,因为GraphQL本身不支持它
  • 我们需要文件上传,因为GraphQL本身不支持它

6. 总结

在本文中,我们使用一个实际示例比较了REST和GraphQL。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章