# Chapter 8 アプリケーション間の連携 ## アプリケーションとアプリケーションをつなぐ ## Web APIのしくみを理解する ### HTTP通信を使ったアプリケーション間の連携の4つの約束事 - HTTP通信はブラウザを使ってインターネット上の様々な情報にアクセスする仕組み。 - WebAPIはHTTP通信をアプリケーション間でデータをやり取りする手段に応用したもの。 - WebAPI普及前はアプリケーション間の連携はやっかいな技術課題だった。普及後は次のように解決された。 | 課題 | 解決 | | -------- | -------- | | 物理的にどう接続するか | インターネット(TCP/IPで)接続 | | 各システムが対応している通信規格の違い | HTTP通信でデータをやりとりする | | データ形式の違い | データ形式はJSONまたはXML | | 文字コードの違い | 文字コードはUTF-8 | - WebAPIを使ってアプリケーション間で行う処理は基本的に「データの取得」と「データの登録」。 - WebAPI利用側(クライアント)が要求(リクエスト)を贈り、WebAPI提供側(サーバ)が応答(レスポンス)を返す。 - WebAPIでアプリケーション間を連携するには要求と応答の約束事が必要。約束事は次の4つ。 - 要求の対象を指定する(URI) - 要求の種類を指定する(HTTPメソッド) - 処理の結果を伝える(HTTPステータスコード) - 応答内容の表現形式(JSONやXML) ### 要求の対象を指定する - 対象データを指定するための約束事がURI。Uniform Resource Identifier(統一資源識別子)。 - 対象となるデータをリソース(資源)と呼ぶ。 | 形式 | スキーム://ホスト名/リソースへのパス | | -------- | -------- | | 例 | https//api.example.com/books/1234 | | 意味 | HTTPSを使ってホストapi.example.comが持つ識別番号1234の書籍データ | - 使いやすくかつ修正や拡張も行いやすいWebAPIを設計するためには、リソースの識別方法の設計が重要。 ### 要求の種類を指定する - URIで指定したリソースに対して、どのような操作を要求するかを指定するのがHTTPメソッド。 | HTTPメソッド| 説明 | 成功時のHTTPステータスコード | | -------- | -------- | -------- | | GET | データを取得する | 200 OK | | POST | データを登録する | 201 Created | | PUT | データを登録する | 200 OK </br> 201 Created </br> 204 No Content | | DELETE | データを削除する | 200 OK </br> 201 Created </br> 204 No Content | #### データを取得するGET - 書籍情報を取得する例。 ``` # URI https//api.example.com/books/1234 # JSON形式のレスポンス { "id":"1234", "title":"本のタイトル", "author":"著者名", "description":"概要の説明" } ``` - 書籍の一覧を取得する例。 ``` # URI https//api.example.com/books ``` - 問い合わせの条件を指定した例。 - 「?」文字以降の部分をクエリパラメータと呼び、「&」でつなぐことが可能。 ``` # URI https//api.example.com/books?category="ソフトウェア設計"&within="1年" ``` - 対象のリソースを一意に指定する識別番号や、リソースのグループを指定するカテゴリー名はクエリパラメータではなく、URIのパスで表現すべき。 - 省略できない必須条件もURIで指定したほうが良い。 - クエリパラメータはオプショナルな絞り込み条件として使うのが基本。 #### データを登録するPOSTとPUT - POSTとPUTどちらも「登録」。 - POSTとPUTでは対象の指定方法が異なる。 | メソッド | 対象の指定方法 | 説明 | | -------- | -------- | -------- | | POST | books | 書籍データを登録し、識別番号を発行してもらう。 | | PUT | books/1234 | 識別番号1234の書籍データを登録する。 | - POSTは登録するデータ(リソース)の識別番号を、サーバ側で発行する。 - クライアント側は事前にどのような識別体型でデータが管理されるかを知らない。 - サーバ側は登録が成功したときにはステータスコード「201 Created」を返し、レスポンスのコンテントに発行した一意の識別情報(例えばbooks/1234)を含める。 - PUTはクライアント側で一意の識別情報(例えばbooks/1234)を指定する。 - 存在しない場合は新規作成し、ステータスコード「201 Created」を返す。 - 存在した場合は既存のデータを上書きし、ステータスコード「200 OK」または「204 No Content」を返す。 - データの登録はPOSTで行ったうべき。 - PUTでは、クライアントがサーバのリソースの識別体系を事前に知っている必要がある。 - これによりアプリケーションの独立性が低くなり、POSTに比べて結合が密になる。 - アプリケーションの独立性を高め、将来の修正や拡張の影響を小さくするためにPOSTでデータの登録をさせたほうが良い。 - PUTには返すべき応答内容の規定がなく、成功した場合に複数のステータスコードを選択できるため、応答内容をどうするかの決め事が必要になる。 - 決め事が多くなるとアプリケーション間の連携が密結合になり、APIの修正や拡張が難しくなる。 #### 更新の依頼もPOSTを使う - 更新は次のような方法でPOSTでも実現できる。 ``` POST https//api.example.com/books/1234/updates ``` - 実際にどう更新するかはサーバ側の責任。 - 上書き更新されるかもしれないし、新しい書籍データを履歴として追加されるかもしれない。 - 登録時の管理はサーバ側に任せることでアプリケーション感の独立性が高くなり、APIの修正や拡張がやりやすくなる。 - サーバ側にbooks/1234が存在するかどうかは、GETして確認できる。 - 存在すれば「200 OK」存在しなければ「400 Bad Request」がサーバから返ってくる。 - データの登録と相手の状態を同時に扱うPUTよりも、POSTによる登録とGETによる状態の取得を組み合わせることがアプリケーション間の依存度を下げる好ましい設計。 #### データを削除するDELETE - 相手のアプリケーション上のリソースを削除する方法としてDELETEメソッドがある。 | メソッド | 対象 | 説明 | | -------- | -------- | -------- | | DELETE | books/1234 | 識別番号1234のデータを削除。 | | DELETE | books | すべての書籍データを削除。 | - DELETEもPUTと同様、クライアントとサーバ間で様々な決め事が必要になるためアプリケーション間の依存性を強くする。 - 削除の実際のタイミング - 削除の妥当性のルール - 削除がうまく行かなかった場合の挙動 - これを避けるためにPOSTメソッドで削除を依頼する方法がある。 ``` POST https//api.example.com/books/1234/deletions ``` - この方法だと、削除をどう実行するかの判断や、どのようにデータの削除を実現するかはサーバ側の責任になる。 - クライアントは削除の「依頼」をするだけになるので、アプリケーション間の依存関係を小さくできる。 ### エラー時の約束事 - アプリケーション間で連携する場合、うまく行かなかったときの決め事も重要。 | コード | テキスト表現 | 説明 | | -------- | -------- | -------- | | 400 | Bad Request | 要求が不正 | | 403 | Forbidden | 要求は禁止されている | | 404 | Not Found | 要求されたリソースが見つからない | | 500 | Internal Server Error | エラーが起きたため要求を正しく処理できなかった | - ステータスコード400番台はクライアント側の問題。 - ステータスコード500番台はサーバ側の問題。基本的にクライアント側で対応できない。 - エラー時の約束事の例として、本の詳細情報を取得する`GET books/1234`を考えてみる。 ``` // ステータスコード 404 Not Found { "error":"書籍番号1234に該当する情報はありません" } // ステータスコード 500 Internal Server Error { "error":"書籍番号1234にの処理を正しく完了できませんでした" } ``` - 「500 Internal Server Error」はアプリケーション内部のエラーであることの表現。 - エラーの内容を詳細にすべきではない。 - クライアントにとって不要な情報になる。 - セキュリティ的に保護すべき内容を含めてしまうリスクがある。 - アプリケーション間の接続エラーはクライアント側で対応する。 - サーバ側では対応できる範囲が少ない。 - エラーメッセージは、クライアント側に返すものとサーバ側画ログに出力するべきものは異なる。 - クライアント側に返すメッセージには、クライアント側がどう対応するかを判断するための手がかりを提供する。 - サーバ側で出力ログは、エラーの原因調査をするための詳細な情報を載せる。 ## ドメインオブジェクトとWeb API ### データ形式とドメインオブジェクトを変換する際に起こる不一致 - ドメインモデルで設計した場合、WebAPIの主な役割はドメインオブジェクトとJSONなどのテキスト表現との変換。 - GET = ドメインオブジェクトをJSON形式に変換してレスポンスして返却する。 - POST = JSON形式やフォームパラメータで送られてきたテキストデータをリクエストとして受け取る。 - JSONとドメインオブジェクトを単純に変換するだけでは不都合な場合がある。 - データ構造の不一致 - 関心事の不一致 #### データ構造の不一致 - データ構造の不一致はデータ項目が多い場合に起きる問題。 - ドメインオブジェクトの関心事はロジックの整理。オブジェクトのネットワーク構造である情報の塊を構成。 - APIで使うデータ形式の関心事はデータ。できるだけ単純な構造の方が良い。 - ドメインオブジェクトの階層構造をそのままデータ構造と表現するのはあまり意味がない。 #### 関心事の不一致 - ドメインオブジェクトの持つ全ての情報が、APIを利用する側で必要だとは限らない。 - 同様に、ドメインオブジェクトが期待するデータ項目がすべてPOSTされるとも限らない。 - 要するにデフォルト値が必要になる。 - ドメインオブジェクトとAPIで扱うデータの関心事のズレが小さい場合は、メインオブジェクトとJSONの単純なマッピングでも済ませることはできる。 - ズレが大きい場合、変換用の中間オブジェクトを用意したほうが、コードをシンプルに保ちやすくなる。 ```go // ドメインジェクトからレスポンスオブジェクトを生成する type BookResponse struct{} func fromBook(Book) (BookResponse, error) { // BookオブジェクトからBookResponseを生成する // ファクトリメソッド } ``` - レスポンス用のBookResponseクラスを用意し、ドメインオブジェクトBookから生成する。 - 構造の違いや項目の違いをファクトリメソッドが吸収する。 ```go // リクエストオブジェクトからドメインオブジェクトを生成する type BookRequest {} func (BookRequest) toBook(){ // BookRequestからBookを生成する } ``` - POSTされたデータをBookRequestオブジェクトにマッピングし、toBookメソッドでドメインオブジェクトBookを生成する。 - BookRequestやBookResponseはプレゼンテーション層(クリーンアーキテクチャーのコントローラー)のビューとして定義する。 - この変換処理は必要な時のみやる。 - ドメインオブジェクトとAPIで扱うデータが単純にマッピングできるときは不要。 ### 導出結果か生データか - Web APIの設計で悩ましい問題が、元データのままでやり取りするのか、加工や計算の結果をやり取りするかの判断。 - マスタ項目のコードと名称 - 合計の計算 - 日付データの形式 #### マスタ項目のコードと名称 - 選択肢は基本的に3つある - コードのみ - コードから名称を取得できるAPIを別途用意する。 - マスタ情報をAPIを使って共有することが必要な場合はこの方法を選択する。 - 利用する側と提供する側が密に結合するため基本的には避けたい。 - 名称のみ - 名称の重複の可能性があるため、厳密さに難がある。 - 名称のみで十分であればこの方法が良い。 - 都道府県など。 - コードと名称の両方 - 名称の重複を考慮する場合の選択肢。 - コードから名前を取得するAPIは必要ない。 #### 計算ロジックの置き場所 - 明細データの合計や誕生日からの年齢の計算といった導出可能なデータをどうするかという問題。 - WebAPI側で導出結果を返せば、利用する側のコードがシンプルになる。 - 提供する側が利用する側の細かい要求に対応するために負担が増える。 - 基本は、合計計算のロジックをどちらのアプリケーションが管理すべきかで判断する。 - API提供側が計算に関するロジックを管理する場合、計算結果を返すAPIだけ提供する。 - 合計の計算ルールがAPIを利用する側のアプリケーションに依存する場合、API提供側は基礎データのみを返す。 - 単純な合計計算の場合は基本的にAPI提供側が計算して返す。 - 基本データを返す場合は、基本データの形式や内容変更が利用側に大きく影響する。 - 導出結果を返す場合は、基本データの変更があっても利用側への影響が少ない。 #### 日付データの形式 - 日付のデータ形式も悩ましい問題。 ``` // 日時の表示形式 2016-10-16T14:30:15+09:00 ``` - ISO 8601/RFC 3339/W3Cの日付形式として標準化された形式。 - ライブラリや実行環境によって、どのように解釈されるかばらつきがある。 - タイムゾーンが無視される問題が実際にある。 - 日付と時刻は別の項目として扱うべき。 - 日付だけであればタイムゾーンに関係しないデータとして扱える。 - 時刻も「14:30:15」というように記述する。 - 現地時間として解釈するか、標準時間として解釈するかは約束事として決めておく。 - ISO 8601形式は人間の関心事ではなくプログラミングの関心事。 - 人間にとって日常使っている表現に合わせるほうが、間違いが少なく、変更にも強い。 ###### tags: `ISBN-978-4-7741-9087-7`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up