はじめに
Webアプリケーションの設計レビューをしていると、
誰もが一度はこんな構造に出会います。
Controller
└ OrderService
├ UserService
│ └ AuthService
│ └ RoleService
├ CampaignService
│ └ DiscountService
└ MailService
└ TemplateService
一見すると
- Controller は薄い
- Service に責務を分離している
- 再利用も意識している
―― ように見えます。
しかし実際には、
どこから読めばいいか分からない
状態に陥っていることが多いです。
この記事では、この状態を 「サービス地獄」 と呼び、
- なぜ起きるのか
- なぜ辛いのか
どうすれば避けられるのか
を、図とコードで整理します。
サービス地獄とは
Service が Service を呼び始め、
依存関係がツリーではなくツタになる状態
です。
本来期待していた姿(ツリー)
Controller
└ OrderService
├ OrderRepository
└ MailSender
- 上から下に読める
- 依存の向きが一方向
- 流れが一目で分かる
現実に起きがちな姿(ツタ)
OrderService
├ UserService
│ └ AuthService
│ └ RoleService
├ CampaignService
│ └ DiscountService
└ MailService
└ TemplateService
- Service が Service を呼ぶ
- 依存が横にも斜めにも伸びる
- 読むたびに別ファイルへジャンプする
なぜ「地獄」なのか
①処理の流れが追えない
OrderService を読んでいるはずなのに、
- UserService に飛び
- AuthService に飛び
- RoleService に飛ぶ
本筋が見えなくなります。
②変更影響範囲が分からない
「この Service、どこから呼ばれてる?」
- IDE の参照検索が前提
- 安全に直せない
- 結果、誰も触らなくなる
③テストが重くなる
new OrderService(
new UserService(
new AuthService(
new RoleService(...)
)
),
new CampaignService(
new DiscountService(...)
)
)
- モックが大量
- DI が複雑
- テストが本体より難しい
なぜ起きるのか
原因① Service の定義が曖昧
多くの現場で Service はこう定義されます。
Controller にロジックを書かないための層
これは 否定形の定義 です。
- Controller に書かない
- Repository に書かない
- Entity にも書かない
結果、
じゃあ全部 Service に置くしかない
となります。
原因② 再利用=Service という誤解
_userService.CanOrder(user);
便利です。 しかしこの一行が、
- Service → Service 依存
- 責務の混在
を生みます。
よくあるコード例
public class OrderService
{
private readonly UserService _userService;
public void PlaceOrder(int userId)
{
var user = _userService.Get(userId);
if (!_userService.CanOrder(user))
throw new Exception();
// 注文登録
}
}
この時点で、
- 取得
- ルール
- フロー
が Service に混在しています。
抜け出す方向性
① Service は「流れ」だけにする
public void PlaceOrder()
{
// ①取得
// ②判定
// ③登録
// ④通知
}
意味を持たせないのがポイントです。
② ルールは Service の外へ
public class User
{
public bool CanPlaceOrder()
{
return IsActive && !IsBanned;
}
}
if (!user.CanPlaceOrder())
throw new Exception();
- Service 依存が消える
- 意味がコードに残る
③ 再利用したくなったら警戒する
再利用したくなった瞬間は、
Service から出す合図
- Entity
- Policy
- Specification
を検討します。
まとめ
サービス地獄は 設計を真面目にやった結果、誰もが踏む
- 問題は Service の数ではない
- 問題は Service に意味を詰め込みすぎたこと
ツリーで描ける設計は、頭でも追える
もしツタになっていたら、
- Service を疑う
- ルールの置き場所を疑う
そこが改善のスタート地点です。