Slide 1

Slide 1 text

Ⓒ2024 Dai Nippon Printing Co., Ltd. All Rights Reserved. 2024年10月27日 大日本印刷株式会社 情報イノベーション事業部 岩崎 清 外部システム連携先が10を超えるシステムでの アーキテクチャ設計・実装事例

Slide 2

Slide 2 text

2 自己紹介 ◼ 名前 : 岩崎 清 ◼ 所属 : 大日本印刷株式会社 情報イノベーション事業部 ICTセンター ICTDX本部 ◼ 経歴 ○ 2000年から Java 利用開始 ○ 2003年から Webアプリケーション開発に従事 ○ 2013年から社内向け Webアプリケーションフレームワークの開発を開始 ○ 現在はフレームワークの開発・保守、実案件でのアーキテクトに従事

Slide 3

Slide 3 text

3 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他 Tips ◼ まとめ

Slide 4

Slide 4 text

4 概要 ◼ 外部システムの REST API を呼び出す外部接続モジュールの アーキテクチャ設計・実装の事例紹介 ○ クラス設計等の実装よりのアーキテクチャ設計事例 ○ 外部システム連携先は10カ所を超える ○ 結合テスト、システムテストでも利用するスタブも含むアーキテクチャ

Slide 5

Slide 5 text

5 システム概要 スマホアプリ 管理画面 API サーバー 管理画面 サーバー 外部連携先 サーバー 外部連携先 サーバー 外部連携先 サーバー ・・・・・・ 連携先は10以上 連携APIは50以上

Slide 6

Slide 6 text

6 主な条件・制約 ◼ 外部連携システムは10カ所以上あり、仕様もまちまちだが、開発期 間も限られているため、効率的な開発が求められる ◼ 外部連携システムとは、外部結合テスト工程まで接続できないため、 実装段階で品質を確保する必要がある ◼ スタブは、複数のAPI呼び出しを連携させる仕組みが必要となる (スタブの制御が複雑) ◼ システムテスト工程では、テスター(≠開発者)がスタブを利用する必 要がある

Slide 7

Slide 7 text

7 主な要件 ◼ 通信設定(タイムアウト、リトライ等)はデプロイなしに変更可能 ◼ 複数サーバーをまたがって同時実行数のキュー制御が必要 (設定はデプロイなしに変更可能) ◼ 特定のレスポンス項目からエラーマッピングし、設定に応じて発報 (設定はデプロイなしに変更可能) ◼ リクエスト、レスポンス(ボディー部分も)を管理画面から検索可能

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

9 外部接続モジュールの要件:同時実行数のキュー制御 実行中 最大待機数 最大実行数 API サーバー 外部連携先 サーバー API サーバー API サーバー • 最大実行数以下は即時実行可能 • 実行終了時にキューから削除 • システム全体で同時実行数の制御が必要 接続要求

Slide 10

Slide 10 text

10 外部接続モジュールの要件:同時実行数のキュー制御 実行中 実行中 実行待ち 最大待機数 最大実行数 API サーバー 外部連携先 サーバー API サーバー API サーバー • 最大実行数を超えると待機 • 待機中に最大待機時間を超えると、 待機を中断してエラー 接続要求

Slide 11

Slide 11 text

11 外部接続モジュールの要件:同時実行数のキュー制御 実行中 実行中 実行待ち 実行待ち 最大待機数 最大実行数 API サーバー 外部連携先 サーバー API サーバー API サーバー • 最大待機数を超えると即時エラー 接続要求 実行待ち

Slide 12

Slide 12 text

外部 連携先 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 外部接続モジュールの要件:エラーマッピング ◼ エラーマッピング表によるエラーコード変換 ○ 以下のような表に従い、レスポンスからエラーコードに変換する ○ 設定は再起動なしにリロード可能にする

Slide 13

Slide 13 text

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ステータスにマッピングする

Slide 14

Slide 14 text

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.", …… } レスポンス例

Slide 15

Slide 15 text

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 は発報対象 • エラーコードの “ー” は成功を表す • 成功の条件 • 必ず条件なしの 行を用意する

Slide 16

Slide 16 text

16 外部接続モジュールの要件:リクエスト・レスポンスの記録 ◼ スマホアプリのリクエストにひもづけて、リクエスト・レスポンスを記録 ○ 管理画面から検索・参照可能 スマホアプリ API サーバー 外部連携先 サーバー 管理画面 参照可能

Slide 17

Slide 17 text

スマホアプリ API サーバー 外部連携先 サーバー 管理画面 参照可能 17 外部接続モジュールの要件:リクエスト・レスポンスの記録 ◼ スマホアプリのリクエストにひもづけて、リクエスト・レスポンスを記録 ○ 管理画面から検索・参照可能 【余談】 • 性能面を危惧してAPI単位でオフする機能を用意したが、 性能テストで問題になることはなかった • 逆に外部結合テスト、システムテストでのメリットが非常 に大きかった • テストチームが自ら調査して迅速に問題解決でき、開発 チームへの問合せは極端に少なかった

Slide 18

Slide 18 text

18 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他 Tips ◼ まとめ

Slide 19

Slide 19 text

19 外部接続モジュールインターフェイスと実装 ◼ 外部接続モジュールのインターフェイスを定義 ○ 実モジュールとスタブモジュールの実装を用意 ○ 単体テスト等ではスタブに切り替える ロジック側とモジュール側の 責務をどう分離するか? 実モジュール スタブモジュール 外部接続モジュール

Slide 20

Slide 20 text

20 ロジックと外部接続モジュールの責務の分離 ◼ ロジックと外部接続モジュールの責務の分離方法 ○ 方法1)ドメインから完全に独立した再利用可能なモジュールにする ○ 方法2)ドメインへの依存を許可し、利用者側が簡単に利用できるようにする ○ 方法3)一部のドメインへの依存は許可する (業務に依存するドメインへの依存は許可しない)

Slide 21

Slide 21 text

21 ロジックと外部接続モジュールの責務の分離 ◼ 方法1)モジュールを、ドメイン等から完全に独立した再利用可能なモ ジュールにする ○ モジュールの独立性は高く、他の案件でも再利用可能 ○ モジュール単体での保守性も高い ○ 全体としての実装コストは高くなる • 連携先ごとの設定(APIキー、パスフレーズ等)や、通信設定は、外部から与える か、専用のプロパティファイルを用意する • 通信設定はデータベースに保存しており、リロードする必要があるが、モジュール の利用者側で管理するか、コールバックを用意する必要がある。 • エラーコードへの依存もできないため、レスポンスからのエラーマッピングも利用 者側に実装する必要がある • リクエスト・レスポンスをデータベースに記録する仕組みを入れづらい

Slide 22

Slide 22 text

22 ロジックと外部接続モジュールの責務の分離 ◼ 方法1 通信設定等の管理 同時実行数の制御 エラーマッピング処理 リクエスト・レスポンス記録

Slide 23

Slide 23 text

23 ロジックと外部接続モジュールの責務の分離 ◼ 方法2)モジュールインターフェイスの、ドメインへの依存を許可し、利 用者側が簡単に利用できるようにする ○ 案件固有の業務ロジックも含めるため、利用者側は使いやすい • 例)会員を渡し、外部接続モジュール側で会員からトークンを取得して通信する ○ 全体としての実装コストは最小化できる • 初期化や設定値のリロードもモジュール内で実装しやすい。 • エラーマッピングもモジュール内で実施可能 • リクエスト・レスポンスのデータベースへの記録も実装可能 ○ モジュールの独立性、再利用性、保守性はかなり低い • ドメインの修正がモジュールに影響する

Slide 24

Slide 24 text

24 ロジックと外部接続モジュールの責務の分離 ◼ 方法2 通信設定等の管理 同時実行数の制御 エラーマッピング処理 案件固有の業務ロジック リクエスト・レスポンス記録

Slide 25

Slide 25 text

25 ロジックと外部接続モジュールの責務の分離 ◼ 方法3)モジュールインターフェイスの、業務に依存する ドメインへの依存は許可しない ○ 方法1と方法2の中間 ○ 会員等の業務に直結するドメインへの依存は許可しないが、通信設定、エ ラーコード等の、システムよりのドメインへの依存は許可する ○ 全体としての実装コストは、方法1>方法3>方法2 • 設定のリロード、エラーマッピング、リクエスト・レスポンスの記録等を、モジュール 側の共通実装にできる ○ モジュールの独立性、再利用性、保守性は、方法1<方法3<方法2 • ただし、システムよりのドメインは安定しているので影響は受けづらい 採用

Slide 26

Slide 26 text

26 ロジックと外部接続モジュールの責務の分離 ◼ 方法3 通信設定等の管理 同時実行数の制御 エラーマッピング処理 案件固有の業務ロジック リクエスト・レスポンス記録

Slide 27

Slide 27 text

27 実モジュールとスタブモジュール ◼ 実装時(単体テスト時)にスタブモジュールを利用する形だと、 実モジュールのテストが不十分 ○ 外部結合テストまで、外部連携先と接続できないこともあり、極力単体テスト 時にコードを動作させておきたい。 実モジュール スタブモジュール 外部接続モジュール 単体テストで実モジュールを 動作させることができない

Slide 28

Slide 28 text

28 実モジュールとスタブモジュール ◼ 実装時(単体テスト時)にスタブモジュールを利用する形だと、 実モジュールのテストが不十分 ○ 外部結合テストまで、外部連携先と接続できないこともあり、極力単体テスト 時にコードを動作させておきたい。 ◼ 単体テスト時の未実行コードを減らすために ○ 対策1)外部接続モジュールの通信部分をさらにインターフェイス化して分離 ○ 対策2)RestTemplate のインターセプタを利用

Slide 29

Slide 29 text

29 対策1:外部接続モジュールの分離 ◼ 通信を行う部分をさらにインターフェイス化して分離 ○ (以降、前段を Client、後段を Connector と表記する) ○ RestTemplate を利用した実際の通信処理は Connector 側で行う ○ それ以外の処理は Client 側で行う Client 側のコードは単体テスト時も実行可能になる

Slide 30

Slide 30 text

30 Client の責務 ◼ Client の主な責務 ○ リクエストの準備と Connector の呼び出し ○ (外部連携先により)同時実行数のキュー制御 ○ (外部連携先により)特定の条件(流量制限エラー等)でのリトライ ○ リクエスト・レスポンスの記録

Slide 31

Slide 31 text

31 Connector の責務 ◼ Connector の主な責務 ○ 通信設定、エラーマッピングの読み込みおよびリロード ○ RestTemplate を利用した外部連携APIの実行 ○ タイムアウトによるリトライ ○ レスポンスからのエラーマッピング

Slide 32

Slide 32 text

32 対策2:RestTemplate のインターセプタの利用 ◼ ConnectorImpl 利用時の挙動 ○ RestTemplate 経由で外部連携先サーバーと通信を行う XxxClientImpl 外部連携先 サーバー XxxConnector Impl RestTemplate

Slide 33

Slide 33 text

33 対策2:RestTemplate のインターセプタの利用 ◼ ConnectorStubImpl は ConnectorImpl を継承し以下の処理を行う ○ RestTemplate のインターセプタを設定する ○ スタブ固有の処理を行う ◼ 単体テストで ConnectorImpl のほぼすべてのコードが実行可能

Slide 34

Slide 34 text

34 対策2:RestTemplate のインターセプタの利用 ◼ ConnectorStubImpl 利用時(単体テスト時)の挙動 ○ RestTemplate のインターセプタを設定し、実際に通信を行う直前でスタブの 処理を行い、返却したいレスポンスのバイト列を返す ⇒ MessageConverter でレスポンスのデシリアライズ処理も実行される XxxClientImpl 外部連携先 サーバー XxxConnector Impl RestTemplate ※RestTemplate の インターセプタ XxxConnector StubImpl 実際に通信は行わず、 レスポンスのバイト列 を返す

Slide 35

Slide 35 text

35 外部接続モジュール間の処理の共通化 ◼ 外部接続モジュール間の処理を共通化し、個別実装を減らし、 開発効率化、品質向上を実現したい ◼ 共通化の方法 ○ コンポーネント化 ○ 共通処理の基底クラス化(継承、テンプレートメソッドパターン) ○ 委譲(デコレータパターン、プロキシパターン)

Slide 36

Slide 36 text

36 外部接続モジュール間の処理の共通化 ◼ 継承と委譲 ○ 一般に設計としては、継承よりも委譲の方がよいケースが多い • 継承はカプセル化を破壊する ○ 継承先も含めて設計・実装できる(継承元を修正することができる)ケースで は、継承の方が効率的に開発できるケースが多い ○ 案件開発においては、継承の方が構造が複雑化せず、効率的に開発できる ケースも多い

Slide 37

Slide 37 text

37 外部接続モジュール間の処理の共通化 ◼ 方針 ○ コンポーネント化しやすい部分はコンポーネント化し、それらを利用する Client, Connector の処理は極力基底クラスに実装する • 差分は主にテンプレートメソッドに実装する

Slide 38

Slide 38 text

38 Client の基底クラスとサブクラスの責務 ◼ サブクラス(XxxClientImpl) ○ Connector に渡すリクエストの準備 ○ Connector の呼び出し ◼ 基底クラス(BaseClient) ○ リクエスト・レスポンスのデータベースへの保存

Slide 39

Slide 39 text

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 connectorResult = callConnector(() -> xxxConnector.someApi(request)); return new SomeApiResult(request, connectorResult); } …… 1.リクエストを用意 2.Connector を 呼び出す 3.結果を返却する

Slide 40

Slide 40 text

40 Connector の基底クラスとサブクラスの責務 ◼ サブクラス(XxxConnectorImpl) ○ RestTemplate に設定する MessageConverter の生成(初期化時) ○ APIごとの RequestEntity の生成 ◼ 基底クラス(BaseConnector) ○ RestTemplate の生成・キャッシュ ○ 通信設定が更新された場合の RestTemplate の再作成 ○ 実際の通信と、通信設定に応じたリードタイムアウト時の リトライ処理 ○ レスポンスからのエラーマッピング

Slide 41

Slide 41 text

41 XxxConnectorImpl の実装イメージ ◼ RestTemplate に設定する MessageConverter の生成(初期化用) public class XxxConnectorImpl extends BaseExtComConnector implements XxxConnector { …… @Override protected List> createMessageConverters( SyExtApiType syExtApiType) { List> messageConverters = new ArrayList<>(); …… // JSON 用の MessageConverter を設定 MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper); messageConverters.add(converter); return messageConverters; }

Slide 42

Slide 42 text

42 XxxConnectorImpl の実装イメージ ◼ 個々のAPIの実装は、RequestEntity を生成するラムダ等を渡し、 基底クラスのメソッドを呼び出す …… @Override public XxxConnectorResult someApi(SomeApiRequestImpl request) { SyExtApiType extApiType = SyExtApiType.XXX_001; return execute( extApiType, () -> createSomeApiRequest(request, XXX_001_PATH), SomeApiResponseImpl.class, createResultCreator(extApiType)); } ……

Slide 43

Slide 43 text

43 XxxConnectorImpl の実装イメージ ◼ RequestEntity を生成するメソッドでは、RequestEntity を生成して、 リクエストとヘッダ等を設定して返す …… private RequestEntity> 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)); } ……

Slide 44

Slide 44 text

44 外部接続モジュール設計・実装のまとめ ◼ 求められる機能要件や開発効率、品質を考慮して行ったアーキテク チャ設計事例を紹介 ○ (汎用的なモジュール開発ではなく)案件開発での事例 ○ リソース等の制約がある中で、トレードオフを判断して設計

Slide 45

Slide 45 text

45 外部接続モジュール設計・実装のまとめ ◼ これまでの説明は出来上がったものに対する説明 ○ 説明した内容を一つ一つ考えながら設計・実装しているわけではない ◼ 要件や制約、実現したいことを頭に入れて、全体的に設計する ○ それぞれ絡み合う部分があり、個別には考えづらい ◼ 実際にはいくつかの外部接続仕様を確認し、どれかひとつを取り上 げ、実装しながら設計していく ○ よりより設計・実装を行うにはトライアンドエラーは重要

Slide 46

Slide 46 text

46 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他 Tips ◼ まとめ

Slide 47

Slide 47 text

47 スタブの設計要因:スタブ設定 ◼ スタブ設定:スタブが返却するレスポンスの設定 ○ 返却するレスポンスのタイプ • 固定レスポンス • 自動レスポンス • 指定レスポンス ○ (指定レスポンスの場合)レスポンスの指定方法 • レスポンスの登録順 • 登録時に指定したキー

Slide 48

Slide 48 text

48 スタブの設計要因:スタブ設定 ◼ レスポンスタイプ:固定レスポンス ○ ファイル等で用意した固定のレスポンスを返す ○ ファイルを差し替えることでレスポンスを変更する ○ ほとんどレスポンスを変える必要がないケース等でしか利用できない APIサーバー スタブ スマホアプリ 固定の レスポンス ※レスポンスの変更は ファイルを差し替える

Slide 49

Slide 49 text

49 スタブの設計要因:スタブ設定 ◼ レスポンスタイプ:自動レスポンス ○ 自動で正常系のレスポンスを返せるケース • 入力値や現在日時、乱数で生成したID等 ○ 異常系は他の方法と併用する ○ 対象のAPIが直接のテスト対象でない場合に有効 APIサーバー スタブ スマホアプリ 自動でレスポンスを 生成して返す

Slide 50

Slide 50 text

50 スタブの設計要因:スタブ設定 ◼ レスポンスタイプ:指定レスポンス ○ 事前に任意のレスポンスを複数登録しておき返却する (単体テスト時以外は、スタブ設定をDBやKVS等に保存する必要がある) ○ 返却するレスポンスの指定方法 • 1. 登録順:レスポンスを登録した順に返す • 2. キー:登録時に指定したキーに対応するレスポンスを返す • 3. キー+登録順:1 と 2 の組合せ

Slide 51

Slide 51 text

51 スタブの設計要因:スタブ設定 – レスポンスタイプ:指定レスポンス ◼ 指定方法:登録順 ○ レスポンスを登録した順に返却する(FIFOキュー) ○ 連続した同一APIの呼び出しで異なるレスポンスを返すことが可能だが、 複数人での同時テストは困難 APIサーバー スタブ スマホアプリ レスポンス1 レスポンス2 …… ・登録したレスポンスは上に積まれる ・スタブは下から利用していく

Slide 52

Slide 52 text

52 スタブの設計要因:スタブ設定 – レスポンスタイプ:指定レスポンス ◼ 指定方法:キー ○ APIの入力項目からキーを決める(※キー項目の詳細は後述) ○ キーを指定してレスポンスを登録する ○ 入力項目に該当するレスポンスを返却する ○ 複数人で同時テスト可能だが、連続した同一APIの呼び出しで異なるレスポン スを返すことはできない APIサーバー スタブ スマホアプリ zzz ⇒ レスポンス3 yyy ⇒ レスポンス2 xxx ⇒ レスポンス1 xxx

Slide 53

Slide 53 text

53 スタブの設計要因:スタブ設定 – レスポンスタイプ:指定レスポンス ◼ 指定方法:キー+登録順 ○ 登録順とキーの組合せ ○ キーごとにキューを持つ ○ 連続した同一APIの呼び出しで異なるレスポンスを返すことも、 複数人での同時テストも可能 APIサーバー スタブ スマホアプリ キー:xxx レスポンスx1 レスポンスx2 …… キー:yyy レスポンスy1 レスポンスy2 …… キー:zzz レスポンスz1 レスポンスz2 …… xxx

Slide 54

Slide 54 text

54 スタブの設計要因:折り返しスタブとスタブサーバー ◼ 折り返しスタブ ○ 外部通信は行わず、 プロセス内で折り返す ◼ スタブサーバー ○ 実モジュールを利用 ○ 実際に通信を行う ○ 外部連携先相当の スタブサーバーを用意する APIサーバー ClientImpl ConnectorImpl ConnectorStubImpl スタブ サーバー APIサーバー ClientImpl ConnectorImpl ConnectorStubImpl

Slide 55

Slide 55 text

55 スタブのユースケース ◼ スタブのユースケース ○ 単体テストでの利用 ○ 自動結合テストでの利用 ○ 手動テスト(結合テスト、システムテスト)での利用

Slide 56

Slide 56 text

56 スタブの各ユースケースと制約 ユースケース 設定の保存場所 複数設定の制御 設定手段 単体テスト • メモリも可 • 1テストずつの実行と なるため、登録順で も問題ない • プログラム (テストコード) 自動結合 テスト • メモリは不可 • DB, KVS等に 保存する必要 あり 手動テスト • 複数人でテストする ため、複数の設定か ら特定する必要あり • 手動 (設定ツールが 必要) 今回のターゲット

Slide 57

Slide 57 text

57 スタブの要件 ◼ スタブ設定は「指定レスポンス」を「キー+登録順」で管理する ○ 設定は KeyValueStore に保存する ○ 単体テストでは不要だが、単体テストも同じ仕組みにする ◼ スタブ設定用管理画面を用意する ○ テスター(≠開発者)が利用するため、GUIツールが必要 ○ 専用のWebアプリを用意する ◼ 外部APIと同じ動作をするスタブサーバーも用意する ○ 外部結合テスト前に、外部接続モジュールの通信テストを行えるようにする ○ 性能テスト時に実際に通信する状態で計測できるようにする

Slide 58

Slide 58 text

58 単体テスト実行時 ClientImpl ConnectorImpl ConnectorStubImpl Redis スタブ設定 データ 単体テスト 1.テスト準備で、スタブ設定を投入する 2.投入されたスタブ設定に従い レスポンスを返す

Slide 59

Slide 59 text

APIサーバー 59 自動結合テスト実行時 ClientImpl ConnectorImpl ConnectorStubImpl Redis スタブ設定 データ 結合テスト (WebDriver) 1.テスト準備で、スタブ設定を投入する 2.投入されたスタブ設定に従い レスポンスを返す

Slide 60

Slide 60 text

スタブサーバー 60 手動テスト実行時 APIサーバー ClientImpl ConnectorImpl ConnectorStubImpl スマホアプリ Redis スタブ設定 データ 外部APIスタブ スタブ設定 管理画面 スタブ設定 1.テスト実行前に、スタブ設定を行う 2.スタブサーバーがスタブ設定に 従ってレスポンスを返す

Slide 61

Slide 61 text

61 スタブ設定 ◼ 共通のスタブ設定項目 ○ 特殊エラー • 接続タイムアウト、リードタイムアウト等 ※外部APIスタブは設定できない ○ 利用回数 • 指定した回数利用すると削除 • 無制限も指定可能 ○ 遅延秒数 • リードタイムアウトを発生させることができる ○ HTTPステータス • レスポンスのHTTPステータスコード

Slide 62

Slide 62 text

62 スタブ設定のキー項目 ◼ スタブ設定のキーは外部APIのリクエスト項目でなくてはならない ◼ キー項目はスマホアプリからの入力値で制御可能でなくてはならい ○ キー項目を直接指定できないが制御可能である例 • スタブ設定のキーはカードID • スマホからのAPIにカードIDは含まれないが、会員IDが含まれる • サーバーで会員IDからカードIDを引いてリクエストに利用している スマホアプリ API サーバー 外部API スタブ リクエスト項目の中から スタブ設定のキーを設定する スタブ設定のキーは、入力値で 制御可能でなくてはならない

Slide 63

Slide 63 text

63 スタブ設定のキー項目 ◼ 以下のようなケースはキー項目の設定が難しい ○ サーバー側で乱数により生成するIDがキーとなる場合 ○ キー項目はあるが、APIが連続して呼び出されることで、キー項目に対するス タブ設定を行うタイミングがない場合

Slide 64

Slide 64 text

APIサーバー 64 キー設定が難しい事例① ◼ 問題点:キー項目値を外部から参照できない スマホアプリ IDを採番 IDはDBに保存 外部API1 口座番号 口座番号, ID DBからIDを 取得 外部API2 ID ID IDは外部からは参照不可 外部API1のスタブキーは 口座番号を利用可能 スタブキーとして利用 できるのはIDのみ

Slide 65

Slide 65 text

APIサーバー 65 キー設定が難しい事例① ◼ 解決策:スタブ側でキー項目へ変換するためのデータを管理する スマホアプリ IDを採番 IDはDBに保存 外部API1 口座番号 口座番号, ID DBからIDを 取得 外部API2 ID IDをキーに口座番号を保存 IDから口座番号を取得。 取得した口座番号をキー としてスタブ設定を取得 ID Redis スタブ設定 キー:口座番号 Redis ID⇒口座番号

Slide 66

Slide 66 text

APIサーバー 66 キー設定が難しい事例② ◼ 問題点:スタブ設定を行うタイミングがない スマホアプリ IDはDBに保存 外部API1 ID、口座番号 ID, 口座番号 DBからIDを 取得 外部API2 ID 外部API1のスタブキーは 口座番号を利用可能 ID IDは管理画面から参照可能 二つのAPI呼び出しは連続 して行われるため、スタブ設 定を行うタイミングがない IDはスマホ内で採番 スタブキーとして利用 できるのはIDのみ

Slide 67

Slide 67 text

67 キー設定が難しい事例② ◼ 解決策:テスト用にスマホアプリ側に一時停止機能を追加する APIサーバー スマホアプリ IDはDBに保存 外部API1 ID、口座番号 ID, 口座番号 DBからIDを 取得 外部API2 ID ID 二つのAPI呼び出しの間 に、テスト用の確認ダイア ログを表示し、一時停止 できるようにして対応

Slide 68

Slide 68 text

68 スタブ設計・実装のまとめ ◼ スタブのユースケースによって求められる機能も変わる ○ 単体テスト<自動結合テスト<手動テストの順で高機能なものが求められる ○ どこまでの機能が必要か見極め、早めに対応した方がよい • 今回の仕組みは、外部連携モジュール自体の設計時から一緒に検討していた ◼ スタブ設定のキー項目をうまく見つける ○ キー項目は「外部APIのリクエスト項目」かつ「APIの入力値で制御できるも の」でなければならない ○ 直接キー項目になるものがない場合も、合わせ技で制御できる場合もある (事例参照)

Slide 69

Slide 69 text

69 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他 Tips ◼ まとめ

Slide 70

Slide 70 text

70 仕様書に未記載の JSON プロパティ ◼ 仕様書に未記載の JSON プロパティが返却され例外が発生した ○ Jackson の ObjectMapper のデフォルト動作では、マッピングしていないプロ パティがあると、例外を送出する ○ ObjectMapper に以下の設定を行うことで例外の発生を抑止できるが、マッピ ングを誤っても気づきづらい ○ DeserializationProblemHandler を設定することで挙動をカスタマイズできる • 未マッピングでも無視するプロパティを設定でき、不明な JSON プロパティが見 つかった場合に、サーバー起動後初回のみエラーレベルのログを出力する Handler を作成して対応 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 不明なJSONプロパティはエラーレベルのログを出力して処理を継続する objectMapper.addHandler(XxxUnknownJsonPropertyLogHandler.INSTANCE);

Slide 71

Slide 71 text

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));

Slide 72

Slide 72 text

72 自動応答を実装しておくと手動テストの効率が上がる ◼ スタブ実装時は単体テストから開始しているため、未設定時はエラー となるように実装していた ○ そのため手動テスト時に、テスト対象にたどり着くまでに呼ばれるAPIにもスタ ブ設定が必要となっていた ◼ そのようなケースでは、正常系のレスポンスが返って次に進めれば よいので、 自動で正常系の応答を返せる API には自動応答の機能 を追加した ○ これにより手動テストの効率がかなり向上した ○ (後で対応したため、 スタブサーバー側のみ対応したが、折り返しスタブにも あった方がよい機能だった)

Slide 73

Slide 73 text

73 OpenID Connect の認証ページ ◼ スタブの範囲外だが OpenID Connect の認証ページも動作検証に 必要となる ◼ 認証ページでスタブ設定を行えるようにしておくと効率的 ○ 入力フォームには、デフォルトで正常系のレスポンスをプリセットしておくと さらにテストを効率的に実施できる

Slide 74

Slide 74 text

スタブサーバー 74 OpenID Connect の認証ページ – シーケンス スタブ設定の 登録画面を返却 認証ページでスタブ 設定を登録する スタブ設定 を入力

Slide 75

Slide 75 text

75 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他 Tips ◼ まとめ

Slide 76

Slide 76 text

76 最後に ◼ 今回の事例は連携先が多く、要件も多かったため、基盤開発にコス トをかけるメリットが大きかった ○ 連携先が少なければ、それぞれ愚直に実装する方がコードの可読性も上がる ○ 共通化することで構造が複雑になり理解しづらい部分もある ◼ 外部連携モジュールの実装コストを抑えつつ、モジュールに起因する 不具合はほとんど発生しなかった ○ 開発の効率化、品質向上を図ることができた ○ スタブ管理画面まで用意したことで、手動テストも効率的に行えた

Slide 77

Slide 77 text

77 最後に ◼ 今回作った仕組みは再利用できそう ○ フレームワーク等と同じで最初に作るのは大変だが、一度作れば次は低コス トで導入できる ◼ やはりアーキテクチャ設計・実装は、難しいが楽しい!

Slide 78

Slide 78 text

78 ご清聴ありがとうございました

Slide 79

Slide 79 text

「 未来のあたりまえをつくる。」はDNP大日本印刷の登録商標です。