| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /serverpod:exception |
| Aliases | /backend:exception, /api:error |
| Tools | Read, Edit, Write, Glob, Grep |
| Model | sonnet |
| Skills | serverpod |
/serverpod:exception โ ๋ฐฑ์๋ ์ค๋ฅ ์ฒ๋ฆฌ ์๋ ์ค๊ณ ๋์ฐ๋ฏธ#
ํ๋ง๋๋ก#
์๋ฒ์์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ "๋ฌด์์ด ์ ์๋ชป๋๋์ง" ๊น๋ํ๊ฒ ์๋ ค์ฃผ๋ ์๋ด ์ฒด๊ณ๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด ์ค๋๋ค. ๊ฑด๋ฌผ๋ง๋ค ๋น์๊ตฌ ํ์งํยท์ํ๊ธฐยท์๋ด ๋ฐฉ์ก์ ๊ท๊ฒฉ๋๋ก ์ค์นํด ๋๋ ๊ฒ๊ณผ ๊ฐ์์. ์ฌ๊ณ ๊ฐ ๋๋ ๋นํฉํ์ง ์๊ณ ์ ํด์ง ๋ฐฉ์๋๋ก ๋์ํ ์ ์๊ฒ ๋ฏธ๋ฆฌ ๊ฐ์ถฐ ๋๋ ์ผ์ ๋๋ค.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ๋ฐฑ์๋ ๊ฐ๋ฐ์๊ฐ ์ ๊ธฐ๋ฅ(Feature)์ ๋ค์ด๊ฐ ์ค๋ฅ ์ฒ๋ฆฌยท์ ๋ ฅ๊ฐ ๊ฒ์ฆยท์์ ์ ์๋ฅผ ํ ๋ฒ์ ๋ง๋ค๊ณ ์ถ์ ๋
/serverpod:exception๋ช ๋ น์ ์ง์ ์คํํ ๋- ์ ์ฒด ๊ธฐ๋ฅ์ ํ๊บผ๋ฒ์ ๋ง๋๋
/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
/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