KINTO Tech Blog
Development

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

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

はじめに

KINTOテクノロジーズでKINTO FACTORYのリードエンジニアをしている中西 葵です。現在KINTO FACTORYプロジェクトでは今後の対応車種や商品の拡充、全国展開を見据えてシステムの見直しを行っており、システム開発もモダンな技術や開発フローを取り入れている先進的なプロジェクトです。

本記事ではKINTO FACTORYで取り組んでいるスキーマファースト開発について解説します。

スキーマファースト開発とは?

スキーマファイルを定義しコードジェネレーターを使用してコードを生成しAPIを開発する手法で以下のような課題を解決します。

  1. 結合してみたら型が違って動かない
  2. ドキュメントが古くてコードが正しい
  3. クライアントの実装が言語毎に重複

1. 結合してみたら型が違って動かない

フロントエンド、バックエンド、各マイクロサービス間、外部サービスなどとのインターフェースとしてスキーマを定義する為、データ構造の齟齬などが発生しにくくなります。

2. ドキュメントが古くてコードが正しい

ドキュメントの生成もジェネレーターを用いて出力することで運用が続くと発生しがちなドキュメントとコードの内容が乖離する状況も回避できます。

3. クライアントの実装が言語毎に重複

Webアプリ, モバイルアプリなどクライアントの開発言語が何であっても定義したスキーマファイルからコードを自動生成するため同一機能の別言語実装など開発工数のムダも防ぐことができます。

その他

チームに経験者がいない場合導入の壁が高いと感じる方も多いですが、他にも値のバリデーション、モックサーバー用コードの自動生成、git上でバージョン管理など、開発者にとっては良いこと尽くめの開発手法がスキーマファースト開発です。

KINTO FACTORYのシステム構成

KINTO FACTORYではマイクロサービスアーキテクチャを採用して以下の通り

  • ブラウザからはGraphQL
  • サードパーティシステムからはREST API
  • 各マイクロサービス間はgRPC(Protocol Buffers)

のような構成で通信を行う設計になっています

KINTO FACTORY SYSTEM DESIGN

定義言語(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を定義する上で必要なファイルを取得

  1. google/api/annotations.proto
  2. google/api/http.proto
  3. google/api/httpbody.proto

準備 2

https://github.com/danielvladco/go-proto-gql

からGraphQL Schemaを定義する上で必要なproto定義ファイルを取得

  1. protobuf/graphql.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テクノロジーズでは一緒にモビリティの未来を創る仲間を募集しています。カジュアル面談なども行っておりますのでご興味をお持ち頂けましたらぜひお気軽にご連絡ください。

https://www.kinto-technologies.com/#/recruit/job

Facebook

関連記事 | Related Posts

We are hiring!

【KINTO FACTORYフルスタックエンジニア】プロジェクト推進G/東京・大阪

KINTO FACTORYについて自動車のソフトウェア、ハードウェア両面でのアップグレードを行う新サービスです。トヨタ・レクサスの車をお持ちのお客様にOTAやハードウェアアップデートを通してリフォーム、アップグレード、パーソナライズなどを提供し購入後にも進化続ける自動車を提供するモビリティ業界における先端のサービスの開発となります。

【KINTO FACTORYバックエンドエンジニア】プロジェクト推進G/東京・大阪

KINTO FACTORYについて自動車のソフトウェア、ハードウェア両面でのアップグレードを行う新サービスです。トヨタ・レクサスの車をお持ちのお客様にOTAやハードウェアアップデートを通してリフォーム、アップグレード、パーソナライズなどを提供し購入後にも進化続ける自動車を提供するモビリティ業界における先端のサービスの開発となります。