Share
Embed
Transcript
79 slides
-
Ⓒ2024 Dai Nippon Printing Co., Ltd. All Rights Reserved. 2024年10月27日
大日本印刷株式会社 情報イノベーション事業部 岩崎 清 外部システム連携先が10を超えるシステムでの アーキテクチャ設計・実装事例 -
2 自己紹介 ◼ 名前 : 岩崎 清 ◼ 所属 :
大日本印刷株式会社 情報イノベーション事業部 ICTセンター ICTDX本部 ◼ 経歴 ◦ 2000年から Java 利用開始 ◦ 2003年から Webアプリケーション開発に従事 ◦ 2013年から社内向け Webアプリケーションフレームワークの開発を開始 ◦ 現在はフレームワークの開発・保守、実案件でのアーキテクトに従事 -
4 概要 ◼ 外部システムの REST API を呼び出す外部接続モジュールの アーキテクチャ設計・実装の事例紹介 ◦ クラス設計等の実装よりのアーキテクチャ設計事例
◦ 外部システム連携先は10カ所を超える ◦ 結合テスト、システムテストでも利用するスタブも含むアーキテクチャ -
5 システム概要 スマホアプリ 管理画面 API サーバー 管理画面 サーバー 外部連携先 サーバー
外部連携先 サーバー 外部連携先 サーバー ・・・・・・ 連携先は10以上 連携APIは50以上 -
6 主な条件・制約 ◼ 外部連携システムは10カ所以上あり、仕様もまちまちだが、開発期 間も限られているため、効率的な開発が求められる ◼ 外部連携システムとは、外部結合テスト工程まで接続できないため、 実装段階で品質を確保する必要がある ◼ スタブは、複数のAPI呼び出しを連携させる仕組みが必要となる
(スタブの制御が複雑) ◼ システムテスト工程では、テスター(≠開発者)がスタブを利用する必 要がある -
7 主な要件 ◼ 通信設定(タイムアウト、リトライ等)はデプロイなしに変更可能 ◼ 複数サーバーをまたがって同時実行数のキュー制御が必要 (設定はデプロイなしに変更可能) ◼ 特定のレスポンス項目からエラーマッピングし、設定に応じて発報 (設定はデプロイなしに変更可能)
◼ リクエスト、レスポンス(ボディー部分も)を管理画面から検索可能 -
8 外部接続モジュールの要件:通信設定、キュー設定 ◼ 通信設定、キュー設定のリロード ◦ 再起動なしに一定間隔で設定をリロード可能にする 外部連携先 接続 タイムアウト リード
タイムアウト リトライ 回数 … X 10,000 10,000 2 … Y 5,000 10,000 3 … …… …… …… …… … 外部連携先 同時実行数 サイズ 最大待機時間 … X 5 10 10,000 … Y 10 15 5,000 … …… …… …… …… … キュー設定 通信設定 外部接続 モジュールX 外部接続 モジュールY -
9 外部接続モジュールの要件:同時実行数のキュー制御 実行中 最大待機数 最大実行数 API サーバー 外部連携先 サーバー API
サーバー API サーバー • 最大実行数以下は即時実行可能 • 実行終了時にキューから削除 • システム全体で同時実行数の制御が必要 接続要求 -
10 外部接続モジュールの要件:同時実行数のキュー制御 実行中 実行中 実行待ち 最大待機数 最大実行数 API サーバー 外部連携先
サーバー API サーバー API サーバー • 最大実行数を超えると待機 • 待機中に最大待機時間を超えると、 待機を中断してエラー 接続要求 -
11 外部接続モジュールの要件:同時実行数のキュー制御 実行中 実行中 実行待ち 実行待ち 最大待機数 最大実行数 API サーバー
外部連携先 サーバー API サーバー API サーバー • 最大待機数を超えると即時エラー 接続要求 実行待ち -
外部 連携先 API種別 外部API 種別 HTTP ステータス 条件1 … 条件N
エラー コード ログ レベル X - - 200 000 … - ー INFO X API_001 API_X01 400 001 … .*error.* E0101 ERROR X API_002 - 400 - … - E0200 WARN X - API_X03 - 102 … - E0304 WORN X - - - - … - E9999 ERROR …… …… …… …… …… … …… …… …… 12 外部接続モジュールの要件:エラーマッピング ◼ エラーマッピング表によるエラーコード変換 ◦ 以下のような表に従い、レスポンスからエラーコードに変換する ◦ 設定は再起動なしにリロード可能にする -
13 外部接続モジュールの要件:エラーマッピング 外部 連携先 API種別 外部API 種別 HTTP ステータス 条件1
… 条件N エラー コード ログ レベル X - - 200 000 … - ー INFO X API_001 API_X01 400 001 … .*error.* E0101 ERROR X API_002 - 400 - … - E0200 WARN X - API_X03 - 102 … - E0304 WORN X - - - - … - E9999 ERROR …… …… …… …… …… … …… …… …… • 共通のマッピング条件 • 呼び出し元のAPI、外部API、レスポンスの HTTPステータスにマッピングする -
14 外部接続モジュールの要件:エラーマッピング 外部 連携先 API種別 外部API 種別 HTTP ステータス 条件1
… 条件N エラー コード ログ レベル X - - 200 000 … - ー INFO X API_001 API_X01 400 001 … .*error.* E0101 ERROR X API_002 - 400 - … - E0200 WARN X - API_X03 - 102 … - E0304 WORN X - - - - … - E9999 ERROR …… …… …… …… …… … …… …… …… • 外部連携先ごとのマッピング条件 • レスポンスの項目と各条件Nをひもづける • 項目と条件のマッピングは外部連携先ごとに 個別に定義する { "result" : "001", "message" : "xxx error.", …… } レスポンス例 -
15 外部接続モジュールの要件:エラーマッピング 外部 連携先 API種別 外部API 種別 HTTP ステータス 条件1
… 条件N エラー コード ログ レベル X - - 200 000 … - ー INFO X API_001 API_X01 400 001 … .*error.* E0101 ERROR X API_002 - 400 - … - E0200 WARN X - API_X03 - 102 … - E0304 WORN X - - - - … - E9999 ERROR …… …… …… …… …… … …… …… …… • 各条件に対するエラーコードとログレベル • ERROR は発報対象 • エラーコードの “ー” は成功を表す • 成功の条件 • 必ず条件なしの 行を用意する -
スマホアプリ API サーバー 外部連携先 サーバー 管理画面 参照可能 17 外部接続モジュールの要件:リクエスト・レスポンスの記録 ◼
スマホアプリのリクエストにひもづけて、リクエスト・レスポンスを記録 ◦ 管理画面から検索・参照可能 【余談】 • 性能面を危惧してAPI単位でオフする機能を用意したが、 性能テストで問題になることはなかった • 逆に外部結合テスト、システムテストでのメリットが非常 に大きかった • テストチームが自ら調査して迅速に問題解決でき、開発 チームへの問合せは極端に少なかった -
21 ロジックと外部接続モジュールの責務の分離 ◼ 方法1)モジュールを、ドメイン等から完全に独立した再利用可能なモ ジュールにする ◦ モジュールの独立性は高く、他の案件でも再利用可能 ◦ モジュール単体での保守性も高い ◦
全体としての実装コストは高くなる • 連携先ごとの設定(APIキー、パスフレーズ等)や、通信設定は、外部から与える か、専用のプロパティファイルを用意する • 通信設定はデータベースに保存しており、リロードする必要があるが、モジュール の利用者側で管理するか、コールバックを用意する必要がある。 • エラーコードへの依存もできないため、レスポンスからのエラーマッピングも利用 者側に実装する必要がある • リクエスト・レスポンスをデータベースに記録する仕組みを入れづらい -
23 ロジックと外部接続モジュールの責務の分離 ◼ 方法2)モジュールインターフェイスの、ドメインへの依存を許可し、利 用者側が簡単に利用できるようにする ◦ 案件固有の業務ロジックも含めるため、利用者側は使いやすい • 例)会員を渡し、外部接続モジュール側で会員からトークンを取得して通信する ◦
全体としての実装コストは最小化できる • 初期化や設定値のリロードもモジュール内で実装しやすい。 • エラーマッピングもモジュール内で実施可能 • リクエスト・レスポンスのデータベースへの記録も実装可能 ◦ モジュールの独立性、再利用性、保守性はかなり低い • ドメインの修正がモジュールに影響する -
25 ロジックと外部接続モジュールの責務の分離 ◼ 方法3)モジュールインターフェイスの、業務に依存する ドメインへの依存は許可しない ◦ 方法1と方法2の中間 ◦ 会員等の業務に直結するドメインへの依存は許可しないが、通信設定、エ ラーコード等の、システムよりのドメインへの依存は許可する
◦ 全体としての実装コストは、方法1>方法3>方法2 • 設定のリロード、エラーマッピング、リクエスト・レスポンスの記録等を、モジュール 側の共通実装にできる ◦ モジュールの独立性、再利用性、保守性は、方法1<方法3<方法2 • ただし、システムよりのドメインは安定しているので影響は受けづらい 採用 -
27 実モジュールとスタブモジュール ◼ 実装時(単体テスト時)にスタブモジュールを利用する形だと、 実モジュールのテストが不十分 ◦ 外部結合テストまで、外部連携先と接続できないこともあり、極力単体テスト 時にコードを動作させておきたい。 実モジュール スタブモジュール
外部接続モジュール 単体テストで実モジュールを 動作させることができない -
28 実モジュールとスタブモジュール ◼ 実装時(単体テスト時)にスタブモジュールを利用する形だと、 実モジュールのテストが不十分 ◦ 外部結合テストまで、外部連携先と接続できないこともあり、極力単体テスト 時にコードを動作させておきたい。 ◼ 単体テスト時の未実行コードを減らすために
◦ 対策1)外部接続モジュールの通信部分をさらにインターフェイス化して分離 ◦ 対策2)RestTemplate のインターセプタを利用 -
29 対策1:外部接続モジュールの分離 ◼ 通信を行う部分をさらにインターフェイス化して分離 ◦ (以降、前段を Client、後段を Connector と表記する) ◦
RestTemplate を利用した実際の通信処理は Connector 側で行う ◦ それ以外の処理は Client 側で行う Client 側のコードは単体テスト時も実行可能になる -
30 Client の責務 ◼ Client の主な責務 ◦ リクエストの準備と Connector の呼び出し
◦ (外部連携先により)同時実行数のキュー制御 ◦ (外部連携先により)特定の条件(流量制限エラー等)でのリトライ ◦ リクエスト・レスポンスの記録 -
31 Connector の責務 ◼ Connector の主な責務 ◦ 通信設定、エラーマッピングの読み込みおよびリロード ◦ RestTemplate
を利用した外部連携APIの実行 ◦ タイムアウトによるリトライ ◦ レスポンスからのエラーマッピング -
32 対策2:RestTemplate のインターセプタの利用 ◼ ConnectorImpl 利用時の挙動 ◦ RestTemplate 経由で外部連携先サーバーと通信を行う XxxClientImpl
外部連携先 サーバー XxxConnector Impl RestTemplate -
33 対策2:RestTemplate のインターセプタの利用 ◼ ConnectorStubImpl は ConnectorImpl を継承し以下の処理を行う ◦ RestTemplate
のインターセプタを設定する ◦ スタブ固有の処理を行う ◼ 単体テストで ConnectorImpl のほぼすべてのコードが実行可能 -
34 対策2:RestTemplate のインターセプタの利用 ◼ ConnectorStubImpl 利用時(単体テスト時)の挙動 ◦ RestTemplate のインターセプタを設定し、実際に通信を行う直前でスタブの 処理を行い、返却したいレスポンスのバイト列を返す
⇒ MessageConverter でレスポンスのデシリアライズ処理も実行される XxxClientImpl 外部連携先 サーバー XxxConnector Impl RestTemplate ※RestTemplate の インターセプタ XxxConnector StubImpl 実際に通信は行わず、 レスポンスのバイト列 を返す -
35 外部接続モジュール間の処理の共通化 ◼ 外部接続モジュール間の処理を共通化し、個別実装を減らし、 開発効率化、品質向上を実現したい ◼ 共通化の方法 ◦ コンポーネント化 ◦
共通処理の基底クラス化(継承、テンプレートメソッドパターン) ◦ 委譲(デコレータパターン、プロキシパターン) -
36 外部接続モジュール間の処理の共通化 ◼ 継承と委譲 ◦ 一般に設計としては、継承よりも委譲の方がよいケースが多い • 継承はカプセル化を破壊する ◦ 継承先も含めて設計・実装できる(継承元を修正することができる)ケースで
は、継承の方が効率的に開発できるケースが多い ◦ 案件開発においては、継承の方が構造が複雑化せず、効率的に開発できる ケースも多い -
38 Client の基底クラスとサブクラスの責務 ◼ サブクラス(XxxClientImpl) ◦ Connector に渡すリクエストの準備 ◦ Connector
の呼び出し ◼ 基底クラス(BaseClient) ◦ リクエスト・レスポンスのデータベースへの保存 -
39 XxxClientImpl の実装イメージ public class XxxClientImpl extends BaseExtComClient implements XxxClient
{ @Autowired private XxxConnector xxxConnector; @Override public SomeApiResult someApi(String memberId, String senderId) { SomeApiRequestImpl request = new SomeApiRequestImpl(); // request のセットアップ …… XxxConnectorResult<SomeApiResponseImpl> connectorResult = callConnector(() -> xxxConnector.someApi(request)); return new SomeApiResult(request, connectorResult); } …… 1.リクエストを用意 2.Connector を 呼び出す 3.結果を返却する -
40 Connector の基底クラスとサブクラスの責務 ◼ サブクラス(XxxConnectorImpl) ◦ RestTemplate に設定する MessageConverter の生成(初期化時)
◦ APIごとの RequestEntity の生成 ◼ 基底クラス(BaseConnector) ◦ RestTemplate の生成・キャッシュ ◦ 通信設定が更新された場合の RestTemplate の再作成 ◦ 実際の通信と、通信設定に応じたリードタイムアウト時の リトライ処理 ◦ レスポンスからのエラーマッピング -
41 XxxConnectorImpl の実装イメージ ◼ RestTemplate に設定する MessageConverter の生成(初期化用) public class
XxxConnectorImpl extends BaseExtComConnector implements XxxConnector { …… @Override protected List<HttpMessageConverter<?>> createMessageConverters( SyExtApiType syExtApiType) { List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); …… // JSON 用の MessageConverter を設定 MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper); messageConverters.add(converter); return messageConverters; } -
42 XxxConnectorImpl の実装イメージ ◼ 個々のAPIの実装は、RequestEntity を生成するラムダ等を渡し、 基底クラスのメソッドを呼び出す …… @Override public
XxxConnectorResult<SomeApiResponseImpl> someApi(SomeApiRequestImpl request) { SyExtApiType extApiType = SyExtApiType.XXX_001; return execute( extApiType, () -> createSomeApiRequest(request, XXX_001_PATH), SomeApiResponseImpl.class, createResultCreator(extApiType)); } …… -
43 XxxConnectorImpl の実装イメージ ◼ RequestEntity を生成するメソッドでは、RequestEntity を生成して、 リクエストとヘッダ等を設定して返す …… private
RequestEntity<MultiValueMap<String, String>> createSomeApiRequest(SomeApiRequestImpl request, String path) { // ヘッダ設定 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); return new RequestEntity<>(toMultiValueMap(request), headers, HttpMethod.POST, getUri(path)); } …… -
45 外部接続モジュール設計・実装のまとめ ◼ これまでの説明は出来上がったものに対する説明 ◦ 説明した内容を一つ一つ考えながら設計・実装しているわけではない ◼ 要件や制約、実現したいことを頭に入れて、全体的に設計する ◦ それぞれ絡み合う部分があり、個別には考えづらい
◼ 実際にはいくつかの外部接続仕様を確認し、どれかひとつを取り上 げ、実装しながら設計していく ◦ よりより設計・実装を行うにはトライアンドエラーは重要 -
47 スタブの設計要因:スタブ設定 ◼ スタブ設定:スタブが返却するレスポンスの設定 ◦ 返却するレスポンスのタイプ • 固定レスポンス • 自動レスポンス
• 指定レスポンス ◦ (指定レスポンスの場合)レスポンスの指定方法 • レスポンスの登録順 • 登録時に指定したキー -
48 スタブの設計要因:スタブ設定 ◼ レスポンスタイプ:固定レスポンス ◦ ファイル等で用意した固定のレスポンスを返す ◦ ファイルを差し替えることでレスポンスを変更する ◦ ほとんどレスポンスを変える必要がないケース等でしか利用できない
APIサーバー スタブ スマホアプリ 固定の レスポンス ※レスポンスの変更は ファイルを差し替える -
49 スタブの設計要因:スタブ設定 ◼ レスポンスタイプ:自動レスポンス ◦ 自動で正常系のレスポンスを返せるケース • 入力値や現在日時、乱数で生成したID等 ◦ 異常系は他の方法と併用する
◦ 対象のAPIが直接のテスト対象でない場合に有効 APIサーバー スタブ スマホアプリ 自動でレスポンスを 生成して返す -
50 スタブの設計要因:スタブ設定 ◼ レスポンスタイプ:指定レスポンス ◦ 事前に任意のレスポンスを複数登録しておき返却する (単体テスト時以外は、スタブ設定をDBやKVS等に保存する必要がある) ◦ 返却するレスポンスの指定方法 •
1. 登録順:レスポンスを登録した順に返す • 2. キー:登録時に指定したキーに対応するレスポンスを返す • 3. キー+登録順:1 と 2 の組合せ -
51 スタブの設計要因:スタブ設定 – レスポンスタイプ:指定レスポンス ◼ 指定方法:登録順 ◦ レスポンスを登録した順に返却する(FIFOキュー) ◦ 連続した同一APIの呼び出しで異なるレスポンスを返すことが可能だが、
複数人での同時テストは困難 APIサーバー スタブ スマホアプリ レスポンス1 レスポンス2 …… ・登録したレスポンスは上に積まれる ・スタブは下から利用していく -
52 スタブの設計要因:スタブ設定 – レスポンスタイプ:指定レスポンス ◼ 指定方法:キー ◦ APIの入力項目からキーを決める(※キー項目の詳細は後述) ◦ キーを指定してレスポンスを登録する
◦ 入力項目に該当するレスポンスを返却する ◦ 複数人で同時テスト可能だが、連続した同一APIの呼び出しで異なるレスポン スを返すことはできない APIサーバー スタブ スマホアプリ zzz ⇒ レスポンス3 yyy ⇒ レスポンス2 xxx ⇒ レスポンス1 xxx -
53 スタブの設計要因:スタブ設定 – レスポンスタイプ:指定レスポンス ◼ 指定方法:キー+登録順 ◦ 登録順とキーの組合せ ◦ キーごとにキューを持つ
◦ 連続した同一APIの呼び出しで異なるレスポンスを返すことも、 複数人での同時テストも可能 APIサーバー スタブ スマホアプリ キー:xxx レスポンスx1 レスポンスx2 …… キー:yyy レスポンスy1 レスポンスy2 …… キー:zzz レスポンスz1 レスポンスz2 …… xxx -
54 スタブの設計要因:折り返しスタブとスタブサーバー ◼ 折り返しスタブ ◦ 外部通信は行わず、 プロセス内で折り返す ◼ スタブサーバー ◦
実モジュールを利用 ◦ 実際に通信を行う ◦ 外部連携先相当の スタブサーバーを用意する APIサーバー ClientImpl ConnectorImpl ConnectorStubImpl スタブ サーバー APIサーバー ClientImpl ConnectorImpl ConnectorStubImpl -
56 スタブの各ユースケースと制約 ユースケース 設定の保存場所 複数設定の制御 設定手段 単体テスト • メモリも可 •
1テストずつの実行と なるため、登録順で も問題ない • プログラム (テストコード) 自動結合 テスト • メモリは不可 • DB, KVS等に 保存する必要 あり 手動テスト • 複数人でテストする ため、複数の設定か ら特定する必要あり • 手動 (設定ツールが 必要) 今回のターゲット -
57 スタブの要件 ◼ スタブ設定は「指定レスポンス」を「キー+登録順」で管理する ◦ 設定は KeyValueStore に保存する ◦ 単体テストでは不要だが、単体テストも同じ仕組みにする
◼ スタブ設定用管理画面を用意する ◦ テスター(≠開発者)が利用するため、GUIツールが必要 ◦ 専用のWebアプリを用意する ◼ 外部APIと同じ動作をするスタブサーバーも用意する ◦ 外部結合テスト前に、外部接続モジュールの通信テストを行えるようにする ◦ 性能テスト時に実際に通信する状態で計測できるようにする -
58 単体テスト実行時 ClientImpl ConnectorImpl ConnectorStubImpl Redis スタブ設定 データ 単体テスト 1.テスト準備で、スタブ設定を投入する
2.投入されたスタブ設定に従い レスポンスを返す -
APIサーバー 59 自動結合テスト実行時 ClientImpl ConnectorImpl ConnectorStubImpl Redis スタブ設定 データ 結合テスト
(WebDriver) 1.テスト準備で、スタブ設定を投入する 2.投入されたスタブ設定に従い レスポンスを返す -
スタブサーバー 60 手動テスト実行時 APIサーバー ClientImpl ConnectorImpl ConnectorStubImpl スマホアプリ Redis スタブ設定
データ 外部APIスタブ スタブ設定 管理画面 スタブ設定 1.テスト実行前に、スタブ設定を行う 2.スタブサーバーがスタブ設定に 従ってレスポンスを返す -
61 スタブ設定 ◼ 共通のスタブ設定項目 ◦ 特殊エラー • 接続タイムアウト、リードタイムアウト等 ※外部APIスタブは設定できない ◦
利用回数 • 指定した回数利用すると削除 • 無制限も指定可能 ◦ 遅延秒数 • リードタイムアウトを発生させることができる ◦ HTTPステータス • レスポンスのHTTPステータスコード -
62 スタブ設定のキー項目 ◼ スタブ設定のキーは外部APIのリクエスト項目でなくてはならない ◼ キー項目はスマホアプリからの入力値で制御可能でなくてはならい ◦ キー項目を直接指定できないが制御可能である例 • スタブ設定のキーはカードID
• スマホからのAPIにカードIDは含まれないが、会員IDが含まれる • サーバーで会員IDからカードIDを引いてリクエストに利用している スマホアプリ API サーバー 外部API スタブ リクエスト項目の中から スタブ設定のキーを設定する スタブ設定のキーは、入力値で 制御可能でなくてはならない -
APIサーバー 64 キー設定が難しい事例① ◼ 問題点:キー項目値を外部から参照できない スマホアプリ IDを採番 IDはDBに保存 外部API1 口座番号
口座番号, ID DBからIDを 取得 外部API2 ID ID IDは外部からは参照不可 外部API1のスタブキーは 口座番号を利用可能 スタブキーとして利用 できるのはIDのみ -
APIサーバー 65 キー設定が難しい事例① ◼ 解決策:スタブ側でキー項目へ変換するためのデータを管理する スマホアプリ IDを採番 IDはDBに保存 外部API1 口座番号
口座番号, ID DBからIDを 取得 外部API2 ID IDをキーに口座番号を保存 IDから口座番号を取得。 取得した口座番号をキー としてスタブ設定を取得 ID Redis スタブ設定 キー:口座番号 Redis ID⇒口座番号 -
APIサーバー 66 キー設定が難しい事例② ◼ 問題点:スタブ設定を行うタイミングがない スマホアプリ IDはDBに保存 外部API1 ID、口座番号 ID,
口座番号 DBからIDを 取得 外部API2 ID 外部API1のスタブキーは 口座番号を利用可能 ID IDは管理画面から参照可能 二つのAPI呼び出しは連続 して行われるため、スタブ設 定を行うタイミングがない IDはスマホ内で採番 スタブキーとして利用 できるのはIDのみ -
67 キー設定が難しい事例② ◼ 解決策:テスト用にスマホアプリ側に一時停止機能を追加する APIサーバー スマホアプリ IDはDBに保存 外部API1 ID、口座番号 ID,
口座番号 DBからIDを 取得 外部API2 ID ID 二つのAPI呼び出しの間 に、テスト用の確認ダイア ログを表示し、一時停止 できるようにして対応 -
68 スタブ設計・実装のまとめ ◼ スタブのユースケースによって求められる機能も変わる ◦ 単体テスト<自動結合テスト<手動テストの順で高機能なものが求められる ◦ どこまでの機能が必要か見極め、早めに対応した方がよい • 今回の仕組みは、外部連携モジュール自体の設計時から一緒に検討していた
◼ スタブ設定のキー項目をうまく見つける ◦ キー項目は「外部APIのリクエスト項目」かつ「APIの入力値で制御できるも の」でなければならない ◦ 直接キー項目になるものがない場合も、合わせ技で制御できる場合もある (事例参照) -
70 仕様書に未記載の JSON プロパティ ◼ 仕様書に未記載の JSON プロパティが返却され例外が発生した ◦ Jackson
の ObjectMapper のデフォルト動作では、マッピングしていないプロ パティがあると、例外を送出する ◦ ObjectMapper に以下の設定を行うことで例外の発生を抑止できるが、マッピ ングを誤っても気づきづらい ◦ DeserializationProblemHandler を設定することで挙動をカスタマイズできる • 未マッピングでも無視するプロパティを設定でき、不明な JSON プロパティが見 つかった場合に、サーバー起動後初回のみエラーレベルのログを出力する Handler を作成して対応 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 不明なJSONプロパティはエラーレベルのログを出力して処理を継続する objectMapper.addHandler(XxxUnknownJsonPropertyLogHandler.INSTANCE); -
71 Content-Type が異なるレスポンス ◼ レスポンスボディーは XML だが、Content-Type が “text/plain” に
なっており、変換できずに例外が発生した ◦ RestTemplate に設定した HttpMessageConverter は getSupportedMediaTypes() が返すメディアタイプに一致する場合にのみ適 用される ◦ 以下の様に SupportedMediaType を変更することで対応可能 (以下はすべての Content-Type に適用する例) MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter(xmlMapper); converter.setSupportedMediaTypes(List.of(MediaType.ALL)); -
72 自動応答を実装しておくと手動テストの効率が上がる ◼ スタブ実装時は単体テストから開始しているため、未設定時はエラー となるように実装していた ◦ そのため手動テスト時に、テスト対象にたどり着くまでに呼ばれるAPIにもスタ ブ設定が必要となっていた ◼ そのようなケースでは、正常系のレスポンスが返って次に進めれば
よいので、 自動で正常系の応答を返せる API には自動応答の機能 を追加した ◦ これにより手動テストの効率がかなり向上した ◦ (後で対応したため、 スタブサーバー側のみ対応したが、折り返しスタブにも あった方がよい機能だった) -
73 OpenID Connect の認証ページ ◼ スタブの範囲外だが OpenID Connect の認証ページも動作検証に 必要となる
◼ 認証ページでスタブ設定を行えるようにしておくと効率的 ◦ 入力フォームには、デフォルトで正常系のレスポンスをプリセットしておくと さらにテストを効率的に実施できる -
スタブサーバー 74 OpenID Connect の認証ページ – シーケンス スタブ設定の 登録画面を返却 認証ページでスタブ
設定を登録する スタブ設定 を入力 -
76 最後に ◼ 今回の事例は連携先が多く、要件も多かったため、基盤開発にコス トをかけるメリットが大きかった ◦ 連携先が少なければ、それぞれ愚直に実装する方がコードの可読性も上がる ◦ 共通化することで構造が複雑になり理解しづらい部分もある ◼
外部連携モジュールの実装コストを抑えつつ、モジュールに起因する 不具合はほとんど発生しなかった ◦ 開発の効率化、品質向上を図ることができた ◦ スタブ管理画面まで用意したことで、手動テストも効率的に行えた