Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
外部システム連携先が10を超えるシステムでのアーキテクチャ設計・実装事例
Search
Kiyoshi Iwasaki
October 27, 2024
Programming
1
380
外部システム連携先が10を超えるシステムでのアーキテクチャ設計・実装事例
JJUG CCC 2024 fall の発表資料です。
Kiyoshi Iwasaki
October 27, 2024
Tweet
Share
More Decks by Kiyoshi Iwasaki
See All by Kiyoshi Iwasaki
フレームワークを10年以上開発する中で培ってきた単体テストのプラクティス / JJUG CCC 2024 Spring
kiwasaki
1
400
Other Decks in Programming
See All in Programming
CQRS+ES の力を使って効果を感じる / Feel the effects of using the power of CQRS+ES
seike460
PRO
0
140
バグを見つけた?それAppleに直してもらおう!
uetyo
0
180
PHPで学ぶプログラミングの教訓 / Lessons in Programming Learned through PHP
nrslib
3
310
Zoneless Testing
rainerhahnekamp
0
120
htmxって知っていますか?次世代のHTML
hiro_ghap1
0
340
KubeCon + CloudNativeCon NA 2024 Overviewat Kubernetes Meetup Tokyo #68 / amsy810_k8sjp68
masayaaoyama
0
260
情報漏洩させないための設計
kubotak
3
380
ChatGPT とつくる PHP で OS 実装
memory1994
PRO
2
120
今年のアップデートで振り返るCDKセキュリティのシフトレフト/2024-cdk-security-shift-left
tomoki10
0
210
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
hirobe1999
0
520
【re:Growth 2024】 Aurora DSQL をちゃんと話します!
maroon1st
0
780
ブラウザ単体でmp4書き出すまで - muddy-web - 2024-12
yue4u
3
490
Featured
See All Featured
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
32
2.7k
Building an army of robots
kneath
302
44k
How to Ace a Technical Interview
jacobian
276
23k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
95
17k
The Art of Programming - Codeland 2020
erikaheidi
53
13k
It's Worth the Effort
3n
183
28k
Making Projects Easy
brettharned
116
5.9k
Agile that works and the tools we love
rasmusluckow
328
21k
GraphQLとの向き合い方2022年版
quramy
44
13k
Stop Working from a Prison Cell
hatefulcrawdad
267
20k
The Cost Of JavaScript in 2023
addyosmani
45
7k
Measuring & Analyzing Core Web Vitals
bluesmoon
4
170
Transcript
Ⓒ2024 Dai Nippon Printing Co., Ltd. All Rights Reserved. 2024年10月27日
大日本印刷株式会社 情報イノベーション事業部 岩崎 清 外部システム連携先が10を超えるシステムでの アーキテクチャ設計・実装事例
2 自己紹介 ◼ 名前 : 岩崎 清 ◼ 所属 :
大日本印刷株式会社 情報イノベーション事業部 ICTセンター ICTDX本部 ◼ 経歴 ◦ 2000年から Java 利用開始 ◦ 2003年から Webアプリケーション開発に従事 ◦ 2013年から社内向け Webアプリケーションフレームワークの開発を開始 ◦ 現在はフレームワークの開発・保守、実案件でのアーキテクトに従事
3 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他
Tips ◼ まとめ
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 は発報対象 • エラーコードの “ー” は成功を表す • 成功の条件 • 必ず条件なしの 行を用意する
16 外部接続モジュールの要件:リクエスト・レスポンスの記録 ◼ スマホアプリのリクエストにひもづけて、リクエスト・レスポンスを記録 ◦ 管理画面から検索・参照可能 スマホアプリ API サーバー 外部連携先
サーバー 管理画面 参照可能
スマホアプリ API サーバー 外部連携先 サーバー 管理画面 参照可能 17 外部接続モジュールの要件:リクエスト・レスポンスの記録 ◼
スマホアプリのリクエストにひもづけて、リクエスト・レスポンスを記録 ◦ 管理画面から検索・参照可能 【余談】 • 性能面を危惧してAPI単位でオフする機能を用意したが、 性能テストで問題になることはなかった • 逆に外部結合テスト、システムテストでのメリットが非常 に大きかった • テストチームが自ら調査して迅速に問題解決でき、開発 チームへの問合せは極端に少なかった
18 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他
Tips ◼ まとめ
19 外部接続モジュールインターフェイスと実装 ◼ 外部接続モジュールのインターフェイスを定義 ◦ 実モジュールとスタブモジュールの実装を用意 ◦ 単体テスト等ではスタブに切り替える ロジック側とモジュール側の 責務をどう分離するか?
実モジュール スタブモジュール 外部接続モジュール
20 ロジックと外部接続モジュールの責務の分離 ◼ ロジックと外部接続モジュールの責務の分離方法 ◦ 方法1)ドメインから完全に独立した再利用可能なモジュールにする ◦ 方法2)ドメインへの依存を許可し、利用者側が簡単に利用できるようにする ◦ 方法3)一部のドメインへの依存は許可する
(業務に依存するドメインへの依存は許可しない)
21 ロジックと外部接続モジュールの責務の分離 ◼ 方法1)モジュールを、ドメイン等から完全に独立した再利用可能なモ ジュールにする ◦ モジュールの独立性は高く、他の案件でも再利用可能 ◦ モジュール単体での保守性も高い ◦
全体としての実装コストは高くなる • 連携先ごとの設定(APIキー、パスフレーズ等)や、通信設定は、外部から与える か、専用のプロパティファイルを用意する • 通信設定はデータベースに保存しており、リロードする必要があるが、モジュール の利用者側で管理するか、コールバックを用意する必要がある。 • エラーコードへの依存もできないため、レスポンスからのエラーマッピングも利用 者側に実装する必要がある • リクエスト・レスポンスをデータベースに記録する仕組みを入れづらい
22 ロジックと外部接続モジュールの責務の分離 ◼ 方法1 通信設定等の管理 同時実行数の制御 エラーマッピング処理 リクエスト・レスポンス記録
23 ロジックと外部接続モジュールの責務の分離 ◼ 方法2)モジュールインターフェイスの、ドメインへの依存を許可し、利 用者側が簡単に利用できるようにする ◦ 案件固有の業務ロジックも含めるため、利用者側は使いやすい • 例)会員を渡し、外部接続モジュール側で会員からトークンを取得して通信する ◦
全体としての実装コストは最小化できる • 初期化や設定値のリロードもモジュール内で実装しやすい。 • エラーマッピングもモジュール内で実施可能 • リクエスト・レスポンスのデータベースへの記録も実装可能 ◦ モジュールの独立性、再利用性、保守性はかなり低い • ドメインの修正がモジュールに影響する
24 ロジックと外部接続モジュールの責務の分離 ◼ 方法2 通信設定等の管理 同時実行数の制御 エラーマッピング処理 案件固有の業務ロジック リクエスト・レスポンス記録
25 ロジックと外部接続モジュールの責務の分離 ◼ 方法3)モジュールインターフェイスの、業務に依存する ドメインへの依存は許可しない ◦ 方法1と方法2の中間 ◦ 会員等の業務に直結するドメインへの依存は許可しないが、通信設定、エ ラーコード等の、システムよりのドメインへの依存は許可する
◦ 全体としての実装コストは、方法1>方法3>方法2 • 設定のリロード、エラーマッピング、リクエスト・レスポンスの記録等を、モジュール 側の共通実装にできる ◦ モジュールの独立性、再利用性、保守性は、方法1<方法3<方法2 • ただし、システムよりのドメインは安定しているので影響は受けづらい 採用
26 ロジックと外部接続モジュールの責務の分離 ◼ 方法3 通信設定等の管理 同時実行数の制御 エラーマッピング処理 案件固有の業務ロジック リクエスト・レスポンス記録
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 外部接続モジュール間の処理の共通化 ◼ 継承と委譲 ◦ 一般に設計としては、継承よりも委譲の方がよいケースが多い • 継承はカプセル化を破壊する ◦ 継承先も含めて設計・実装できる(継承元を修正することができる)ケースで
は、継承の方が効率的に開発できるケースが多い ◦ 案件開発においては、継承の方が構造が複雑化せず、効率的に開発できる ケースも多い
37 外部接続モジュール間の処理の共通化 ◼ 方針 ◦ コンポーネント化しやすい部分はコンポーネント化し、それらを利用する Client, Connector の処理は極力基底クラスに実装する •
差分は主にテンプレートメソッドに実装する
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)); } ……
44 外部接続モジュール設計・実装のまとめ ◼ 求められる機能要件や開発効率、品質を考慮して行ったアーキテク チャ設計事例を紹介 ◦ (汎用的なモジュール開発ではなく)案件開発での事例 ◦ リソース等の制約がある中で、トレードオフを判断して設計
45 外部接続モジュール設計・実装のまとめ ◼ これまでの説明は出来上がったものに対する説明 ◦ 説明した内容を一つ一つ考えながら設計・実装しているわけではない ◼ 要件や制約、実現したいことを頭に入れて、全体的に設計する ◦ それぞれ絡み合う部分があり、個別には考えづらい
◼ 実際にはいくつかの外部接続仕様を確認し、どれかひとつを取り上 げ、実装しながら設計していく ◦ よりより設計・実装を行うにはトライアンドエラーは重要
46 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他
Tips ◼ まとめ
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
55 スタブのユースケース ◼ スタブのユースケース ◦ 単体テストでの利用 ◦ 自動結合テストでの利用 ◦ 手動テスト(結合テスト、システムテスト)での利用
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 スタブ リクエスト項目の中から スタブ設定のキーを設定する スタブ設定のキーは、入力値で 制御可能でなくてはならない
63 スタブ設定のキー項目 ◼ 以下のようなケースはキー項目の設定が難しい ◦ サーバー側で乱数により生成するIDがキーとなる場合 ◦ キー項目はあるが、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の入力値で制御できるも の」でなければならない ◦ 直接キー項目になるものがない場合も、合わせ技で制御できる場合もある (事例参照)
69 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他
Tips ◼ まとめ
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 の認証ページ – シーケンス スタブ設定の 登録画面を返却 認証ページでスタブ
設定を登録する スタブ設定 を入力
75 アジェンダ ◼ 概要 ◼ 外部接続モジュールの設計・実装 ◼ スタブの設計・実装 ◼ その他
Tips ◼ まとめ
76 最後に ◼ 今回の事例は連携先が多く、要件も多かったため、基盤開発にコス トをかけるメリットが大きかった ◦ 連携先が少なければ、それぞれ愚直に実装する方がコードの可読性も上がる ◦ 共通化することで構造が複雑になり理解しづらい部分もある ◼
外部連携モジュールの実装コストを抑えつつ、モジュールに起因する 不具合はほとんど発生しなかった ◦ 開発の効率化、品質向上を図ることができた ◦ スタブ管理画面まで用意したことで、手動テストも効率的に行えた
77 最後に ◼ 今回作った仕組みは再利用できそう ◦ フレームワーク等と同じで最初に作るのは大変だが、一度作れば次は低コス トで導入できる ◼ やはりアーキテクチャ設計・実装は、難しいが楽しい!
78 ご清聴ありがとうございました
「 未来のあたりまえをつくる。」はDNP大日本印刷の登録商標です。