JSON Schema から API 仕様と Go コードを自動で生成する — BOT エントリーの裏側 Part.1

こんにちは、Pairs事業部の kaneshin です。
先月、 BOT エントリー というエンジニアインターン向けのエントリーを公開していたため、今回はその裏側の技術部分をお話しようと思います。

BOT エントリーとは?

BOT エントリーでは、こちらが用意した Slack チームに参加していただき、そのチームの中で Slack API や Bot API を使った Bot を用意してもらい、ある要件を満たしてもらうエントリーとなっています。

その要件とは、#botentry というチャネルに一定時間が経過するとランダムな文字列を投稿するので、その文字列を認証用の鍵としてこちらの用意した API に Bot を経由してリクエストを送信してもらうというエントリー内容となっています。

実際は Bot を経由しなくても構わないのですが、トークンの有効期限が投稿されてから約1秒弱のため、手動で API リクエストを送信するには時間が足りないようにしています。

技術要件

BOT エントリーは Go 言語と Google Cloud Platform の App Engine を利用しています。今回は Go 言語部分で、更にメンテナンス可能なコードにするためにコード生成を部分的に活用しているので、今回は BOT エントリーのコード生成についてご紹介したいと思います。

生成的プログラミング

BOT エントリーに応募するために、冒頭で少し述べた通り Slack に Bot を在駐させて API リクエストを送信する必要があります。この API リクエストの部分について API 仕様書の生成とコードの生成を行っています。

Botentry gen 1

API 仕様の生成 — Markdown

prmd という Heroku が開発をしている JSON Schema を、リソース単位の JSON Hyper Schema で定義・検証ができるツールを使用して 、JSON Schema から Markdown 形式の API 仕様を生成しています。

Botentry gen 2

prmd

prmd の使い方を軽く説明します。prmd は gem からインストールすることが可能です。

$ gem install prmd

インストール後は JSON Hyper Schema を作成し、ファイルを結合してドキュメント (Markdown) 生成するだけとなります。

$ mkdir -p schemata
$ prmd init user > schemata/user.json
# メタデータ作成
$ cat <<EOF > meta.json
{
"description": "Hello world prmd API",
"id": "hello-prmd",
"links": [{
"href": "https://api.hello.com",
"rel": "self"
}],
"title": "Hello Prmd"
}
EOF
# JSON Hyper Schema を結合
$ prmd combine --meta meta.json schemata/ > schema.json
# 結合した JSON Schema を検証
$ prmd verify schema.json
# Markdown 生成
$ prmd doc schema.json > schema.md
このようにコマンド上でも生成は可能ですが、実際は Rakefile のタスクにすることが多いと思います。
require 'rubygems'
require 'rake'
require 'prmd/rake_tasks/combine'
require 'prmd/rake_tasks/verify'
require 'prmd/rake_tasks/doc'
require 'prmd/link'
namespace :schema do
Prmd::RakeTasks::Combine.new do |t|
t.options[:meta] = 'schema/meta.json'
t.paths << 'schema/schemata'
t.output_file = 'schema/api.json'
end
Prmd::RakeTasks::Verify.new do |t|
t.files << 'schema/api.json'
end
Prmd::RakeTasks::Doc.new do |t|
t.files = { 'schema/api.json' => 'schema/api.md' }
end
end
task default: ['schema:combine', 'schema:verify', 'schema:doc']
API 仕様の生成 - HTML
生成された Markdown ファイルから HTML ファイルを生成するために redcarpet を使用し、最終的に API仕様 のページを出力しています。
redcarpet
redcarpet も gem からインストールすることが可能です。
$ gem install redcarpet
redcarpet のインスタンスから Markdown を読み込んで HTML を生成します。redcarpet は Markdown のパースオプションが複数あるため、適切なものを設定しないとうまくパースされない可能性があります。
require 'redcarpet'
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true, fenced_code_blocks: true)
html = markdown.render(File.read('schema/api.md'))
以上により、 JSON Schema をメンテナンスさえしていれば、最新の API 仕様書を HTML として生成することができます。
ただ、問題として JSON Schema がメンテナンスされなくなった場合、API 仕様も同時にメンテナンスされなくなってしまいます。そうならないように JSON Schema からプロダクションコードを生成するようにして JSON Schema がメンテナンスされ続ける仕組みを作っています。
Go コードの生成
JSON Schema から Go のソースコードを生成するために、これまた Heroku が開発している interagent/schematic というツールを利用してコード生成を行っています。
ただし、interagent/schematic は内部でコード生成するテンプレートを保持しているために使い手側はテンプレートを定義することができません。BOT エントリーではそれを可能にするため、 interagent/schematic の実装を壊さずに改修をした  kaneshin/schematic を使用しています。
schematic
schematic を import しつつ、自分で定義した template ファイルをセットしたコマンドを作成します。詳しくは kaneshin/schematic-example が参考になると思います。
例えば、 botschematic という命名で schematic のコマンドツールを作成した場合、下記のように //go:generate を書いておけば適当なタイミングで go generate することも可能です。
package main
//go:generate schematic-example -o=example_gen.go ./schema/api.json
schematic を使いこなせれば Go で JSON Schema からコード生成するのは簡単になると思います。
おわりに
BOT エントリーではメンテナンスと運用コストをかけないようにするというところに焦点を当てていたので、コード生成や今回話しには上げませんでしたが、GCP を使った運用面の削減はかなり成功したと思っています。
実は BOT エントリーを最初の足掛かりにして、社内ではこれ以上にコード生成を行っているプロジェクトも存在しています。それについてはまた来月書こうかと思います。
prmd -> redcarpet -> schematic のサンプルコードを公開しているので、是非参考にしてみてください!
Like what you read? Give eureka_developers a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.