このページに書いてあること
この記事やこの記事でかいていたクラス分割について、個人的にこうすればいいんじゃない?という内容を書きます。
概念を示してくれる記事は多いのですが、実際のクラス分けとなるとわからなくなってしまう場合が多いので、参考程度に役立てていただければ。
前提
- Java8以上
- 通信はHTTPS/JSON(どの方式でもあんまり関係ない)
- Javaのルールで説明するが、C#でも同じ。
サンプルシステム
ユーザーからリクエストを受け、別アプリのユーザー情報を操作するAPIと認証を実施するAPIを順次呼んでいきます。
ユーザー取得をして、認証をするというイメージです。イメージは以下。
今回開発するのは「開発対象」と書いています。
クラス構成を検討(1)
クラスを設計していきましょう。
前述の記事でも書いたとおり、SOLID原則のうちのISPやDSPを意識。
まずはざっくりこんな感じ。ポイントは以下の通り。
- Controllerパッケージ内にServicesパッケージを用意し、Controllerが使用するServiceクラスのインタフェースを配置。←依存性逆転の原則(DSP)
- APIに対する操作は、APIのサービスごとに用意する。上の例では、ユーザー情報返却アプリと認証システム用にそれぞれIFを用意した。今はIFの形は同じだが、接続システムが違うため分離。←インタフェース分離の原則(ISP)
- 取得や登録といった1機能ごとに用意する。これにより、登録に変更が必要になった場合は登録クラスの実装を変更するだけで、他クラスには一切影響を与えずに修正できる。←単一責任の原則(SRP)
前の記事でも書きましたが、DSPとISPを守っておけば、大抵はSRPなどの他のSOLID原則も充足することができます。
で、ControllerはDIによってIFから実装クラスを差し込んでいくことができれば、クラス間はかなり疎結合を保つことができます。
上記の例では省略していますが、全てのServiceクラスはControllerとDTO経由で情報をやり取りするようにしています。
明確な理由がありまして、ここはController層とService層を中継する必要があります。つまり、ここは層の分離を図るために疎結合というか柔軟性を持たせたいわけです。以下個別に検証。
上記例のUserSearchServiceにIFに引数を1個追加したい場合を考えます。
この場合、実際の引数をIFが要求していた場合大変なことになります。
(見易さのために、関係ないクラスは消しました)
この時にIFの引数をUserSearchServiceに追加したい場合大変なことになります。
IFをいじると、全ての実装クラスに引数を追加しないといけません。UserRegistServiveは変更する必要がないのに!!これはオープンクローズの原則に反するのではないでしょうか。
そこで、IF自体は総称型で引数や戻り値を規定しておき、実装側で引数や戻り値の型を確定するのです。
これにより、IFの修正は引数に渡しているDTOの修正に矮小化されます。
どうです!よくないですか??
最初の例通り、UserSearchServiceの引数をUserSearchRequestDtoとしていた場合、引数を追加したい場合はこのDTOのフィールドを追加してやればいいのです。
UserRegisterServiceには一切影響を及ぼしません。
少し長くなったので、ここで分割します。
次はServiceパッケージ内をさらにいけてる設計にして、修正に強くしましょう。