このページに書いてあること
この記事の続きです。
最初にざっくり書いた以下のクラス図。
こちらのクラス図を更に改良していきます。
どこが改善ポイントかというと、Serviceパッケージ内の外部APIに通信する部分です。送信処理、応答の受信、リトライの制御などです。
これは別記事で書いた、「仕組みは共通」の部分に通じる部分になります。どのAPIにどのような電文を送信するかというのは業務に当たりますから共通化はしない方が良いです。
どこを共通化するか
共通化するのは仕組みです。今回の場合は、
- WebAPIへの送受信部分(ただし、電文の内容だけは個別の実装で準備)
- 送受信時のタイムアウト、リトライ制御部分
どこが共通できないか
各APIが個別に要求するものは共通化できません。今回の場合は、
- APIへのヘッダ生成部分(URL指定とか)
クラス設計
そんな前提を理解した上で、以下のようにしてみました。
ポイントは以下です。
- sendメソッドの中でAPIへの送受信やリトライ処理を行う
- sendメソッドは受信した応答を整形せず文字列のまま返す(これにより返りがJSONだろうがXMLだろうがPOST電文であろうがsendメソッドは関係なくなります。どの形式の応答が来るかを知っているべきなのは、各サービスクラスなので、IFの実装クラス側で変換しましょう)
- どのAPIがどのようなヘッダーを要求するかは不明なため、実装クラス側に作り込んでもらう
- ヘッダーメソッドへの情報引き渡しは、IF側のexecuteメソッドの引数をそのまま渡すとすっきりする(executeメソッドの引数を増やしてHeaderDtoなどを渡してもいい)
- ただし、createHeaderメソッドはsendメソッド内で呼び出すようにしておく
sendメソッドは抽象クラスの中で実際にリクエスト送信処理を実施します。
この中では、タイムアウト処理やリトライ処理も行います。
そして、どこに送信するかという情報や認証情報を付与するかというものはAPIごとに要求するものが違いますが、大抵はHTTPヘッダで制御することになります。(Authorizationヘッダーなど)
なので、ヘッダ生成処理は抽象メソッドとして実装クラスで規定してもらいます。
呼び出しの関係ですが、sendメソッド内でcreateHeaderメソッドを呼び出すようにしておきます。
そうすることで、インタフェースで規定したexecuteメソッドには電文を準備してsendメソッドを呼び出せという非常にシンプルなルールにすることができます。
(execute内でcreateHeaderを呼び出した後にsendを呼び出せというルールは守られない可能性がある)
createHeaderメソッドには各種実装クラスがIFから受け取る総称型を引き数に受け取ります。
これにより、APIごとにヘッダに付与したい情報を実装クラスごとに柔軟に設定することができます。
※ちなみに、送信先のURLなどは設定ファイルに書かれることが多いと思います。これはDIなどでフィールド変数に持っていることが多いと思うのでそちらを直接使えばいいと思います。もちろん、各種RequestDtoに入れてもいいですが・・・
各種RequestDtoはURLごとに分けた方がいい
これをやると結構反発があることがあります。その理由は・・・
「クラス数めっちゃ増えません?」
というものです。
それはもちろん、ある場合には当てはまりますが、当てはまらない場合もあります。
当てはまる場合というのは、明確な根拠なく何となくクラスを分けようとしている場合です。
明確な意味意義なくクラスを分けると後からメンテするのが大変です・・・・
反対に当てはまらない場合というのは、明確な根拠がある場合です。
意味のない巨大DTOを作ることを防ぐ目的があります。
例えば、UserSearchService#execute(DTO)が3個の情報をJSONで要求しているとします。で、UserRegisterService#execute(DTO)は全く異なる情報を4つ、JSONで要求していましたという場合・・・
DTOのフィールド数は7個になります。
だからどうしたという感じですか?こんな感じですね。
この図ではどのクラス用かを図示しているのでわかりますね。
ここにUserSearchServiceの改修により引数が追加された場合どうなるでしょうか。param8が追加されますね。
これを後から参入した技術者がみた時、どのフィールドがUserSearchService用のものかわかりますか?
UserSearchServiceを新規に使おうとした時、実装者はRequestDtoのどのフィールドに値をセットすれば良いのでしょうか??
この考え方は、SOLID原則のOPEN-CLOSE原則や単一責任の原則にも絡んできますので是非気に留めてください!
APIへの通信処理をこんな風な作りにしておくと、例えば別のAPIを追加する時や、既存のAPIのIFを変更する場合も、非常に軽量なコードで完了することができる様になります。