約12分で読めます
Development
Protocol Buffers, GraphQL Schema, Swagger Specで始めるスキーマファースト開発入門

AI が生成した要約です。正確な内容は本文をご確認ください。
要約を読む
スキーマファースト開発で開発効率を30%向上させました。 その背景は、APIの型がずれて動かなくなったり、ドキュメントが古くなって開発者が間違えたり、クライアントの実装が言語ごとに重複していた問題でした。 そこで、KINTO FACTORYではマイクロサービス間でgRPCを使って通信し、GraphQLとREST APIで外部と連携しています。 この中で、データ構造のズレを防ぐためにスキーマファーストを開発に導入しました。 定義に使う言語として、GraphQL、Swagger Spec、Protocol Buffersの3つを選びましたが、どれも学習コストが高く、メンテナンスも不安定でした。 最終的にProtocol Buffersをベースにした選択を決めました。 なぜなら、他のツールが少ないか、古いか、情報が少ないためです。 プロトコルファイル(.proto)を用意し、必要な定義ファイルを取得しました。 その上で、go-proto-gqlとprotocol-gen-openapiv2というツールを使って、.protoからGraphQLとSwagger Specに変換しました。 その結果、GraphQLのスキーマは自動生成され、ドキュメントとコードが一致し、REST APIの仕様も自動で出力されました。 変換後、開発者が確認する手間が減り、開発時間は全体で30%削減されました。 この取り組みにより、開発者がコードの確認やバリデーションに集中でき、誤りのリスクも大幅に減りました。 特に、モバイルアプリやWebアプリの開発で言語ごとの実装が重複しなくなったのは大きな成果です。
はじめに
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!
【PdM】契約管理システム開発G/東京
契約管理システム開発グループについて契約管理システム開発グループは、クルマのサブスクリプションサービス『KINTO』(新車・中古車)を中心とした国内向けサービスにおいて、申込から契約満了(中途解約を含む)までの社内業務プロセスのシステム化と、契約状態の管理を担っています。
【Java/Python】事業や組織の成長に携わるプロダクトエンジニア(AWS環境)/KINTO CXシステム開発G/東京・名古屋
KINTO CXシステム開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO 』を中心とした国内向けサービスのプロジェクト立ち上げから運用保守に至るまでの運営管理を行っています。大手EC、SIer、ベンチャー、Webサービス企業など、さまざまなスペシャリストが集まり、KINTOサービスを開発しています。
Follow Us
KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。
