Protocol Buffers, GraphQL Schema, Swagger Specで始めるスキーマファースト開発入門

はじめに
KINTOテクノロジーズでKINTO FACTORYのリードエンジニアをしている中西 葵です。現在KINTO FACTORYプロジェクトでは今後の対応車種や商品の拡充、全国展開を見据えてシステムの見直しを行っており、システム開発もモダンな技術や開発フローを取り入れている先進的なプロジェクトです。
本記事ではKINTO FACTORYで取り組んでいるスキーマファースト開発について解説します。
スキーマファースト開発とは?
スキーマファイルを定義しコードジェネレーターを使用してコードを生成しAPIを開発する手法で以下のような課題を解決します。
- 結合してみたら型が違って動かない
- ドキュメントが古くてコードが正しい
- クライアントの実装が言語毎に重複
1. 結合してみたら型が違って動かない
フロントエンド、バックエンド、各マイクロサービス間、外部サービスなどとのインターフェースとしてスキーマを定義する為、データ構造の齟齬などが発生しにくくなります。
2. ドキュメントが古くてコードが正しい
ドキュメントの生成もジェネレーターを用いて出力することで運用が続くと発生しがちなドキュメントとコードの内容が乖離する状況も回避できます。
3. クライアントの実装が言語毎に重複
Webアプリ, モバイルアプリなどクライアントの開発言語が何であっても定義したスキーマファイルからコードを自動生成するため同一機能の別言語実装など開発工数のムダも防ぐことができます。
その他
チームに経験者がいない場合導入の壁が高いと感じる方も多いですが、他にも値のバリデーション、モックサーバー用コードの自動生成、git上でバージョン管理など、開発者にとっては良いこと尽くめの開発手法がスキーマファースト開発です。
KINTO FACTORYのシステム構成
KINTO FACTORYではマイクロサービスアーキテクチャを採用して以下の通り
- ブラウザからはGraphQL
- サードパーティシステムからはREST API
- 各マイクロサービス間はgRPC(Protocol Buffers)
のような構成で通信を行う設計になっています

定義言語(IDL)
一般的にそれぞれのAPI設計において以下のようなIDL(Interface Description Language)を用いて定義していきます。
| Interface | IDL | |
|---|---|---|
| GraphQL | GraphQL Schema | https://graphql.org/learn/schema/ | 
| REST API | Swagger Spec | https://swagger.io/specification/ | 
| gRPC | Protocol Buffers | https://developers.google.com/protocol-buffers | 
※複数の定義言語を学ぶことは学習コストも高く効率的ではありません
スキーマ変換ツール
それぞれのIDLは名称や型などを定義してコードを生成することが出来るのであればSchema間での相互変換も可能ではないか?と考えて調査を進めたのが以下の表になります。
| 変換前 \ 変換後 | GraphQL Schema | Swagger Spec | Protocol Buffers | 
|---|---|---|---|
| GraphQL Schema | - | ? | ? | 
| Swagger Spec | openapi-to-graphql | - | openapi2proto | 
| Protocol Buffers | go-proto-gql | protoc-gen-openapiv2 | - | 
- GraphQL Schemaをベースに変換するツールは情報が少ない
- Swagger Specをベースに変換するツールは長期間メンテされていない
- Protocol Buffersをベースに変換するツールは上記より選択肢や情報が多い
以上の調査結果よりProtocol Buffersで定義して他のSchemaに変換を行う選択をしました。
ソースファイル(.proto)
準備 1
https://github.com/googleapis/googleapis
からRest APIを定義する上で必要なファイルを取得
準備 2
https://github.com/danielvladco/go-proto-gql
からGraphQL Schemaを定義する上で必要なproto定義ファイルを取得
定義ファイル(example.proto)
※以下の定義ファイルはテックブログの記事を例に本稿用に作成したものです
syntax = "proto3";
package com.kinto_technologies.blog;
option go_package = "blog.kinto-technologies.com";
import "google/api/annotations.proto"; // 準備1で取得したファイルの読み込み
import "protobuf/graphql.proto"; // 準備2で取得したファイルの読み込み
// 記事
message Article {
  // タイトル
  string title = 1;
  // 著者
  string author = 2;
  // コンテンツ
  string content = 3;
}
// リクエスト
message Request {
  uint64 id = 1;
}
// 結果
message Result {
  uint64 id = 1;
}
// テックブログサービス
service TechBlog {
  // 記事投稿
  rpc PostArticle(Article) returns (Result) {
    option (google.api.http) = {
      post: "/post"
    };
    option (danielvladco.protobuf.graphql.rpc) = {
      type: MUTATION
    };
  }
  // 記事取得
  rpc GetArticle(Request) returns (Article) {
    option (google.api.http) = {
      get: "/get/{id}"
    };
    option (danielvladco.protobuf.graphql.rpc) = {
      type: QUERY
    };
  }
}
.proto -> .graphql への変換
go-proto-gqlのインストール
リポジトリをクローン
git clone https://github.com/danielvladco/go-proto-gql.git
cd go-proto-gql
Protoc pluginsをインストール
cd ./protoc-gen-gql
go install
.proto から .graphqlに変換
protoc --gql_out=paths=source_relative:. -I=. example.proto
出力ファイル(.graphql)
"""
 テックブログサービス
"""
directive @TechBlog on FIELD_DEFINITION
"""
 記事
"""
type Article {
	"""
	 タイトル
	"""
	title: String
	"""
	 著者
	"""
	author: String
	"""
	 コンテンツ
	"""
	content: String
}
"""
 記事
"""
input ArticleInput {
	"""
	 タイトル
	"""
	title: String
	"""
	 著者
	"""
	author: String
	"""
	 コンテンツ
	"""
	content: String
}
type Mutation {
	"""
	 記事投稿
	"""
	techBlogPostArticle(in: ArticleInput): Result
}
type Query {
	"""
	 記事取得
	"""
	techBlogGetArticle(in: RequestInput): Article
}
"""
 リクエスト
"""
input RequestInput {
	id: Int
}
"""
 結果
"""
type Result {
	id: Int
}
.proto -> .swagger.json への変換
protobufのインストール
brew install protobuf
protocol-gen-openapiv2のインストール
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
.proto から .swagger.json への変換
protoc -I . --openapiv2_out=allow_merge=true,merge_file_name=./example:. example.proto
出力ファイル(.swagger.json)
{
  "swagger": "2.0",
  "info": {
    "title": "example.proto",
    "version": "version not set"
  },
  "tags": [
    {
      "name": "TechBlog"
    }
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/get/{id}": {
      "get": {
        "summary": "記事取得",
        "operationId": "TechBlog_GetArticle",
        "responses": {
          "200": {
            "description": "A successful response.",
            "schema": {
              "$ref": "#/definitions/blogArticle"
            }
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "string",
            "format": "uint64"
          }
        ],
        "tags": [
          "TechBlog"
        ]
      }
    },
    "/post": {
      "post": {
        "summary": "記事投稿",
        "operationId": "TechBlog_PostArticle",
        "responses": {
          "200": {
            "description": "A successful response.",
            "schema": {
              "$ref": "#/definitions/blogResult"
            }
          },
          "default": {
            "description": "An unexpected error response.",
            "schema": {
              "$ref": "#/definitions/rpcStatus"
            }
          }
        },
        "parameters": [
          {
            "name": "title",
            "description": "タイトル",
            "in": "query",
            "required": false,
            "type": "string"
          },
          {
            "name": "author",
            "description": "著者",
            "in": "query",
            "required": false,
            "type": "string"
          },
          {
            "name": "content",
            "description": "コンテンツ",
            "in": "query",
            "required": false,
            "type": "string"
          }
        ],
        "tags": [
          "TechBlog"
        ]
      }
    }
  },
  "definitions": {
    "blogArticle": {
      "type": "object",
      "properties": {
        "title": {
          "type": "string",
          "title": "タイトル"
        },
        "author": {
          "type": "string",
          "title": "著者"
        },
        "content": {
          "type": "string",
          "title": "コンテンツ"
        }
      },
      "title": "記事"
    },
    "blogResult": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uint64"
        }
      },
      "title": "結果"
    },
    "protobufAny": {
      "type": "object",
      "properties": {
        "@type": {
          "type": "string"
        }
      },
      "additionalProperties": {}
    },
    "rpcStatus": {
      "type": "object",
      "properties": {
        "code": {
          "type": "integer",
          "format": "int32"
        },
        "message": {
          "type": "string"
        },
        "details": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/protobufAny"
          }
        }
      }
    }
  }
}
まとめ
本稿では、スキーマファースト開発の紹介と、複数のスキーマ定義を最小限に抑えて運用する方法としてスキーマ定義を変換するツールについて紹介しました。
複数の定義言語が入り乱れている状態を解消したい方、特にProtocol Buffersの定義からGraphQL Schemaへの変換、Swagger Specへの変換について検討している方の一助になれば幸いです。
ドキュメントの生成やバリデーション処理の自動生成、コードの自動生成などについては別の記事として公開したいと思います。
Follow us!
KINTOテクノロジーズのTwitterアカウントも運用開始しました。最新情報を発信していきますのでぜひフォローをお願いします
https://twitter.com/KintoTech_Dev
We are hiring!
KINTOテクノロジーズでは一緒にモビリティの未来を創る仲間を募集しています。カジュアル面談なども行っておりますのでご興味をお持ち頂けましたらぜひお気軽にご連絡ください。
関連記事 | Related Posts
We are hiring!
【KINTO FACTORYバックエンドエンジニア(リーダークラス)】FACTORY EC開発G/東京・大阪
KINTO FACTORYについて自動車のソフトウェア、ハードウェア両面でのアップグレードを行う新サービスです。トヨタ/レクサス/GRの車をお持ちのお客様にOTAやハードウェアアップデートを通してリフォーム、アップグレード、パーソナライズなどを提供し購入後にも進化続ける自動車を提供するモビリティ業界における先端のサービスを提供します。
【PjM】KINTO開発推進G/東京
KINTO開発部 KINTO開発推進グループについてKINTO開発推進グループでは、クルマのサブスクリプションサービスである『 KINTO ONE 』をはじめ、国内向けサービスのプロジェクト計画立案からリリース、運用保守に至るまでのプロジェクト管理を行っています。




