jpskill.com
📦 その他 コミュニティ

allra-error-handling

Allraのバックエンドで発生するエラーへの対処や、独自のエラーを定義、エラー発生時のレスポンスを実装する際に、標準化された方法でエラーハンドリングと例外処理を行うSkill。

📜 元の英語説明(参考)

Allra 백엔드 에러 핸들링 및 예외 처리 표준. Use when handling errors, creating custom exceptions, or implementing error responses.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Allraのバックエンドで発生するエラーへの対処や、独自のエラーを定義、エラー発生時のレスポンスを実装する際に、標準化された方法でエラーハンドリングと例外処理を行うSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o allra-error-handling.zip https://jpskill.com/download/17137.zip && unzip -o allra-error-handling.zip && rm allra-error-handling.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17137.zip -OutFile "$d\allra-error-handling.zip"; Expand-Archive "$d\allra-error-handling.zip" -DestinationPath $d -Force; ri "$d\allra-error-handling.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して allra-error-handling.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → allra-error-handling フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] allra-error-handling

Allra Backend 에러 핸들링 표준

Allra 백엔드 팀의 에러 핸들링, 예외 처리, 로깅 표준을 정의합니다.

예외 클래스 설계

1. 비즈니스 예외 계층 구조

// 최상위 비즈니스 예외
public abstract class BusinessException extends RuntimeException {

    private final ErrorCode errorCode;

    protected BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }

    protected BusinessException(ErrorCode errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public ErrorCode getErrorCode() {
        return errorCode;
    }

    public int getStatus() {
        return errorCode.getStatus();
    }
}

// ErrorCode Enum (예시)
public enum ErrorCode {
    // 400 Bad Request
    INVALID_INPUT_VALUE(400, "E001", "잘못된 입력값입니다"),

    // 401 Unauthorized
    UNAUTHORIZED(401, "E101", "인증이 필요합니다"),
    INVALID_TOKEN(401, "E102", "유효하지 않은 토큰입니다"),

    // 403 Forbidden
    FORBIDDEN(403, "E201", "권한이 없습니다"),

    // 404 Not Found
    ENTITY_NOT_FOUND(404, "E301", "요청한 리소스를 찾을 수 없습니다"),
    USER_NOT_FOUND(404, "E302", "사용자를 찾을 수 없습니다"),

    // 409 Conflict
    DUPLICATE_RESOURCE(409, "E401", "이미 존재하는 리소스입니다"),

    // 500 Internal Server Error
    INTERNAL_SERVER_ERROR(500, "E999", "서버 내부 오류가 발생했습니다");

    private final int status;
    private final String code;
    private final String message;

    ErrorCode(int status, String code, String message) {
        this.status = status;
        this.code = code;
        this.message = message;
    }

    // getters...
}

참고: ErrorCode 체계(E001, E101 등)와 메시지 언어(한국어/영어)는 프로젝트별로 다를 수 있습니다.

2. 도메인별 예외 클래스

// 엔티티를 찾을 수 없을 때
public class EntityNotFoundException extends BusinessException {
    public EntityNotFoundException(String entityName, Long id) {
        super(ErrorCode.ENTITY_NOT_FOUND,
              String.format("%s(id=%d)을 찾을 수 없습니다", entityName, id));
    }
}

// 사용자 관련 예외
public class UserNotFoundException extends BusinessException {
    public UserNotFoundException(Long userId) {
        super(ErrorCode.USER_NOT_FOUND,
              String.format("사용자(id=%d)를 찾을 수 없습니다", userId));
    }
}

// 중복 리소스 예외
public class DuplicateResourceException extends BusinessException {
    public DuplicateResourceException(String resourceName, String field, String value) {
        super(ErrorCode.DUPLICATE_RESOURCE,
              String.format("%s의 %s=%s가 이미 존재합니다", resourceName, field, value));
    }
}

// 인증/인가 예외
public class UnauthorizedException extends BusinessException {
    public UnauthorizedException() {
        super(ErrorCode.UNAUTHORIZED);
    }
}

public class ForbiddenException extends BusinessException {
    public ForbiddenException(String message) {
        super(ErrorCode.FORBIDDEN, message);
    }
}

Global Exception Handler

@RestControllerAdvice 구현

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 비즈니스 예외 처리
    @ExceptionHandler(BusinessException.class)
    protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.warn("BusinessException: code={}, message={}",
                 e.getErrorCode().getCode(), e.getMessage());

        ErrorResponse response = ErrorResponse.of(e.getErrorCode(), e.getMessage());
        return ResponseEntity
            .status(e.getStatus())
            .body(response);
    }

    // Bean Validation 예외 처리
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) {

        log.warn("MethodArgumentNotValidException: {}", e.getMessage());

        List<ErrorResponse.FieldError> fieldErrors = e.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> new ErrorResponse.FieldError(
                error.getField(),
                error.getRejectedValue() != null ? error.getRejectedValue().toString() : null,
                error.getDefaultMessage()
            ))
            .toList();

        ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, fieldErrors);
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(response);
    }

    // 예상하지 못한 예외 처리
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("Unexpected exception occurred", e);

        ErrorResponse response = ErrorResponse.of(
            ErrorCode.INTERNAL_SERVER_ERROR,
            "서버 오류가 발생했습니다"
        );
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(response);
    }
}

에러 응답 형식 (Allra 표준)

ErrorResponse DTO

public record ErrorResponse(
    String code,
    String message,
    List<FieldError> errors,
    LocalDateTime timestamp
) {

    public static ErrorResponse of(ErrorCode errorCode) {
        return new ErrorResponse(
            errorCode.getCode(),
            errorCode.getMessage(),
            Collections.emptyList(),
            LocalDateTime.now()
        );
    }

    public static ErrorResponse of(ErrorCode errorCode, String message) {
        return new ErrorResponse(
            errorCode.getCode(),
            message,
            Collections.emptyList(),
            LocalDateTime.now()
        );
    }

    public static ErrorResponse of(ErrorCode errorCode, List<FieldError> errors) {
        return new ErrorResponse(
            errorCode.getCode(),
            errorCode.getMessage(),
            errors,
            LocalDateTime.now()
        );
    }

    public record FieldError(
        String field,
        String rejectedValue,
        String message
    ) {}
}

참고: 에러 응답 구조는 프로젝트별로 커스터마이징할 수 있습니다. 중요한 것은 일관성 있는 형식을 유지하는 것입니다.

에러 응답 예시

단일 에러:

{
  "code": "E302",
  "message": "사용자(id=123)를 찾을 수 없습니다",
  "errors": [],
  "timestamp": "2024-12-17T10:30:00"
}

**Vali

📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Allra Backend 에러 핸들링 표준

Allra 백엔드 팀의 에러 핸들링, 예외 처리, 로깅 표준을 정의합니다.

예외 클래스 설계

1. 비즈니스 예외 계층 구조

// 최상위 비즈니스 예외
public abstract class BusinessException extends RuntimeException {

    private final ErrorCode errorCode;

    protected BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }

    protected BusinessException(ErrorCode errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public ErrorCode getErrorCode() {
        return errorCode;
    }

    public int getStatus() {
        return errorCode.getStatus();
    }
}

// ErrorCode Enum (예시)
public enum ErrorCode {
    // 400 Bad Request
    INVALID_INPUT_VALUE(400, "E001", "잘못된 입력값입니다"),

    // 401 Unauthorized
    UNAUTHORIZED(401, "E101", "인증이 필요합니다"),
    INVALID_TOKEN(401, "E102", "유효하지 않은 토큰입니다"),

    // 403 Forbidden
    FORBIDDEN(403, "E201", "권한이 없습니다"),

    // 404 Not Found
    ENTITY_NOT_FOUND(404, "E301", "요청한 리소스를 찾을 수 없습니다"),
    USER_NOT_FOUND(404, "E302", "사용자를 찾을 수 없습니다"),

    // 409 Conflict
    DUPLICATE_RESOURCE(409, "E401", "이미 존재하는 리소스입니다"),

    // 500 Internal Server Error
    INTERNAL_SERVER_ERROR(500, "E999", "서버 내부 오류가 발생했습니다");

    private final int status;
    private final String code;
    private final String message;

    ErrorCode(int status, String code, String message) {
        this.status = status;
        this.code = code;
        this.message = message;
    }

    // getters...
}

참고: ErrorCode 체계(E001, E101 등)와 메시지 언어(한국어/영어)는 프로젝트별로 다를 수 있습니다.

2. 도메인별 예외 클래스

// 엔티티를 찾을 수 없을 때
public class EntityNotFoundException extends BusinessException {
    public EntityNotFoundException(String entityName, Long id) {
        super(ErrorCode.ENTITY_NOT_FOUND,
              String.format("%s(id=%d)을 찾을 수 없습니다", entityName, id));
    }
}

// 사용자 관련 예외
public class UserNotFoundException extends BusinessException {
    public UserNotFoundException(Long userId) {
        super(ErrorCode.USER_NOT_FOUND,
              String.format("사용자(id=%d)를 찾을 수 없습니다", userId));
    }
}

// 중복 리소스 예외
public class DuplicateResourceException extends BusinessException {
    public DuplicateResourceException(String resourceName, String field, String value) {
        super(ErrorCode.DUPLICATE_RESOURCE,
              String.format("%s의 %s=%s가 이미 존재합니다", resourceName, field, value));
    }
}

// 인증/인가 예외
public class UnauthorizedException extends BusinessException {
    public UnauthorizedException() {
        super(ErrorCode.UNAUTHORIZED);
    }
}

public class ForbiddenException extends BusinessException {
    public ForbiddenException(String message) {
        super(ErrorCode.FORBIDDEN, message);
    }
}

Global Exception Handler

@RestControllerAdvice 구현

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 비즈니스 예외 처리
    @ExceptionHandler(BusinessException.class)
    protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.warn("BusinessException: code={}, message={}",
                 e.getErrorCode().getCode(), e.getMessage());

        ErrorResponse response = ErrorResponse.of(e.getErrorCode(), e.getMessage());
        return ResponseEntity
            .status(e.getStatus())
            .body(response);
    }

    // Bean Validation 예외 처리
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) {

        log.warn("MethodArgumentNotValidException: {}", e.getMessage());

        List<ErrorResponse.FieldError> fieldErrors = e.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> new ErrorResponse.FieldError(
                error.getField(),
                error.getRejectedValue() != null ? error.getRejectedValue().toString() : null,
                error.getDefaultMessage()
            ))
            .toList();

        ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, fieldErrors);
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(response);
    }

    // 예상하지 못한 예외 처리
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("Unexpected exception occurred", e);

        ErrorResponse response = ErrorResponse.of(
            ErrorCode.INTERNAL_SERVER_ERROR,
            "서버 오류가 발생했습니다"
        );
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(response);
    }
}

에러 응답 형식 (Allra 표준)

ErrorResponse DTO

public record ErrorResponse(
    String code,
    String message,
    List<FieldError> errors,
    LocalDateTime timestamp
) {

    public static ErrorResponse of(ErrorCode errorCode) {
        return new ErrorResponse(
            errorCode.getCode(),
            errorCode.getMessage(),
            Collections.emptyList(),
            LocalDateTime.now()
        );
    }

    public static ErrorResponse of(ErrorCode errorCode, String message) {
        return new ErrorResponse(
            errorCode.getCode(),
            message,
            Collections.emptyList(),
            LocalDateTime.now()
        );
    }

    public static ErrorResponse of(ErrorCode errorCode, List<FieldError> errors) {
        return new ErrorResponse(
            errorCode.getCode(),
            errorCode.getMessage(),
            errors,
            LocalDateTime.now()
        );
    }

    public record FieldError(
        String field,
        String rejectedValue,
        String message
    ) {}
}

참고: 에러 응답 구조는 프로젝트별로 커스터마이징할 수 있습니다. 중요한 것은 일관성 있는 형식을 유지하는 것입니다.

에러 응답 예시

단일 에러:

{
  "code": "E302",
  "message": "사용자(id=123)를 찾을 수 없습니다",
  "errors": [],
  "timestamp": "2024-12-17T10:30:00"
}

Validation 에러:

{
  "code": "E001",
  "message": "잘못된 입력값입니다",
  "errors": [
    {
      "field": "email",
      "rejectedValue": "invalid-email",
      "message": "올바른 이메일 형식이 아닙니다"
    }
  ],
  "timestamp": "2024-12-17T10:30:00"
}

서비스 레이어에서 예외 사용

1. 엔티티 조회 시 예외 처리

@Service
public class UserService {

    private final UserRepository userRepository;

    @Transactional(readOnly = true)
    public User findUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

2. 비즈니스 로직 검증

@Service
public class UserService {

    @Transactional
    public User createUser(SignUpRequest request) {
        // 중복 체크
        if (userRepository.existsByEmail(request.email())) {
            throw new DuplicateResourceException("User", "email", request.email());
        }

        User user = User.create(request.email(), request.password());
        return userRepository.save(user);
    }

    @Transactional
    public void deleteUser(Long id, Long currentUserId) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));

        // 권한 체크
        if (!user.getId().equals(currentUserId)) {
            throw new ForbiddenException("본인의 계정만 삭제할 수 있습니다");
        }

        userRepository.delete(user);
    }
}

로깅 전략

1. 로깅 레벨

@Service
@Slf4j
public class UserService {

    // DEBUG: 개발 시 디버깅 정보
    log.debug("Finding user by id: {}", id);

    // INFO: 정상적인 비즈니스 플로우
    log.info("User created successfully: userId={}", user.getId());

    // WARN: 비즈니스 예외 (예상된 에러)
    log.warn("User not found: userId={}", id);

    // ERROR: 시스템 예외 (예상하지 못한 에러)
    log.error("Unexpected error occurred while creating user", e);
}

참고: 로깅 레벨과 형식은 프로젝트의 로깅 정책에 따라 다를 수 있습니다.

2. 로깅 포맷

// ✅ 권장: 구조화된 정보
log.info("User signup completed: userId={}, email={}, signupAt={}",
         user.getId(), user.getEmail(), LocalDateTime.now());

log.warn("Failed login attempt: email={}, reason={}",
         email, "Invalid password");

// ❌ 피하기: 단순 문자열 연결
log.info("User " + user.getId() + " signed up");

When to Use This Skill

이 skill은 다음 상황에서 자동으로 적용됩니다:

  • 커스텀 예외 클래스 생성
  • Service 레이어에서 예외 throw
  • Global Exception Handler 구현
  • 에러 응답 DTO 작성
  • 로깅 코드 작성

Checklist

에러 핸들링 코드 작성 시 확인사항:

  • [ ] 비즈니스 예외는 BusinessException을 상속하는가?
  • [ ] ErrorCode enum에 적절한 HTTP 상태 코드가 정의되었는가?
  • [ ] Global Exception Handler에 예외 처리가 추가되었는가?
  • [ ] 에러 응답이 표준 형식을 따르는가?
  • [ ] 비즈니스 예외는 WARN 레벨로 로깅하는가?
  • [ ] 시스템 예외는 ERROR 레벨로 로깅하는가?
  • [ ] 민감한 정보(비밀번호 등)가 로그에 포함되지 않는가?
  • [ ] orElseThrow를 사용해 Optional을 처리하는가?