仮想政府ニャルニアの歩み:2日目・OpenAPIとJsonSchemaってだいたい一緒じゃない?
諸行無常:API仕様はいつまでたっても完成しないことを言い表す言葉。
執務室にて
ここはニャルニア国の執務室である。一匹の猫が公共サービスを共通のAPIとして実装するべく動き出していた。ニャルニアは猫の国である。ニャルニアに公共サービスはない。公共サービスを新規実装するならITを使えと飼い主に教えられてきた。
ニャンマルハウトはニャルニア国のマイナンバーを定義した。
つぎは、新しくマイナンバーを手に入れるためのAPIを設計しようとしている。親友ししゃも丸と共に、今日もニャルニア国公共サービスAPIを作る。
設計は大事だ。ところで設計と仕様って使い分けが難しい言葉だよね。API設計はAPI仕様の策定で、API実装とは分離している。一方で、API実装の設計も別途必要である。
マイナンバー管理APIの仕様策定
ニャンマルハウト「では実際にマイナンバー管理APIの仕様を策定していく」
ししゃも丸「仕様ってなに?」
ニャンマルハウト「どんなAPIにするのか、っていうことを考えるんだ。実際に作ることは実装という」
ししゃも丸「ていうか、そもそもAPIってなに?」
ニャンマルハウト「ここでいうAPIというのは、WebAPIだな。インターネット上にあって、だれでもプログラムを使ってアクセスできるものだ。例えば、ニャルニア国の国籍を取得するスマートフォンアプリを作る時に、このAPIにアクセスするプログラムを書くことになる。APIを作っておけば、みんなが好きなプログラムでニャルニア国の公共サービスにアクセスできるというわけだ」
ししゃも丸「なるほど!(わかってない)」
ニャンマルハウト「GraphQLもブームだが、クライアントの実装コストが増大するのが悩ましいところだ。バックエンドをRESTful APIにしておいてGraphQLを間に挟むというアプローチもあるので、今回はおおむねRESTful APIという方針にしよう」
ししゃも丸「はーい!」
ニャンマルハウト「RESTful APIはリソース指向APIということなので、とりあつかうリソースが何なのか、という定義からスタートする。ししゃも丸、マイナンバー管理APIでとりあつかうリソース……つまり、情報の塊がどんなものかわかるかい」
ししゃも丸「そうだな。情報の塊か。やっぱり、マイナンバーじゃないか。マイナンバー管理ってことは、一人ひとりのマイナンバーを扱うんだろう?」
ニャンマルハウト「そうだな。マイナンバーがリソースになると思うかもしれない。けど、マイナンバーっていうのはあくまで猫に結びついた番号なんだ。管理されるリソースは……猫だ!」
ししゃも丸「なん……だと……」
ニャンマルハウト「実際のところ、ニャルニア国籍を取得するということが、マイナンバー管理APIへの猫の登録という形式を取るわけだな。したがって、とりあえずマイナンバー管理APIと呼んでいたが、これは実際には国籍管理APIなんだ。その中でマイナンバーを使っている」
ししゃも丸「じゃあ、ニャルニア国籍を取得したらマイナンバーがもらえるってことか。俺もマイナンバーほしい。48bitの乱数を眺めてみたいんだ」
ニャンマルハウト「もちろん実装まで終わったらししゃも丸に使ってもらいたい。けど1番は俺な」
整理:今回の範囲はあくまでニャルニア国籍を登録したり、変更したりする。国籍を取得すると、自動的にマイナンバーが与えられる。国籍を取得せずにマイナンバーをもらうことは、今の所できない。
ニャンマルハウト「国籍というとややこしいが、要するにアカウントってことになる。ニャルニア国アカウントを持っていると、公共サービスが利用できる。このアカウントは実際の猫一匹に結びついている。アカウントは一度作成したら削除することはできない……死んだ時は、停止されるだけだ。死後の手続きっていうのもいろいろあるからな」
ししゃも丸「かなしい〜」
ニャンマルハウト「生き物はいつか死ぬさ。実際のところ、ニャルニア国籍をもっていない猫でもニャルニア国の公共サービスを受けることができる、という場合もある。例えば、旅行でニャルニア国にきた猫も、ニャルニア国の公共交通機関を利用できるべきだろう。将来的には、こういった国籍と結びついていないマイナンバーも発行できるようにしていかないといけないかもしれないな」
日本の場合、こういうことにはならないはずです。そもそも公共交通機関の利用にマイナンバーは不要なので。ただ、ニャルニア国ではすべての公共サービスをAPIで実装しようとしているので、このようになるわけです。
ニャンマルハウト「とにかく最初はaccountだ。APIのエンドポイントはこんな感じになる」
POST accounts
GET accounts/{personal-identifier}
GET accounts/{personal-identifier}/records
POST accounts/{personal-identifier}/records/update
POST accounts/{personal-identifier}/records/close
POST accounts/{personal-identifier}/records/reopen
ニャンマルハウト「実際はこれだと色々問題があるが、一旦はこんな感じにしておこう。アカウント情報の更新や停止を POST にしているのは、アカウントリソースの性質を表現している」
エンドポイント:ざっくりいえばURL。ここにプログラムを使ってデータを投げたり、問い合わせたりすると、結果が返信される。
ニャンマルハウト「アカウントのデータ更新は自由に認められるべきではなく、また履歴=recordも保存されるべきだからだ。アカウントの情報変更履歴を作成する、停止履歴を作成する、という意味で、POSTを使っている」
ししゃも丸「難しいが、履歴書みたいなイメージ?」
ニャンマルハウト「まあそうだな。次にアカウント情報の内容を決めていく。……こんなところか?」
{
"name": "ししゃも丸",
"personal-identifier": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
"registered-at": "2020-06-01T12:00:00+0900",
"birthday": "2018-02-14",
"status": "active"
}
ししゃも丸「俺の情報だ! けど、マイナンバーがないね」
ニャンマルハウト「マイナンバーは実際にAPIが出来上がってから取得したいからな。とにかく、最低限の情報はこんな感じだろう」
ししゃも丸「年齢がないよ〜」
ニャンマルハウト「年齢は生年月日と現在時刻を比べて計算するから、データとして持っておくのは生年月日だけで大丈夫なんだよ」
ニャンマルハウト「つぎは履歴だな。account-recordは……こんな感じだろうか」
{
"recorded-at": "2020-06-01T12:00:00+0900",
"operation": "open"
}
ニャンマルハウト「これはもちろん最低限の情報で、実際にはいろいろと付け足す必要があるだろうな。それはおいおいやっていくことにしよう」
ニャンマルハウト「さてRESTful APIの仕様としてはおおざっぱにできてきたが、詳細な部分まで詰めたものを用意しておきたい。そこで、Open API をつかって API 仕様をきちんと定義しておくことにする」
openapi: 3.0.2
info:
title: Nyarnia API
paths:
"/accounts":
post:
summary: Register New Account
responses:
'200':
content:
application/json:
schema:
type: object
properties:
account:
"$ref": "#/components/schemas/Account"
"/accounts/{pid}":
get:
summary: Get Account
parameters:
- "$ref": "#/components/parameters/PersonalIdentifier"
responses:
'200':
content:
application/json:
schema:
type: object
properties:
account:
"$ref": "#/components/schemas/Account"
"/accounts/{pid}/records":
get:
summary: Get Account Records
parameters:
- "$ref": "#/components/parameters/PersonalIdentifier"
responses:
'200':
content:
application/json:
schema:
type: object
properties:
account:
"$ref": "#/components/schemas/Account"
records:
"$ref": "#/components/schemas/AccountRecord"
required:
- records
"/accounts/{pid}/records/{operation}":
post:
summary: Create Account Record
parameters:
- "$ref": "#/components/parameters/PersonalIdentifier"
- in: path
name: operation
schema:
type: string
enum:
- update
- close
- reopen
responses:
'201':
content:
application/json:
schema:
type: object
properties:
record:
"$ref": "#/components/schemas/AccountRecord"
required:
- record
components:
schemas:
AccountRecord:
type: object
properties:
recorded-at:
type: string
format: date-time
Account:
type: object
properties:
name:
type: string
birthday:
type: string
format: date
personal-identifier:
type: string
format: uuid
registered-at:
type: string
format: date-time
status:
type: string
enum:
- active
- in-review
- closed
- stopped
required:
- name
- birthday
- personal-identifier
- registered-at
- status
parameters:
PersonalIdentifier:
in: path
name: pid
schema:
type: string
format: uuid
ししゃも丸「長っ!」
ニャンマルハウト「たったの115行だよ」
ししゃも丸「ええ……」
HTTPについて
ニャンマルハウト「さて、ここまで当たり前のように説明をすっ飛ばしてきたが、そもそもWebAPIはどんな風にできているのか、という部分を解説していなかったな」
ししゃも丸「ええー、そういう解説いるの? あえて聞かなかったのに」
ニャンマルハウト「こらこら、俺を厄介なオタク猫みたいに扱うんじゃない」
ししゃも丸(渋い顔)
ニャンマルハウト「実際、WebAPIという言葉に正確な定義は与えられていない。一般的には、Webの技術をAPI実装に転用したもののことだ。Webの技術というと、ブラウザ(SafariとかGoogleChromeとか)でウェブサイトを閲覧する技術のことだな」
ししゃも丸「ブラウザは俺もよくつかってるよ。セクシーな女の子の動画を見てる。人間も好きだよね、猫の動画」
ニャンマルハウト「人間は猫が好きだからな。さて、このブラウザというのは大むね、URLを入力すると、そのURLの宛先から情報をダウンロードして、画面に表示するという機能を持ったソフトウェアだ」
ししゃも丸「ふむふむ。URLってさ、そもそもなんなの?」
ニャンマルハウト「あれは、データの住所だ。Webでアクセスできるデータにはほとんどすべて住所がある。その住所をURLと呼ぶんだ」
厳密にいうと住所ではないですが、たとえ話の例としてはそこそこ適切かと思います。
ニャンマルハウト「で、URLの冒頭に書いてある https:// だとか https:// だとかは、スキームと呼ぶ。ざっくりいえば通信のルールを書く場所だな。ftp:// とか git+ssh:// とか、実は http/https の他にも種類がある。ほとんどのURLが http/https からスタートするのは、ブラウザが原則的に http プロトコルという通信ルールを使っているからだ」
ししゃも丸「でもニャンマルハウト、俺のブラウザには http:// なんて表示されてないぜ」
ししゃも丸「こんな感じだ」
ニャンマルハウト「最近では、スキーマ部分を隠してしまうブラウザが多いからな。カーソルを当てたり、キーボードの矢印を打ったりすると、URLが全部表示されるぜ」
ししゃも丸「やってみる」
ししゃも丸「お、出た。たしかに https:// から始まってる」
ニャンマルハウト「この https というのは、セキュリティの高い http 通信という意味で、通信ルールそのものは http と同じだ。そして、HTTP というのは非常にシンプルな構成の通信ルールで、データの要求(HTTPリクエスト)と要求への返答(HTTPレスポンス)のペアだけしかない。問い合わせと応答の1ペアを1回の通信として取り扱うわけだ」
ししゃも丸「ボタンを押すと餌が出てくるってこと?」
ニャンマルハウト「そういうこと」
ニャンマルハウト「さて、このHTTPはURLをサーバーに送信し、サーバーがURLに対応したデータを返送するという流れになっている。普通の場合、データとしてHTMLドキュメントが返ってくる。これをブラウザがうまく表示して、見た目のいいウェブサイトを楽しむことができる」
ししゃも丸「HTMLっていうのはわからないけど、ウェブサイトはHTMLっていう種類のデータってことなんだな。猫はどういう種類のデータ?」
猫は液体じゃないっけ。
ニャンマルハウト「ただ、別にHTMLドキュメントを返すというルールはない。他のデータでもいい。画像や、ただのテキストや、圧縮ファイルでも大丈夫だ。そういうデータが返ってきた場合、ブラウザはデータをコンピューターにダウンロードしたり、あるいは表示できるデータなら表示したりする。HTMLという仕組みと、HTTPという仕組みは、それぞれ別のものなんだ」
ニャンマルハウト「なので、HTTPの返送データの形式を工夫すれば、HTTPを使ったプログラム向けのデータ通信を実現できる。こういう、ウェブサイトのための仕組みを活用してAPIを実装したものを、一般にはWebAPIと呼ぶわけだな」
ししゃも丸「専用に通信ルール作ったらだめなの?」
ニャンマルハウト「だめじゃないさ。ただ、そうすると考えることがたくさんあって大変だし、HTTP通信するためのプログラムっていうのはすでにたくさんある。ブラウザなんて使わなくても、たいていのプログラム言語はHTTP通信のためのライブラリが最初から用意されている。HTTP通信はルールが明確で、理解も比較的簡単だ。そういうわけで、HTTPを使ったほうが楽ちんなんだ」
ししゃも丸「なるほどなぁ。つまり、WebAPIっていうのはみんな使ってるよくあるやり方ってことか」
ニャンマルハウト「そういうことだ。実際、Twitterなんかは公開APIが用意されていて、これはHTTPの仕組みを利用したWebAPIとして実装されている」
まとめ
ししゃも丸「そういえばニャンマルハウト、OpenAPIの説明がないよ」
ニャンマルハウト「そういえばそうだな……。まあ、WebAPIの仕様を書くためのルールだ。Google や Microsoft という人間のグループが作ったプロジェクトだ。便利なので、調べてみると良い」
ししゃも丸「ほへー。人間は集団で活動するのが得意だね」
ししゃも丸「ところでタイトルにあったJsonSchemaってなに?」
ニャンマルハウト「…………」
ししゃも丸「JsonSchemaって……」
ニャンマルハウト「…………」
今回の作業はこのコミットにまとめられています。
https://github.com/niaeashes/nyarnia/commit/b851045561b00bfbb2d520a989abfee6d8951553
次回予告
前途多難なニャルニア国の公共サービス実装、次回はデータベースの設計を行っていきます。
ニャンマルハウト「MySQLが突然MariaDBになった時、俺は宇宙猫になった」
この記事が気に入ったらサポートをしてみませんか?