現場で役立つシステム設計の原則 ch8 アプリケーション間の連携

勉強メモ現場で役立つシステム設計の原則

出典: 

-

アプリケーションとアプリケーションをつなぐ

ほかのアプリケーションとの連携がアプリケーションの価値を高める

  • Amazonとかの事例
  • アプリケーション間の連携はビジネスチャンス

アプリケーションを連携する4つのやり方

  • 4つのやり方

    • ファイル転送
    • DB共有
    • Web API
    • 非同期メッセージング
  • 後ろのものほど…

    • 実現できる機能・非機能要件の範囲が広がる
    • 設計や運用は複雑になる
  • 本章ではWeb API (と一部非同期メッセージング)について説明

Web APIのしくみを理解する

HTTP通信を使ったアプリケーション間の連携の4つの約束事

技術課題 Web APIによる解決
物理的にどう接続する TCP/IP
通信規格 HTTP
データ形式 JSON, XML
文字コード UTF-8
  • 4つの約束事

    • 要求の対象(URI)
    • 要求の種類(HTTPメソッド)
    • 処理の結果(HTTPステータスコード)
    • 応答内容の表現形式(JSON, XML)

要求の対象を指定する

  • URI

要求の種類を指定する

  • 代表的なHTTPメソッド
HTTPメソッド 意味 HTTPステータスコード
GET 取得 200 OK
POST 登録 201 Created
PUT 登録 200 OK, 201 Created, 204 No Content
DELETE 削除 200 OK, 202 Accepted, 204 No Content

データを登録するPOSTとPUT

  • POSTもPUTも「登録」
  • どう違うの

    • POST

      • 例: POST /books
      • API提供側アプリケーション側がリソースの識別番号を発行
      • 【補】冪等でない
      • 発行した識別番号を201 Createdで返却する
    • PUT

      • 例: PUT /books/1234
      • 呼び出し側がリソースの識別番号(1234)を指定
      • 新規作成時: 201 Createdを返す
      • 更新時: 200 OK または 204 No Contentを返す
  • PUTよりもPOSTを使うべき

    • PUTの場合、識別番号の体系を呼び出し側が知っている必要がある

      • 【補】枝番なのかUUIDなのか、とか
      • これすなわち密結合
    • PUTのHTTPステータスコードは複数あり、決めごとが多い

      • これすなわち呼び出し側との密結合
    • 呼び出し側と提供側アプリケーションとが密結合だと、修正や拡張の影響が大きい

更新の依頼もPOSTを使う

  • こうじゃなくて
PUT /books/1234
  • こう
POST /books/1234/updates
  • 更新処理の実装を提供側アプリケーションに委ねる

    • 上書きするのか

      • 【補】冪等なふるまい
    • 履歴をぶら下げるのか

      • 【補】冪等でないふるまい
  • 呼び出し側は更新処理の実装を意識せず、「更新の依頼」を積むだけ

データを削除するDELETE

  • こうじゃなくて
DELETE /books/1234
  • こう
POST /books/1234/deletions
  • 下記のような呼び出し側との決めごとがいらなくなる

    • 削除の実際のタイミング
    • 削除の妥当性のルール
    • 削除がうまく行かなかった場合の挙動
  • 「削除の依頼」を積むだけ

エラー時の約束事

  • 4xx系と5xx系の話

    • 4xx系: 呼び出し側がなんとかするべき
    • 5xx系: 呼び出し側はどうしようもない
  • 5xx系エラーの出し方

    • 呼び出し側向けエラーには詳細の情報を含めない

      • 呼び出し側は詳細を受け取ってもどうしようもない
      • セキュリティ的に保護すべき内容
    • 提供側アプリケーションサーバ内のログには詳細の情報を含める

      • 原因調査の重要な手がかり

良いWeb APIとは何か

使いにくいWeb API 〜大は小を兼ねるのか?

  • One Size Fits AllなAPI

    • パラメータも応答のデータ内容も多い
    • 利用側の負担が多い
    • 提供側もつらい

      • パラメータを増やせば増やすほどif文ふえる
    • 修正や拡張が困難になり、ビジネスの発展を阻害する
  • なぜこういうAPIが生まれるのか

    • 連携相手のアプリケーションの理解不足
    • 初期に把握できていなかったニーズが明らかになるたびにパラメータを継ぎ足していく

アプリケーションを組み立てるための部品を提供する

  • Web APIってそもそもどういう意味?

    • 呼び出し側でアプリケーションを組み立てるための部品のセット

      • Yes, that’s API
    • 利用する側でプログラミングせずに利用できる完成品

      • むしろ「サービス」
  • APIの粒度と組み立ての特性
APIの粒度 実現できる機能の多様性 組み立ての複雑さ
小さい 幅広い 複雑
大きい 限定的 単純
  • 組み立ての多様性を保ちつつ、組み立ての負担が増えすぎないようにしたい

    • 【補】GoFのFacade、PofEAAのRemote Facadeが思い浮かぶ

発展性に富んだAPI開発のやり方

単純なことをかんたんにできるAPIの提供から始める/動かしながら設計を発展させていく/APIを利用する側とAPIを提供する側の共同作業の環境を整える

  • 仕様書ファーストは今は昔

    • 時間かかる
    • しかも、いざ実装してみると仕様の不整合や考慮漏れが発覚しがち
  • 今日びは実際に作ってみるのが主流

    • 実際に作って使ってみることが簡単にできるようになった
  • Web API用のフレームワークとライブラリ

    • Spring MVCのRestController, Jackson
  • APIドキュメントを自動生成するツール

    • Swagger

      • 動かせる仕様書
  • コミュニケーションツールを活用する

    • チャットサービス等
    • 提供側と呼び出し側とで、互いのニーズや制約を適切に理解する
  • APIの成長サイクルを回す

    1. API原案の提示と意見交換
    2. 合意したAPIの開発
    3. テスト環境とドキュメントの自動生成
    4. フィードバック
    5. フィードバックをもとにした改良
    6. 改良結果の確認

中核となるAPIのセットを設計する

  • 登録と参照を分ける

    • POSTではリソース内容を返さず、リソースの識別番号のみ返す
  • リソースの単位を分ける

    • GET /members/1234/name とか
    • 【補】本章でPATCHメソッドが触れられていない理由が多分これ

      • 「リソースの一部を更新」ではなく、「より小さなリソースを丸ごと登録」

Web APIのバージョン管理

http://api.example.com/v2/members
  • ↑こういうやつ
  • Web API全体のバージョン管理を行うことに意味はない

    • 新しいAPIを生やすぶんにはバージョン管理不要
    • あるAPIの変更時は、個別に廃止の予告と実施を行う
  • 段階的な廃止手順の例

    1. 新APIを追加、互換性のため旧APIも残す
    2. 旧APIを303 See Otherを返すように変更

      • 新APIの情報を返す
    3. 旧APIを404 Not Foundを返すように変更
    4. 旧API自体を削除

APIを複合したサービスの提供

  • 部品としてのAPIだけでなく、まとまった機能をサービスとして提供する
  • 2層

    • 複合サービス

      • 【補】おそらく、Backend for Frontendってやつ
    • 基本API
  • 複合サービス層は、できれば呼び出し側が開発すべき

    • 【所感】さもないと依存の向きがおかしい

ドメインオブジェクトとWeb API

データ形式とドメインオブジェクトを変換する際に起こる不一致

  • データ構造の不一致

    • ドメインオブジェクトはロジックの整理のために階層構造になっている
    • APIで扱うデータ形式はデータだけが関心事なので、同じ構造は必ずしも適さない
  • 関心事の不一致

    • ドメインオブジェクトの全ての情報は要らない
    • ドメインオブジェクトが期待する情報の全てはPOSTしない
  • 不一致が大きい場合はマッピング層を設ける

    • プレゼンテーション層(HTTP層)の中に
    • 書籍ではRequest/Responseクラスに変換ロジックを積んでいる

      • Requestクラス: リクエスト->ドメインオブジェクト
      • Responseクラス: ドメインオブジェクト->レスポンス
  • 不一致が小さく、単純な変換で問題ないのなら、JSONとドメインオブジェクトの変換はFWに委ねるのがよい

導出結果か生データか

  • マスタ項目のコードと名称

    • コードのみAPI + コード->名称API

      • 呼び出し側と提供側とが密結合するので良くない
      • 【補】決めごとが多い、ということかな
    • 名称のみAPI

      • 重複しないなら可

        • 都道府県とか
    • コードと名称API

      • 名称が重複するならこれ
  • 計算ロジック

    • 内容による
    • 利用側アプリケーション依存のロジックならば生データ

      • API提供側に利用側ロジックを持ち込まない
  • 日付データの形式

    • できるだけシンプルに、かつあいまいでないように
    • ISO 8601/RFC 3339/W3C標準は、ライブラリの対応状況に鑑みると不確実

      • とくにTZ
    • 人間にとっての通常の表現に合わせるとよい

      • TZはAPIの決めごととする
      • 間違いが少なく、変更に強い

複雑な連携に取り組む

共通部分と個別対応部分を明確にする

  • 3グループに分ける

    • 基本API

      • コア
    • 拡張API

      • 複合サービス的なやつ
      • 全利用者向け
    • 個別対応API

      • 特定利用者向け

APIを進化させる

  • 3つのグルーピングを絶えず実状に合わせる
  • 共通APIで済むほうが呼び出し側・提供側ともに嬉しい

    • 呼び出し側:

      • 機能改善・信頼性向上頻度が高い
    • 提供側:

      • 開発・運用の負担減

小さなアプリケーションに分けて組み合わせる

  • マイクロサービス

    • 最初から机上の概念的な設計だけで分割すると失敗しやすい

      • マイクロサービスの分割のしかたを気軽に再構成できないため

        • 1つのアプリケーション内のクラスやメソッドのリファクタリングとは異なる部分
    • 1つのアプリケーション内で、境目がはっきりして設計が安定したところから切り出してしくとよい

複雑なデータの交換

  • JSONのウリはシンプルさ

    • 反面、ちょっとしたデータ形式の変更が思わぬ副作用を生んだりする
  • 複雑なデータではXMLを検討する

    • メタ情報や属性を使うことで表現の幅が広がる

非同期メッセージングを使ったアプリケーション間連携

  • 相手のアプリケーションの稼働状況から独立してメッセージを送ることができる

    • cf. Web APIの場合、相手の状態によりエラーや処理待ちが生じる
  • 共通の中間加工をやりやすい

    • アプリケーションの組み合わせごとに、アプリケーション上にデータ変換のしくみを作るのは大変
    • メッセージング基盤上にデータ変換を積むことができる

      • アプリケーションは変換を意識しなくて済む
  • 人間の仕事のやり方に合わせた処理を実現しやすい

    • Eメール送受信とそっくり
    • OOPよろしく、人間のメンタルモデルそのままなので、システムの構造がわかりやすくなり、修正や拡張がやりやすい