(defprofile lagénorhynque
:id @lagenorhynque
:reading "/laʒenɔʁɛ̃k/"
:aliases ["カマイルカ" "🐬"]
:languages [Java Clojure Haskell Python
日本語 English français русский]
:interests [プログラミング 語学/言語学 数学
法/政治 財務/会計]
:job-roles [エンジニアリングマネージャー
ソフトウェアアーキテクト]
:motto "楽しく楽にクールにスマートに"
(仙台のイベント「タガヤス」ということで)
- 🐬は岐阜出身、千葉在住
- 2016年4月から株式会社オプト(本社: 東京・市ヶ谷)所属
- 2017年からオプトの「仙台ラボラトリ」メンバーと合同で仕事することが増えた
- 2018年春、2023年春には仙台に出張する機会も
記事: サービス間連携のためのGraphQL APIをClojureで開発している話
発表: JavaからScala、そしてClojureへ: 実務で活きる関数型プログラミング
at 【タガヤス その26】初心者歓迎!関数型プログラミングって何だろう?(2022-06-24)
-
Web APIのスキーマ駆動開発
-
現代の主要なWeb API方式
-
REST/GraphQL/gRPCのIDL
-
Web APIのサーバとクライアントの間の「契約」であるスキーマ(schema)を先行して定義し、それを起点に実装を進める開発スタイル
- スキーマはインターフェース定義言語(inteface definition/description language; IDL)で記述する
- 認識齟齬/不整合を避けて効率的に開発できる
- コード/ドキュメント生成などの応用もしやすい
-
a.k.a. スキーマファースト開発(schema-first development)
-
APIの構想/方式設計
-
APIスキーマの(初期)設計
- アウトプット: IDLファイル
-
[optional] プロジェクト/コードの自動生成
- アウトプット: プロジェクトテンプレート/スタブ/型定義コード
- 利用言語/ライブラリ/ツールに大きく依存する
-
APIサーバ実装/テスト ↺ APIスキーマの拡張/修正
REST, GraphQL, gRPC
REST | GraphQL | gRPC | |
---|---|---|---|
schema definition | OpenAPI Specification | GraphQL SDL | Protocol Buffers (IDL) |
data format | JSON (text), etc. | JSON (text), etc. | Protocol Buffer wire format (binary) |
notes |
|
|
|
- 本質: HTTPプロトコルに寄り添ったWeb APIの設計パターン
- 具体的な形式は設計者によって揺れが大きい: Richardson Maturity Model
- OpenAPI Specificationに基づいてYAML/JSON形式でスキーマを記述できる(de facto standard?)
- Swagger/OpenAPIの各種ツールでスキーマ編集/閲覧や動作確認、コード生成などができる
- e.g. Swagger Editor (ブラウザ版)
- Swagger/OpenAPIの各種ツールでスキーマ編集/閲覧や動作確認、コード生成などができる
- 本質: クライアントに自由度を与えるWebサービスのクエリ言語
- cf. RDBに対するSQL
- (単純なREST APIで発生しやすい)オーバーフェッチング/アンダーフェッチングを回避できる
- GraphQL Schema Definition Language (SDL)でスキーマを記述する
- クライアントはGraphQLのクエリ言語を用いて必要最小限のデータのみを選択的に取得できる
- 本質: HTTP/2ベースの効率的なRPC (remote procedure call)のためのフレームワーク
- マイクロサービスアーキテクチャのバックエンドサービス間連携で有力な選択肢
- Webフロントエンド向けのAPIとしても利用できる: grpc-web
- Protocol BuffersのIDLでスキーマを記述する
- protoc (Protocol Buffers compiler)でサーバ/クライアントのコード生成ができる
- Protocol Buffer wire format (バイナリ形式)でデータを送受信する
API style | IDL |
---|---|
REST | OpenAPI Specification (.yaml , .json ) |
GraphQL | GraphQL SDL (.graphql ) |
gRPC | Protocol Buffers (.proto ) |
※ サンプルコード: web-api-style-comparison
components:
schemas:
Book:
title: Book
type: object
properties:
id:
type: integer
description: 書籍ID
minimum: 0
readOnly: true
title:
type: string
description: 書名
# ...(以下略)...
components > schemas > Book
:Book
データのJSON仕様(cf. JSON Schema)
required:
- id
- title
- author
# ...(以下略)...
description: 書籍
example:
id: 1
title: Web APIの設計
author: '(著) Arnaud Lauret, (翻訳) 株式会社クイープ, (監
修) 株式会社クイープ'
publisher: 翔泳社
publication_date: '2020-08-26'
official_site_url: 'https://www.shoeisha.co.jp/book/
detail/9784798167015'
required
: 必須のプロパティexample
: データ例
paths:
/books:
get:
tags:
- book_catalog
summary: 書籍の一覧取得
operationId: get-books
description: 検索条件を満たす書籍をカタログから取得する。
paths > /books > get
: パス/books
に対するGETリクエスト
parameters:
- $ref: '#/components/parameters/limit'
- $ref: '#/components/parameters/page'
- schema:
type: string
minLength: 1
in: query
name: title
description: 書名 (部分一致)
- schema:
type: string
minLength: 1
in: query
# ...(以下略)...
parameters
: パラメータの仕様
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Book'
pagination:
$ref: '#/components/schemas/Pagination'
# ...(以下略)...
responses > '200'
: レスポンスステータスコード200の場合content > application/json
: レスポンスボディのJSON仕様
paths:
/books:
post:
tags:
- book_catalog
summary: 書籍の追加
operationId: post-books
description: 書籍をカタログに追加する。
paths > /books > post
: パス/books
に対するPOSTリクエスト
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
examples:
example-1:
value:
title: Web APIの設計
author: '(著) Arnaud Lauret, (翻訳) 株式会社
クイープ, (監修) 株式会社クイープ'
publisher: 翔泳社
publication_date: '2020-08-26'
# ...(以下略)...
description: 書籍
requestBody > content > application/json
: リクエストボディのJSON仕様
responses:
'201':
description: Created
headers:
Location:
schema:
type: string
format: uri-reference
example: /books/1
description: 追加された書籍へのURL
responses > '201'
: レスポンスステータスコード201の場合headers > Location
: レスポンスのLocationヘッダーの仕様
"""書籍"""
type Book {
"""書籍ID"""
id: ID!
"""書名"""
title: String!
"""著者"""
author: String!
"""出版社"""
publisher: String!
"""出版年月日"""
# ...(中略)...
"""レビューの集計結果"""
reviewSummary: ReviewSummary!
}
- Bookデータのオブジェクト型定義
T!
はNon-Null型(ただのT
はnullable型)
"""レビューの集計結果"""
type ReviewSummary {
"""平均ランク"""
averageRank: Float
"""リスト"""
reviews: [Review!]!
}
"""レビュー"""
type Review {
"""ランク (星の数1〜5)"""
rank: Int
"""コメント"""
comment: String
}
- ReviewSummary, Reviewデータのオブジェクト型定義
[T]
はList型([T!]
,[T]!
,[T!]!
もある)
type Query {
"""カタログの書籍の一覧取得"""
booksInCatalog(
"""書名 (部分一致)"""
title: String
"""著者 (部分一致)"""
author: String
"""出版社 (部分一致)"""
publisher: String
"""出版年月日 (始点)"""
publishedOnFrom: Date
"""出版年月日 (終点)"""
publishedOnTo: Date
): [Book!]!
}
- booksInCatalogクエリ(Query型フィールド)の定義
type Mutation {
"""カタログへの書籍の追加"""
addBookToCatalog(
"""追加内容"""
input: AddBookToCatalogInput!
): AddBookToCatalogPayload
}
- addBookToCatalogミューテーション(Mutation型フィールド)の定義
input AddBookToCatalogInput {
"""書名"""
title: String!
"""著者"""
author: String!
"""出版社"""
publisher: String!
"""出版年月日"""
# ...(以下略)...
}
type AddBookToCatalogPayload {
"""追加された書籍"""
book: Book!
}
- addBookToCatalogミューテーション用の入出力の型定義
- 入力の型は
input
、出力の型はtype
で定義する
- 入力の型は
// 書籍
message Book {
// 書籍ID
int32 id = 1;
// 書名
string title = 2;
// 著者
string author = 3;
// 出版社
string publisher = 4;
// 出版年月日
/* ...(中略)... */
// レビューの集計結果
ReviewSummary review_summary = 9;
}
- Bookデータのメッセージ型定義
=
の右辺のフィールド番号でフィールドが識別される(ユニークに保ち、再利用しない)
// レビューの集計結果
message ReviewSummary {
// 平均ランク
optional float average_rank = 1;
// リスト
repeated Review reviews = 2;
}
// レビュー
message Review {
// ランク (星の数1〜5)
optional int32 rank = 1;
// コメント
optional string comment = 2;
}
- ReviewSummary, Reviewデータのメッセージ型定義
optional
は省略可能フィールドrepeated
は0個以上の繰り返しフィールド
// 書籍管理サービス"Biblog"のgRPC API
service BiblogApi {
// 書籍を一覧取得する
rpc ListBooksInCatalog(ListBooksInCatalogRequest) returns (
ListBooksInCatalogResponse);
}
- BiblogApiサービスのListBooksInCatalogメソッドの定義
message ListBooksInCatalogRequest {
// 書名 (部分一致)
optional string title = 1;
// 著者 (部分一致)
optional string author = 2;
// 出版社 (部分一致)
optional string publisher = 3;
// 出版年月日 (始点)
/* ...(以下略)... */
}
message ListBooksInCatalogResponse {
// 書籍のリスト
repeated Book books = 1;
}
- ListBooksInCatalogメソッド用の入出力の型定義
// 書籍管理サービス"Biblog"のgRPC API
service BiblogApi {
// 書籍を追加する
rpc AddBookToCatalog(AddBookToCatalogRequest) returns (AddB
ookToCatalogResponse);
}
- BiblogApiサービスのAddBookToCatalogメソッドの定義
message AddBookToCatalogRequest {
// 書名
string title = 1;
// 著者
string author = 2;
// 出版社
string publisher = 3;
// 出版年月日
/* ...(以下略)... */
}
message AddBookToCatalogResponse {
// 追加された書籍
Book book = 1;
}
- AddBookToCatalogメソッド用の入出力の型定義
-
REST以外の方式も選択肢として検討しよう
-
スキーマ駆動開発を実践しよう
-
内部実装だけでなく設計について学ぼう
-
lagenorhynque/clj-rest-api: REST API (Clojure)
-
lagenorhynque/aqoursql: GraphQL API (Clojure)
-
lagenorhynque/route-guide: gRPC API (Clojure)