/serverpod:exception — 백엔드 오류 처리 자동 설계 도우미#
| 항목 | 내용 |
|---|---|
| 실행 명령 | /serverpod:exception |
| 별칭 | /backend:exception, /api:error |
| 모델 | sonnet |
| 사용 도구 | Read, Edit, Write, Glob, Grep |
| 연계 스킬 | serverpod |
한마디로#
서버에서 문제가 생겼을 때 "무엇이 왜 잘못됐는지" 깔끔하게 알려주는 안내 체계를 자동으로 만들어 줍니다. 건물마다 비상구 표지판·소화기·안내 방송을 규격대로 설치해 두는 것과 같아요. 사고가 나도 당황하지 않고 정해진 방식대로 대응할 수 있게 미리 갖춰 두는 일입니다.
누가·언제 쓰나요#
- 백엔드 개발자가 새 기능(Feature)에 들어갈 오류 처리·입력값 검증·상수 정의를 한 번에 만들고 싶을 때
/serverpod:exception명령을 직접 실행할 때- 전체 기능을 한꺼번에 만드는
/cc-flutter:feature:create과정 중 자동으로 호출될 때 (1.5단계)
무엇을 해주나요#
특정 기능 폴더 안에 오류 처리에 필요한 파일 묶음을 규격대로 생성합니다.
- exception 폴더 — 오류 종류별 클래스와 오류 메시지 모음 (예: 데이터를 못 찾음, 입력값이 잘못됨, DB 작업 실패)
- validation 폴더 — 입력값이 올바른지 검사하는 Validator
- constant 폴더 — HTTP 상태 코드·페이지 크기 같은 고정값 모음
- helper 폴더 — 오류를 일관되게 감싸 주는
exception_handler.dart
즉, "어떤 상황에서 어떤 오류를 어떻게 알릴지"를 기능마다 같은 방식으로 표준화해 줍니다.
어떻게 쓰나요#
이 명령은 두 가지 정보(기능 이름, 엔티티 이름)를 받아 동작하며, 일부는 끄고 켤 수 있습니다.
# Validator와 Constants를 함께 생성 (기본 동작)
feature_name = book (기능 이름, 소문자_언더스코어)
entity_name = Book (엔티티 이름, 첫 글자 대문자)
# 검증 클래스 생성을 건너뛰기
include_validator = false
# 상수 클래스 생성을 건너뛰기
include_constants = false
feature_name(필수): 기능 모듈 이름, 소문자+언더스코어 형식entity_name(필수): 엔티티 이름, 첫 글자 대문자 형식include_validator(선택): Validator 생성 여부, 기본값 trueinclude_constants(선택): Constants 생성 여부, 기본값 true
안에서 무슨 일이 벌어지나요#
정해진 규격(패턴)에 맞춰 오류 처리 파일들을 차례로 만들어 둡니다.
- 오류 종류 정의 — "못 찾음 / 입력값 오류 / 파라미터 오류 / DB 실패"처럼 상황별 오류를 분류하고, 각각에 HTTP 상태 코드를 붙입니다.
- 오류 메시지 정리 — 사람이 읽을 수 있는 메시지 문구를 한곳에 모아 두어, 어디서든 같은 문구를 쓰게 합니다.
- 고정값 정리 — HTTP 코드, 페이지 크기 한계, 필드 이름 등 자주 쓰는 값을 상수로 모읍니다.
- 입력값 검증 — ID나 페이징 값이 규칙에 맞는지 확인하고, 어긋나면 명확한 오류를 냅니다.
- 안전 실행 감싸기 — 작업을
safeExecute()로 감싸, 예상 못 한 오류도 정해진 형태로 변환해 일관되게 처리합니다. - 점검 — sealed class 적용, 모든 오류에 상태 코드 포함, 문서 주석 작성 등 체크리스트로 마무리 확인합니다.
⚙️ 상세 옵션·실행 명세 (개발자 / AI 에이전트용)
Role#
Implements the exception handling system for the Serverpod backend.
- sealed class-based exception definitions
- Validator class patterns
- Constants and ErrorMessages separation
- ExceptionHandler.safeExecute() wrapping
Activation Conditions#
- Activated on
/serverpod:exceptioncommand invocation - Called in Step 1.5 of
/cc-flutter:feature:createorchestration
Parameters#
| Parameter | Required | Description |
|---|---|---|
feature_name | ✅ | Feature module name (snake_case) |
entity_name | ✅ | Entity name (PascalCase) |
include_validator | ❌ | Generate Validator (default: true) |
include_constants | ❌ | Generate Constants (default: true) |
Generated Files#
backend/kobic_server/lib/src/feature/{feature_name}/
├── exception/
│ ├── exception.dart # Export file
│ ├── {feature_name}_exception.dart # Exception classes
│ └── {feature_name}_error_messages.dart # Error messages
├── validation/
│ ├── validation.dart # Export file
│ └── {feature_name}_validator.dart # Validation classes
├── constant/
│ ├── constant.dart # Export file
│ └── {feature_name}_constants.dart # Constants classes
└── helper/
├── helper.dart # Export file
└── exception_handler.dart # Exception handlerImport Order (Required)#
// 1. Dart standard libraries
import 'dart:core';
// 2. Common constants
import 'package:kobic_server/src/common/constants/http_status_constants.dart';
// 3. Feature internal constants/messages
import '../constant/constant.dart';
Core Patterns#
1. sealed class Exception Definition#
/// Base class for {Feature}-related exceptions
sealed class {Feature}Exceptions implements Exception {
const {Feature}Exceptions(
this.message,
this.statusCode, [
this.stackTrace,
]);
final String message;
final int statusCode;
final StackTrace? stackTrace;
}
/// {Feature} resource not found
final class {Feature}NotFoundException extends {Feature}Exceptions {
{Feature}NotFoundException(int resourceId)
: super(
{Feature}ErrorMessages.notFound('{feature}', resourceId),
{Feature}Constants.httpNotFound,
);
}
/// {Feature} validation failure
final class {Feature}ValidationException extends {Feature}Exceptions {
{Feature}ValidationException(String reason)
: super(
reason,
{Feature}Constants.httpBadRequest,
);
}
/// {Feature} parameter validation failure
final class Invalid{Feature}ParameterException extends {Feature}Exceptions {
Invalid{Feature}ParameterException(String parameter, String reason)
: super(
{Feature}ErrorMessages.invalidParameter(parameter, reason),
{Feature}Constants.httpBadRequest,
);
}
/// {Feature} database operation failure
final class {Feature}DatabaseOperationException extends {Feature}Exceptions {
{Feature}DatabaseOperationException(
String operation,
String errorMessage, [
StackTrace? stackTrace,
]) : super(
{Feature}ErrorMessages.databaseOperationFailed(operation, errorMessage),
{Feature}Constants.httpInternalServerError,
stackTrace,
);
}
2. ErrorMessages Class#
/// {Feature} error message definitions
class {Feature}ErrorMessages {
const {Feature}ErrorMessages._();
/// Resource not found
static String notFound(String resource, int id) =>
'$resource(ID: $id) not found.';
/// Parameter validation failure
static String invalidParameter(String param, String reason) =>
'$param: $reason';
/// Database operation failure
static String databaseOperationFailed(String operation, String error) =>
'Database operation failed ($operation): $error';
/// Required parameter missing
static const String idRequired = 'ID is required.';
static const String idTooSmall = 'ID must be 1 or greater.';
/// Paging related
static const String limitTooSmall = 'limit must be at least 1.';
static const String limitTooLarge = 'limit must be at most 100.';
static const String offsetNegative = 'offset must be 0 or greater.';
}
3. Constants Class#
/// {Feature} related constants
class {Feature}Constants {
const {Feature}Constants._();
// ============ HTTP Status Codes ============
static const int httpOk = HttpStatusConstants.ok; // 200
static const int httpCreated = HttpStatusConstants.created; // 201
static const int httpBadRequest = HttpStatusConstants.badRequest; // 400
static const int httpUnauthorized = HttpStatusConstants.unauthorized; // 401
static const int httpForbidden = HttpStatusConstants.forbidden; // 403
static const int httpNotFound = HttpStatusConstants.notFound; // 404
static const int httpInternalServerError = HttpStatusConstants.internalServerError; // 500
// ============ Paging Constants ============
static const int defaultPageLimit = 20;
static const int maxPageLimit = 100;
static const int minPageLimit = 1;
static const int defaultOffset = 0;
// ============ ID Related ============
static const int minValidId = 1;
// ============ Field Names (for validation) ============
static const String fieldId = 'id';
static const String fieldLimit = 'limit';
static const String fieldOffset = 'offset';
// ============ DB Operation Names ============
static const String dbOperationInsert = '{feature} insert';
static const String dbOperationUpdate = '{feature} update';
static const String dbOperationDelete = '{feature} delete';
static const String dbOperationSelect = '{feature} select';
}
4. Validator Class#
/// {Feature} input validation
class {Feature}Validator {
const {Feature}Validator._();
/// Validate ID (optional)
static void validateId(int? id) {
if (id != null && id < {Feature}Constants.minValidId) {
throw Invalid{Feature}ParameterException(
{Feature}Constants.fieldId,
{Feature}ErrorMessages.idTooSmall,
);
}
}
/// Validate ID (required)
static void validateRequiredId(int? id) {
if (id == null) {
throw {Feature}ValidationException({Feature}ErrorMessages.idRequired);
}
validateId(id);
}
/// Validate paging parameters
static void validatePagingParams(int limit, int offset) {
if (limit < {Feature}Constants.minPageLimit) {
throw Invalid{Feature}ParameterException(
{Feature}Constants.fieldLimit,
{Feature}ErrorMessages.limitTooSmall,
);
}
if (limit > {Feature}Constants.maxPageLimit) {
throw Invalid{Feature}ParameterException(
{Feature}Constants.fieldLimit,
{Feature}ErrorMessages.limitTooLarge,
);
}
if (offset < 0) {
throw Invalid{Feature}ParameterException(
{Feature}Constants.fieldOffset,
{Feature}ErrorMessages.offsetNegative,
);
}
}
/// Null check for object
static void validateNotNull<T>(T? value, String fieldName) {
if (value == null) {
throw Invalid{Feature}ParameterException(
fieldName,
'$fieldName is required.',
);
}
}
}
5. ExceptionHandler#
/// Exception handling wrapper
class ExceptionHandler {
const ExceptionHandler._();
/// Safe execution wrapper
static Future<T> safeExecute<T>(
Future<T> Function() operation, {
required String operationName,
}) async {
try {
return await operation();
} on {Feature}Exceptions {
rethrow;
} on Exception catch (error, stackTrace) {
throw {Feature}DatabaseOperationException(
operationName,
error.toString(),
stackTrace,
);
}
}
}
Reference Files#
backend/kobic_server/lib/src/feature/banner/exception/banner_exception.dart
backend/kobic_server/lib/src/feature/banner/constant/banner_constants.dart
backend/kobic_server/lib/src/feature/banner/validation/banner_validator.dart
backend/kobic_server/lib/src/common/constants/http_status_constants.dartChecklist#
- Apply sealed class pattern
- Include statusCode in all exceptions
- Separate ErrorMessages into methods/constants
- Reference HttpStatusConstants from Constants
- Clear messages on Validator failure
- Apply ExceptionHandler.safeExecute() pattern
- Write KDoc comments