微服务查询语言 - restQL 初识

restQL is a microservice query language that makes easy to fetch information from multiple services in the most efficient manner. here

介绍

restQL的设计是用来简化客户端查询,特别是在微服务时代,对于一个复杂的页面,可能需要调用多个服务的接口,获得数据渲染页面。这对于客户端来说,这有着极大的消耗。而 restQL较好的解决的这个问题,并且使用restQL对原来API server无需改动。

restQL支持以下几种查询方式

并行调用(Parallel calls)

他们所产生的查询,是并发进行的,等待所有的处理完成后将直接返回

from hero
    with
        name = "Restman"

from villain
    with
        name = "SOAPLord"

链式调用(Chained invocations)

第二个查询依赖第一个查询的结果,所以需要等待第一个查询结束后,才会调用第二个查询,最终返回结果

from hero
    with
        name = "Restman"

from sidekick
    with
        hero = hero.id

Multiplexed invocations

这个同样是第一个查询依赖第二个查询,但是第一个查询出来的是一个array,并对于array中的每个元素,调用第二个查询,处理完成后返回

from search
    with
        role = "hero"

from hero as heroes
    with
        name = search.results.name

详细介绍见:click here

安装

restQL依赖一个restQL-http对外提供服务,由restQL-http解析restQL的查询语言并且调用资源服务(API server)

下载restQL-http到任意目录,并且解压,这里安装的是

## 下载
curl -O https://github.com/B2W-BIT/restQL-http/releases/download/v3.5.5/restql-http-v3.5.5.zip

## 解压
unzip -d restql-http restql-http-v3.5.4.zip

## 运行
cd restql-http
planets=https://swapi.co/api/planets/:id ./bin/run.sh

入门

对于入门最好的方式,莫过于官方文档了click here

官网是以swapi.co的API作为测试,这里也将使用这个服务作为简单的入门使用。如果需要配置更高级的使用方式,click here

运行

cd restql-http
planets=https://swapi.co/api/planets/:id ./bin/run.sh

启动成功后,将会默认监听9000端口,http://localhost:9000,也可以通过设置环境变量的方式,使其端口改变

export PORT=1456
planets=https://swapi.co/api/planets/:id ./bin/run.sh

restQL提供的接口路径是 http://localhost:9000/run-query

可以通过curl方式调用服务,检查是否启动正常

curl -d "from planets as allPlanets" -H "Content-Type: text/plain" localhost:9000/run-query

正常情况下将会得到类似以下的输出

{
    "allPlanets": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": ...
    }
}

查询一条数据

restQL 查询语句

from planets
    with id = 1

调用接口,这里推荐使用PostmanInsomnia等工具测试

curl -d "from planets with id = 1" -H "Content-Type: text/plain" localhost:9000/run-query

将会得到以下数据

{
    "planets": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": {
            "surface_water": "1",
            "climate": "arid",
            "residents": [
                ...
            ],
            "orbital_period": "304",
            "name": "Tatooine",
            "diameter": "10465",
            "created": "2014-12-09T13:50:49.641000Z",
            "gravity": "1 standard",
            "edited": "2014-12-21T20:48:04.175778Z",
            "films": [
                ...
            ],
            "population": "200000",
            "terrain": "desert",
            "url": "https://swapi.co/api/planets/1/",
            "rotation_period": "23"
        }
    }
}

查询语言

语法

查询语句的顺序,对于 restQL 异常的重要

[ [ use modifier = value ] ]

METHOD resource-name [as some-alias] [in some-resource]
  [ headers HEADERS ]
  [ timeout INTEGER_VALUE ]
  [ with WITH_CLAUSES ]
  [ [only FILTERS] OR [hidden] ]
  [ [ignore-errors] ]

Methods

restQL 提供了HTTP常用的几个 methods (GET,POST,PUT, PATCH, DELETE)

  • from - HTTP GET
  • to - HTTP POST
  • into - HTTP PUT
  • update - HTTP PATCH
  • delete - HTTP DELETE

from

planets = https://swapi.co/api/planets/:id

restQL 语句

from planets
    with
        name = "Restman"
        id = 1

将会映射到以下调用

GET https://swapi.co/api/planets/1?name=Restman

to

planets = https://swapi.co/api/planets

restQL 语句

to planets
    with
        name = "Restman"

将会映射到以下调用

POST https://swapi.co/api/planets
BODY {"name": "Restman"}

into

planets = https://swapi.co/api/planets/:id

restQL 语句

into planets
    with
        id = 1
        name = "xxxx"

将会映射到以下调用

PUT https://swapi.co/api/planets/1
BODY {"name": "xxxx"}

update

planets = https://swapi.co/api/planets/:id

restQL 语句

update planets
    with
        id = 1
        name = "abc"

将会映射到以下调用

PATCH https://swapi.co/api/planets/1
BODY {"name": "abc"}

delete

planets = https://swapi.co/api/planets/:id

restQL 语句

delete planets
    with
        id = 1

将会映射到以下调用

DELETE https://swapi.co/api/planets/1?name=Restman

实践

这里将以 https://api.spacexdata.com/ 作为数据服务,接下来的所有数据,都是由它来提供。 click here

restQL 支持多种资源映射方式,可以通过环境变量,也可以通过restql.yml的方式亦或者通过restql-manager

前面演示了通过环境变量的方式,这里将通过restql.yml,在restql-http目录创建restql.yml文件

restql.yml

mappings:
    launches: "https://api.spacexdata.com/v3/launches"

执行

./bin/run.sh

启动成功后,将可以看到类似这样的输出

2019-11-08 14:46:03,239 INFO restql.http.plugin.core [main] No plugins to load
2019-11-08 14:46:03,244 INFO restql.http.server.core [main] Server starting with {:port 1456, :executor-utilization 0.5, :executor-max-threads 512, :executor-control-period 1000}
2019-11-08 14:46:03,465 INFO restql.http.server.core [main] Server started!

简单查询

restql 语句

from launches

查询结果

{
    "launches": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": [...]
    }
}

如果想要查询 2017-06-222017-06-25 之间的数据,只需要给查询语句加上 with

from launches
    with
        start = "2017-06-22"
        end = "2017-06-25"

其他参数也按照这种方式。如果需要指定的获取某一条数据,调用的接口是是

https://api.spacexdata.com/v3/launches/{{flight_number}}

可以添加一条资源映射规则写入到restql.yml

mappings:
    launches: "https://api.spacexdata.com/v3/launches"
    oneLaunches: "https://api.spacexdata.com/v3/launches/:flight_number"

例如查询的是 42

from oneLaunches
    with
        flight_number = 42

响应结果

{
    "oneLaunches": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": {
            ...
            "flight_number": 42,
            ...
        }
    }
}

过滤器(Filter)

restQL还支持一个filter特性,可以指定某个资源获取的数据,这样可以减少payload大小,对于移动客户端优化非常重要。

这里以 launches 作为演示。

from launches
    with
        start = "2017-06-22"
        end = "2017-06-25"
    only
        mission_name,
        flight_number,
        launch_site.site_id,
        ships,
        rocket.first_stage.cores.land_success

响应结果

{
    "launches": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": [
            {
                "mission_name": "BulgariaSat-1",
                "flight_number": 42,
                "launch_site": {
                    "site_id": "ksc_lc_39a"
                },
                "ships": [
                    "ELSBETH3",
                    "GOQUEST",
                    "GOSEARCHER",
                    "OCISLY"
                ],
                "rocket": {
                    "first_stage": {
                        "cores": [
                            {
                                "land_success": true
                            }
                        ]
                    }
                }
            }
        ]
    }
}

并行查询

并行调用,正是restQL需要处理的问题,能够有效减少客户端和服务器的连接数,对于微服务架构而言,可能一个页面所呈现需要的数个服务来提供。这就意味着需要创建数个连接获取资源,这对于客户端来讲,是非常糟糕的。

这里添加一个新的资源映射地址,作为一个测试

mappings:
    launches: "https://api.spacexdata.com/v3/launches"
    oneLaunches: "https://api.spacexdata.com/v3/launches/:flight_number"
    oneHistory: "https://api.spacexdata.com/v3/history/:id"
from launches
    with
        start = "2017-06-22"
        end = "2017-06-25"
    only
        mission_name,
        flight_number,
        launch_site.site_id,
        ships,
        rocket.first_stage.cores.land_success
from oneHistory
    with
        id = 1
{
    "launches": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": [...]
    },
    "oneHistory": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": {
            "event_date_unix": 1222643700,
            "title": "Falcon 1 Makes History",
            "details": "Falcon 1 becomes the first privately developed liquid fuel rocket to reach Earth orbit.",
            "id": 1,
            "event_date_utc": "2008-09-28T23:15:00Z",
            "flight_number": 4,
            "links": {
                "wikipedia": "https://en.wikipedia.org/wiki/Falcon_1",
                "article": "http://www.spacex.com/news/2013/02/11/flight-4-launch-update-0",
                "reddit": null
            }
        }
    }
}

链式查询

链式调用也是restQL的一个亮点,也简单做一下演示

from oneHistory
    with
        id = 1
from oneLaunches
    with
        flight_number = oneHistory.flight_number
    only
        mission_name,
        flight_number,
        launch_site.site_id,
        rocket.first_stage.cores.land_success

响应结果

{
    "oneHistory": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": {
            ...
            "flight_number": 4,
            ...
        }
    },
    "oneLaunches": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": {
            "mission_name": "RatSat",
            "flight_number": 4,
            "launch_site": {
                "site_id": "kwajalein_atoll"
            },
            "rocket": {
                "first_stage": {
                    "cores": [
                        {}
                    ]
                }
            }
        }
    }
}

这个的应用场景也是极为广泛的,特别在于页面信息极其复杂的情况下,在一次请求就可以获取到关联的数据。

多路调用

为了避免歧义,这里将添加一个新的映射地址,作为本次演示,编辑 restql.yaml文件

mappings:
    launches: "https://api.spacexdata.com/v3/launches"
    oneLaunches: "https://api.spacexdata.com/v3/launches/:flight_number"
    history: "https://api.spacexdata.com/v3/history"
    oneHistory: "https://api.spacexdata.com/v3/history/:id"
from history
    with
        start = "2016-06-22"
        end = "2017-06-25"
    only
        flight_number
from oneLaunches
    with
        flight_number = history.flight_number
    only
        mission_name,
        flight_number,

响应结果

{
    "history": {
        "details": {
            "success": true,
            "status": 200,
            "metadata": {}
        },
        "result": [
            {
                "flight_number": 38
            },
            {
                "flight_number": 41
            }
        ]
    },
    "oneLaunches": {
        "details": [
            {
                "success": true,
                "status": 200,
                "metadata": {}
            },
            {
                "success": true,
                "status": 200,
                "metadata": {}
            }
        ],
        "result": [
            {
                "mission_name": "SES-10",
                "flight_number": 38
            },
            {
                "mission_name": "CRS-11",
                "flight_number": 41
            }
        ]
    }
}

完结

restQL 还有几个高级特性,详情请见官网

参考链接