Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active February 5, 2024 03:03
Show Gist options
  • Save lagenorhynque/b4fe344233f5db72bd05b6daeabf66b7 to your computer and use it in GitHub Desktop.
Save lagenorhynque/b4fe344233f5db72bd05b6daeabf66b7 to your computer and use it in GitHub Desktop.
インターフェース定義言語から学ぶモダンなWeb API方式: REST, GraphQL, gRPC

インターフェース定義言語から学ぶモダンなWeb API方式

REST, GraphQL, gRPC


(defprofile lagénorhynque
  :id           @lagenorhynque
  :reading      "/laʒenɔʁɛ̃k/"
  :aliases      ["カマイルカ" "🐬"]
  :languages    [Java Clojure Haskell Python
                             日本語 English français русский]
  :interests    [プログラミング 語学/言語学 数学
                             法/政治 財務/会計]
  :job-roles    [エンジニアリングマネージャー
                             ソフトウェアアーキテクト]
  :motto        "楽しく楽にクールにスマートに"

twitter icon


私の仙台との接点

(仙台のイベント「タガヤス」ということで)

  • 🐬は岐阜出身、千葉在住
  • 2016年4月から株式会社オプト(本社: 東京・市ヶ谷)所属
  • 2017年からオプトの「仙台ラボラトリ」メンバーと合同で仕事することが増えた
  • 2018年春、2023年春には仙台に出張する機会も

記事: サービス間連携のためのGraphQL APIをClojureで開発している話

tech-magazine_clojure-graphql-api


発表: JavaからScala、そしてClojureへ: 実務で活きる関数型プログラミング

from-java-through-scala-to-clojure

at 【タガヤス その26】初心者歓迎!関数型プログラミングって何だろう?(2022-06-24)


Table of Contents

  1. Web APIのスキーマ駆動開発

  2. 現代の主要なWeb API方式

  3. REST/GraphQL/gRPCのIDL


1. Web APIのスキーマ駆動開発


スキーマ駆動開発(schema-driven development)

  • Web APIのサーバとクライアントの間の「契約」であるスキーマ(schema)を先行して定義し、それを起点に実装を進める開発スタイル

    • スキーマはインターフェース定義言語(inteface definition/description language; IDL)で記述する
    • 認識齟齬/不整合を避けて効率的に開発できる
    • コード/ドキュメント生成などの応用もしやすい
  • a.k.a. スキーマファースト開発(schema-first development)


スキーマ駆動開発(サーバサイド)の基本的な流れ

  1. APIの構想/方式設計

  2. APIスキーマの(初期)設計

    • アウトプット: IDLファイル
  3. [optional] プロジェクト/コードの自動生成

    • アウトプット: プロジェクトテンプレート/スタブ/型定義コード
    • 利用言語/ライブラリ/ツールに大きく依存する
  4. APIサーバ実装/テスト ↺ APIスキーマの拡張/修正


2. 現代の主要なWeb 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
  • query language

REST (representational state transfer)

  • 本質: HTTPプロトコルに寄り添ったWeb APIの設計パターン
  • OpenAPI Specificationに基づいてYAML/JSON形式でスキーマを記述できる(de facto standard?)
    • Swagger/OpenAPIの各種ツールでスキーマ編集/閲覧や動作確認、コード生成などができる

screenshot_insomnia-rest


GraphQL

  • 本質: クライアントに自由度を与えるWebサービスのクエリ言語
    • cf. RDBに対するSQL
    • (単純なREST APIで発生しやすい)オーバーフェッチング/アンダーフェッチングを回避できる
  • GraphQL Schema Definition Language (SDL)でスキーマを記述する
    • GraphiQLというブラウザIDEでAPIコールを試すことができる
  • クライアントはGraphQLのクエリ言語を用いて必要最小限のデータのみを選択的に取得できる

screenshot_insomnia-graphql


gRPC

  • 本質: HTTP/2ベースの効率的なRPC (remote procedure call)のためのフレームワーク
    • マイクロサービスアーキテクチャのバックエンドサービス間連携で有力な選択肢
    • Webフロントエンド向けのAPIとしても利用できる: grpc-web
  • Protocol BuffersのIDLでスキーマを記述する
    • protoc (Protocol Buffers compiler)でサーバ/クライアントのコード生成ができる
  • Protocol Buffer wire format (バイナリ形式)でデータを送受信する

screenshot_insomnia-grpc


3. REST/GraphQL/gRPCのIDL


インターフェース定義言語(IDL)の記述

API style IDL
REST OpenAPI Specification (.yaml, .json)
GraphQL GraphQL SDL (.graphql)
gRPC Protocol Buffers (.proto)

題材: 蔵書/読書管理サービス

  • cf. ブクログ, 読書メーター

  • 主な機能

    • カタログの書籍情報のCRUD

    • 本棚(蔵書)の書籍の読書状況/レビュー情報のCRUD

※ サンプルコード: web-api-style-comparison


[REST] データモデル(エンティティ)の定義例

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: データ例

[REST] 参照系操作の定義例

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仕様

[REST] 更新系操作の定義例

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ヘッダーの仕様

[GraphQL] データモデル(エンティティ)の定義例

"""書籍"""
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!]! もある)

[GraphQL] 参照系操作の定義例

type Query {
  """カタログの書籍の一覧取得"""
  booksInCatalog(
    """書名 (部分一致)"""
    title: String
    """著者 (部分一致)"""
    author: String
    """出版社 (部分一致)"""
    publisher: String
    """出版年月日 (始点)"""
    publishedOnFrom: Date
    """出版年月日 (終点)"""
    publishedOnTo: Date
  ): [Book!]!
}
  • booksInCatalogクエリ(Query型フィールド)の定義

[GraphQL] 更新系操作の定義例

type Mutation {
  """カタログへの書籍の追加"""
  addBookToCatalog(
    """追加内容"""
    input: AddBookToCatalogInput!
  ): AddBookToCatalogPayload
}
  • addBookToCatalogミューテーション(Mutation型フィールド)の定義

input AddBookToCatalogInput {
  """書名"""
  title: String!
  """著者"""
  author: String!
  """出版社"""
  publisher: String!
  """出版年月日"""
  # ...(以下略)...
}
type AddBookToCatalogPayload {
  """追加された書籍"""
  book: Book!
}
  • addBookToCatalogミューテーション用の入出力の型定義
    • 入力の型は input 、出力の型は type で定義する

[gRPC] データモデル(エンティティ)の定義例

// 書籍
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個以上の繰り返しフィールド

[gRPC] 参照系操作の定義例

// 書籍管理サービス"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メソッド用の入出力の型定義

[gRPC] 更新系操作の定義例

// 書籍管理サービス"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メソッド用の入出力の型定義

XaaS時代のWebサービスのインターフェース(API)

  • REST以外の方式も選択肢として検討しよう

  • スキーマ駆動開発を実践しよう

  • 内部実装だけでなく設計について学ぼう


Further Reading

公式ドキュメント


(REST) APIとその設計


GraphQL, gRPC


サンプルコード

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment