早期リターン+例外処理+共通レスポンスを活かしたアーキテクチャ設計ベストプラクティス
ここまでのサンプルをさらに発展させて、サービス層とコントローラ層を分離した設計を紹介します。これは実務でよく使われる「レイヤードアーキテクチャ」の考え方です。
なぜ分離するのか?
- コントローラ層 → 入力チェック、レスポンス生成、例外ハンドリング
- サービス層 → 業務ロジック(ビジネスルール)の実装
- メリット
- コードの責務が明確になる
- テストがしやすい(サービス層だけをユニットテスト可能)
- 保守性が高まる(UIやAPI仕様が変わってもサービス層は再利用できる)
サンプル構成
コントローラ層(入力チェック+レスポンス)
public class UserController {
private final UserService userService;
public UserController(UserService service) {
this.userService = service;
}
public Response getUserProfile(Request req) {
// --- 早期リターンで入力チェック ---
if (req == null) return errorResponse(400, "リクエストが空です");
if (req.getUserId() == null || req.getUserId().isEmpty()) {
return errorResponse(400, "ユーザーIDが必要です");
}
// --- サービス層呼び出し+例外処理 ---
try {
UserProfile profile = userService.fetchProfile(req.getUserId());
if (profile == null) {
return errorResponse(404, "ユーザーが見つかりません");
}
return successResponse(200, "成功", profile);
} catch (IOException e) {
return errorResponse(503, "外部サービスエラー: " + e.getMessage());
} catch (Exception e) {
return errorResponse(500, "予期せぬエラー: " + e.getMessage());
}
}
private Response errorResponse(int code, String message) {
return new Response(code, message);
}
private Response successResponse(int code, String message, Object data) {
return new Response(code, message, data);
}
}
Javaサービス層(業務ロジック)
public class UserService {
private final ExternalApi api;
public UserService(ExternalApi api) {
this.api = api;
}
public UserProfile fetchProfile(String userId) throws IOException {
// 業務ロジック:外部APIからプロフィール取得
return api.fetchProfile(userId);
}
}
Javaテスト設計の観点
- サービス層テスト
fetchProfile("validId")→ 正常なプロフィールを返す- APIモックで
IOExceptionを投げる → 例外が伝播する
- コントローラ層テスト
req = null→ 400エラーuserId = ""→ 400エラー- サービス層が
nullを返す → 404エラー - サービス層が例外を投げる → 503 or 500エラー
👉 責務が分離されているので、サービス層はユニットテスト、コントローラ層は統合テストと役割を分けられる。
まとめ
- 早期リターン: コントローラ層で入力チェックを即終了。
- 例外処理: コントローラ層で外部要因を安全にキャッチ。
- サービス層: 業務ロジックを担当し、テストや再利用が容易。
- 共通レスポンス: エラーメッセージや成功レスポンスを統一して保守性アップ。
✅ この設計は「読みやすく・安全で・テストしやすい」コードを実現するためのベストプラクティスです。
