「例外階層の設計テンプレート(アプリ全体で統一する例外方針)」は、
チーム開発や長期運用の現場で必須の“例外ポリシー設計”です。
ここでは、
- アプリ全体で共通化する例外階層図
- 分類ルール(業務/システム/基盤)
- 例外ハンドリング方針
を、Spring Boot を前提に図+コード+表でまとめます。
1. 目的:例外を“伝える”仕組みを整理する
┌──────────────────────────┐
│ 目的:どの層で例外を扱い、どう返すかを統一する │
│──────────────────────────│
│ Controller :レスポンス変換(catch) │
│ Service :業務・システム例外をthrow │
│ Repository :DB系例外をwrapしてthrow │
└──────────────────────────┘
2. 例外階層全体図(アプリ共通設計)
BaseAppException (抽象基底クラス)
├── BusinessException // 業務ロジック例外(想定内)
│ ├── ValidationException // 入力・形式エラー
│ ├── RuleViolationException // 業務ルール違反
│ └── AuthorizationException // 権限不足
│
└── SystemException // システム障害(想定外)
├── DatabaseException // DB接続・SQL障害
├── ExternalApiException // 外部連携失敗
└── ConfigException // 設定欠落・環境異常
3. コード例:ベースクラスと派生クラス
BaseAppException(全ての共通基底)
public abstract class BaseAppException extends RuntimeException {
private final String errorCode;
protected BaseAppException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
JavaBusinessException(想定内の業務例外)
public class BusinessException extends BaseAppException {
public BusinessException(String message, String code) {
super(message, code);
}
}
JavaSystemException(想定外・技術的例外)
public class SystemException extends BaseAppException {
public SystemException(String message, String code, Throwable cause) {
super(message, code);
initCause(cause);
}
}
Javaサブクラス例(業務・技術別)
public class ValidationException extends BusinessException {
public ValidationException(String message) {
super(message, "VALIDATION_ERROR");
}
}
public class DatabaseException extends SystemException {
public DatabaseException(String message, Throwable cause) {
super(message, "DB_ERROR", cause);
}
}
Java4. ハンドリング方針(Spring Bootでの統一例)
Springの @ControllerAdvice を使い、
例外を一括でレスポンス変換します。
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// --- 業務例外 ---
@ExceptionHandler(BusinessException.class)
public ResponseEntity<?> handleBusiness(BusinessException e) {
log.warn("[業務例外] {} : {}", e.getErrorCode(), e.getMessage());
return ResponseEntity
.badRequest()
.body(Map.of("error", e.getErrorCode(), "message", e.getMessage()));
}
// --- システム例外 ---
@ExceptionHandler(SystemException.class)
public ResponseEntity<?> handleSystem(SystemException e) {
log.error("[システム例外] {} : {}", e.getErrorCode(), e.getMessage(), e);
return ResponseEntity
.internalServerError()
.body(Map.of("error", e.getErrorCode(), "message", "内部エラーが発生しました"));
}
// --- 想定外のその他例外 ---
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleUnknown(Exception e) {
log.error("[想定外例外] {}", e.getMessage(), e);
return ResponseEntity
.internalServerError()
.body(Map.of("error", "UNKNOWN", "message", "予期しないエラーが発生しました"));
}
}
Java5. 各層の責務と例外方針
| 層 | 役割 | 投げる例外 | キャッチ | 備考 |
|---|---|---|---|---|
| Controller | 入出力・レスポンス変換 | なし | ✅ 例外をキャッチ | エラーレスポンス返却 |
| Service | 業務ロジック中心 | BusinessException / SystemException | ❌ | 想定外はthrow |
| Repository | DB・外部APIアクセス | DatabaseException / ExternalApiException | ❌ | SQLException等をwrap |
6. 安全設計ポイント
| 原則 | 内容 |
|---|---|
| ✅ 「想定内」=業務エラー | → BusinessException(400系) |
| ✅ 「想定外」=技術障害 | → SystemException(500系) |
| ⚠️ 「全部Exceptionでまとめない」 | → 障害分析が困難になる |
| 🧱 例外は上層で変換(wrap) | → 下層の実装に依存しない |
🧩 @ControllerAdviceで一括変換 | → レスポンス仕様を統一 |
7. 実務パターン図(例外の流れ)
[Controller]
↓
call
↓
[Service]
↓
throw new ValidationException(...)
↓
@ControllerAdvice
↓
return HTTP 400
[Repository]
↓
SQLException発生
↓
catch → throw new DatabaseException(...)
↓
[Service] → throw上位へ
↓
@ControllerAdvice → return HTTP 500
8. 運用ベストプラクティスチェックリスト
| チェック項目 | 推奨 |
|---|---|
| 例外の共通基底クラスがある | ✔ |
| errorCode で一意に識別可能 | ✔ |
| 例外ハンドラが一括管理 | ✔ |
| 「想定内」「想定外」を明確に区別 | ✔ |
| ログレベルが適切(warn / error) | ✔ |
| 呼び出し層で再throwせず、wrapで伝達 | ✔ |
9. まとめ:統一設計の狙い
┌────────────────────────────┐
│ 例外階層の統一で得られる効果 │
│────────────────────────────│
│ ✅ 障害原因の分類が明確 │
│ ✅ ログ解析・監視が容易 │
│ ✅ チーム内で設計基準が統一 │
│ ✅ 新人が例外処理で迷わない │
└────────────────────────────┘
