はじめに 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を定義する上で必要なファイルを取得 google/api/annotations.proto google/api/http.proto google/api/httpbody.proto 準備 2 https://github.com/danielvladco/go-proto-gql からGraphQL Schemaを定義する上で必要なproto定義ファイルを取得 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