Rust で REST/gRPC/GraphQL API サーバー達を立てる

この記事は[Rust 3 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/rust3)の 20 日目の記事です。
前日の記事は[hibi221b](https://qiita.com/hibi221b)様の[『top 500 Rustlang repositories』](https://hibi221b.dev/rustlang_repos-3/)です。分野問わず様々なリポジトリがリストアップされております。

## 本記事のまとめ
Rust で下記 API を用いるサーバーの実装を試しました。本記事ではそれらのサンプルコードおよび参考にした記事を共有します。

* gRPC（RPC 系）
* REST
* GraphQL

[こちらのリポジトリ](https://github.com/Surpris/test-web-api-rust)に、本記事に関連するコードが含まれております。

## 本編の目次
1. [今回対象とする API の参考記事](#今回対象とする-api-の参考記事)
1. 実装例
    1. [gRPC](#grpc)
    1. [REST](#rest)
    1. [GraphQL](#graphql)
1. [終わりに](#終わりに)

# 今回対象とする API の参考記事
Web API の一般的な説明については、例えば下記の記事が参考になります。

* [『WebAPIについての説明』](https://qiita.com/busyoumono99/items/9b5ffd35dd521bafce47)（[busyoumono99](https://qiita.com/busyoumono99)様）
* [『翻訳: WebAPI 設計のベストプラクティス』](https://qiita.com/mserizawa/items/b833e407d89abd21ee72)（[mserizawa](https://qiita.com/mserizawa)様）

Web 関係で使用される API は多数存在します。[altexsoft 社の記事](https://www.altexsoft.com/blog/soap-vs-rest-vs-graphql-vs-rpc/)によれば、API のアーキテクチャーのスタイルは下のように分類されるようです。

* RPC 系：[XML-RPC](https://ja.wikipedia.org/wiki/XML-RPC), [JSON-RPC](https://ja.wikipedia.org/wiki/JSON-RPC), [gRPC](https://grpc.io/) など
* [SOAP](https://ja.wikipedia.org/wiki/SOAP_(%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB))
* REST 系：[OData](https://www.odata.org/) など
* [GraphQL](https://graphql.org/)

今回実装する gRPC, REST, GraphQL については下の Qiita 記事に詳しくありますので、そちらをご参照ください。

* [『REST APIの設計で消耗している感じたときのgRPC入門』](https://qiita.com/disc99/items/cfca50a32240284578bb)（[disc99](https://qiita.com/disc99)様）
* [『gRPCって何？』](https://qiita.com/oohira/items/63b5ccb2bf1a913659d6)（[oohira](https://qiita.com/oohira)様）
* [『0からREST APIについて調べてみた』](https://qiita.com/masato44gm/items/dffb8281536ad321fb08)（[masato44gm](https://qiita.com/masato44gm)様）
* [『GraphQLの全体像とWebApp開発のこれから』](https://qiita.com/saboyutaka/items/171f7382cdf75b67d076)（[saboyutaka](https://qiita.com/saboyutaka)様）
* [『GraphQLはサーバーサイド実装のベストプラクティスとなるか』](https://qiita.com/saboyutaka/items/8a75602a3a7b52af1a69)（[saboyutaka](https://qiita.com/saboyutaka)様）

# 実装例
## gRPC
gRPC のライブラリは [grpc-ecosystem/awesome-grpc](https://github.com/grpc-ecosystem/awesome-grpc) リポジトリに掲載されています。
上記リポジトリを 2020/12/19 に確認した限りでは、Rust で gRPC の機能を提供しているクレートは下記のようです。

* [grpc-rs](https://github.com/tikv/grpc-rs) - The gRPC library for Rust built on C Core library and futures
* [grpc-rust](https://github.com/stepancheg/grpc-rust) - Rust implementation of gRPC
* [tower-grpc](https://github.com/tower-rs/tower-grpc) - A client and server gRPC implementation based on Tower
* [tonic](https://github.com/hyperium/tonic) - A native gRPC client & server implementation with async/await support

今回はこれらの中で tonic を使用します。[tonic リポジトリのサンプル](https://github.com/hyperium/tonic/blob/master/examples/helloworld-tutorial.md)と[こちらの Qiita 記事](https://qiita.com/watawuwu/items/114e2674736e44d4b16d)を参考にしました。

### ファイル構成など
ファイル構成は次のようになります。

```
grpc_hello_server/
    proto/
        hello_server.proto
    src/
        client.rs
        server.rs
    build.rs
    Cargo.toml
```

`Cargo.toml` の中身を以下に示します。依存クレートは `tonic`、`prost`、`tokio` です。

```rust:Cargo.toml
[package]
name = "grpc-hello-server"
version = "0.1.0"
authors = ["XXXX"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[[bin]]
name = "grpc-hello-server"
path = "src/server.rs"

[[bin]]
name = "grpc-hello-client"
path = "src/client.rs"

[dependencies]
tonic = "0.3.1"
prost = "^0.6"
tokio = { version = "^0.2.13", features = ["macros"] }

[build-dependencies]
tonic-build = "0.3"
```

gRPC では API の仕様を proto ファイルに定義します。
今回実装した `hello_server.proto` の中身を以下に示します。

```proto:hello_server.proto
syntax = "proto3";
package hello_server;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}
```

定義された proto ファイルをコンパイルし、それを他のファイルで呼び出せるようにします。
コンパイルするための関数を `build.rs` の中に定義しています。

```rust:build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("proto/hello_server.proto")?;
    Ok(())
}
```

上記でコンパイルされると、下記 `client.rs` や `server.rs` のように proto で定義されたものを利用できるようになります。
※コンパイル自体は `client.rs` などを作成した後で問題ありません。

`client.rs`の中身を以下に示します。

```rust:client.rs
// hello_server.proto 内のアイテムをモジュールとしてインポート
pub mod hello_server {
    tonic::include_proto!("hello_server");
}

// 上記モジュール内のアイテムを呼び出す
use hello_server::greeter_client::GreeterClient;
use hello_server::HelloRequest;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = GreeterClient::connect("http://[::1]:50051").await?;

    let request = tonic::Request::new(HelloRequest {
        name: "Tonic".into(),
    });

    let response = client.say_hello(request).await?;

    println!("RESPONSE={:?}", response);

    Ok(())
}
```

`server.rs` の中身を以下に示します。

```rust:server.rs
use tonic::{transport::Server, Request, Response, Status};

// hello_server.proto 内のアイテムをモジュールとしてインポート
pub mod hello_server {
    tonic::include_proto!("hello_server"); 
}

// 上記モジュール内のアイテムを呼び出す
use hello_server::greeter_server::{Greeter, GreeterServer};
use hello_server::{HelloReply, HelloRequest};

#[derive(Debug, Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
    // Return an instance of type HelloReply
    async fn say_hello(
        &self,
        // Accept request of type HelloRequest
        request: Request<HelloRequest>, 
    ) -> Result<Response<HelloReply>, Status> { 
        println!("Got a request: {:?}", request);

        let reply = HelloReply {
            // We must use .into_inner() as the fields of gRPC requests and responses are private
            message: format!("Hello {}!", request.into_inner().name).into(), 
        };

        // Send back our formatted greeting
        Ok(Response::new(reply)) 
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse().unwrap();
    let greeter = MyGreeter::default();

    println!("GreeterServer listening on {}", addr);

    Server::builder()
        .add_service(GreeterServer::new(greeter))
        .serve(addr)
        .await?;

    Ok(())
}
```

### テスト方法
1. `grpc_hello_server/` 内で `cargo run --bin grpc-hello-server` を実行。
2. 別のコマンドプロンプトで `cargo run --bin grpc-hello-client` を実行。

## REST
REST API は Actix Web 単体でも実装可能です。[公式サイトの記事](https://actix.rs/docs/getting-started/)を参考にしました。

### ファイル構成など
ファイル構成は次のようになります。

```
rest_hello_server/
    src/
        client.rs
        server.rs
    Cargo.toml
```

`Cargo.toml` の中身を以下に示します。依存クレートは `actix-web`、`actix-http`、`awc` です。

```rust:Cargo.toml
[package]
name = "rest-hello-server"
version = "0.1.0"
authors = ["XXXX"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "3.3.2"
actix-http = "^2.2.0"
awc = "^2.0.3"

[[bin]]
name = "rest-hello-server"
path = "src/server.rs"

[[bin]]
name = "rest-hello-client"
path = "src/client.rs"
```

`client.rs` の中身を以下に示します。

```rust:client.rs
use actix_http::Error;

#[actix_web::main]
async fn main() -> Result<(), Error> {
    let client = awc::Client::new();
    // Create request builder, configure request and send
    let addr: &str = "http://localhost:8080/";
    let mut response = client
        .get(addr)
        .send()
        .await?;

    // server http response
    println!("Response: {:?}", response);

    // read response body
    let body = response.body().await?;
    println!("Downloaded: {:?} bytes, {:?}", body.len(), body);

    // Get a response from the second endpoint
    let addr: &str = "http://localhost:8080/hey";
    let mut response = client
        .get(addr)
        .send()
        .await?;

    // server http response
    println!("Response: {:?}", response);

    // read response body
    let body = response.body().await?;
    println!("Downloaded: {:?} bytes, {:?}", body.len(), body);

    Ok(())
}
```

`server.rs` の中身を以下に示します。

```rust:server.rs
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello world!")
}

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
    HttpResponse::Ok().body(req_body)
}

async fn manual_hello() -> impl Responder {
    HttpResponse::Ok().body("Hey there!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(echo)
            .route("/hey", web::get().to(manual_hello))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
```

### テスト方法
1. `rest_hello_server/` 内で `cargo run --bin rest-hello-server` を実行。
2. 別のコマンドプロンプトで `cargo run --bin rest-hello-client` を実行。

## GraphQL
[GraphQL の公式サイト](https://graphql.org/code/#rust)に、以下の GraphQL in Rust ライブラリが掲載されています。今回は juniper を利用しました。

* [graphql-rust/juniper](https://github.com/graphql-rust/juniper) - GraphQL server library for Rust
* [async-graphql/async-graphql](https://github.com/async-graphql/async-graphql) - A GraphQL server library implemented in Rust

実装にあたって、[actix/examples/juniper](https://github.com/actix/examples/tree/master/juniper)を参考にしました。

### ファイル構成など
ファイル構成は次のようになります。

```
graphql_server/
    src/
        scheme.rs
        server.rs
    Cargo.toml
```

`Cargo.toml` の中身を以下に示します。

```rust:Cargo.toml
[package]
name = "graphql-server"
version = "0.1.0"
authors = ["XXXX"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "3"
actix-cors = "0.4.0"
serde = "1.0.103"
serde_json = "1.0.44"
serde_derive = "1.0.103"
juniper = "0.14.2"

[[bin]]
name = "graphql-server"
path = "src/server.rs"
```

GraphQL ではやり取りするデータのスキームを定義します。今回は `scheme.rs` 内に以下のようにスキームを定義しました。

```rust:schema.rs
use juniper::FieldResult;
use juniper::RootNode;

use juniper::{GraphQLInputObject, GraphQLObject};

// オブジェクトの定義
#[derive(GraphQLObject)]
#[graphql(description = "Hello struct")]
struct Hello {
    id: String,
    message: String,
}

#[derive(GraphQLInputObject)]
#[graphql(description = "NewHello struct")]
struct NewHello {
    message: String,
}

// クエリの定義（Get用）
pub struct QueryRoot;

#[juniper::object]
impl QueryRoot {
    fn human(id: String) -> FieldResult<Hello> {
        Ok(Hello {
            id: "0".to_owned(),
            message: "Hello GraphQL!".to_owned(),
        })
    }
}

// ミューテーションの定義（Post用）
pub struct MutationRoot;

#[juniper::object]
impl MutationRoot {
    fn create_hello(new_hello: NewHello) -> FieldResult<Hello> {
        Ok(Hello {
            id: "1234".to_owned(),
            message: new_hello.message,
        })
    }
}

pub type Schema = RootNode<'static, QueryRoot, MutationRoot>;

pub fn create_schema() -> Schema {
    Schema::new(QueryRoot {}, MutationRoot {})
}
```

`server.rs` の中身を以下に示します。こちらはほぼ [actix/examples/juniper 内サンプルコード](https://github.com/actix/examples/blob/master/juniper/src/main.rs)のままです。

```rust:server.rs

use std::io;
use std::sync::Arc;

use actix_cors::Cors;
use actix_web::{web, App, Error, HttpResponse, HttpServer};
use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest;

mod schema;

use crate::schema::{create_schema, Schema};

async fn graphiql() -> HttpResponse {
    let html = graphiql_source("http://127.0.0.1:8080/graphql");
    HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(html)
}

async fn graphql(
    st: web::Data<Arc<Schema>>,
    data: web::Json<GraphQLRequest>,
) -> Result<HttpResponse, Error> {
    let user = web::block(move || {
        let res = data.execute(&st, &());
        Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?)
    })
    .await?;
    Ok(HttpResponse::Ok()
        .content_type("application/json")
        .body(user))
}

#[actix_web::main]
async fn main() -> io::Result<()> {
    // Create Juniper schema
    let schema = std::sync::Arc::new(create_schema());

    // Start http server
    HttpServer::new(move || {
        App::new()
            .data(schema.clone())
            .wrap(
                Cors::new()
                    .allowed_methods(vec!["POST", "GET"])
                    .supports_credentials()
                    .max_age(3600)
                    .finish(),
            )
            .service(web::resource("/graphql").route(web::post().to(graphql)))
            .service(web::resource("/graphiql").route(web::get().to(graphiql)))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
```

クライアントサーバーについて、GraphQL には client サーバーとして QraphiQL が実装されていますので、それを利用します。

### テスト方法
1. `graphql_server/` 内で `cargo run --bin graphql-server` を実行。
2. `http://127.0.0.1:8080/graphiql` にアクセスして GET/POST を行う。

## 終わりに
本記事では API サーバーを Rust で立てる例を紹介しました。ほかにもいろいろな機能（並列処理など）が実装されているので、それらを有効に活用すると、サーバー実装がより楽しく、効率的になると思われます。