agentsclimarketplace

Allra error handling

Skill ComeOnOliver/skillshub/skills/aiskillstore/marketplace/allra-fintech/allra-error-handling

🧠 The right skill, one API call. AI agent skills registry with token-efficient skill resolution. 5,000+ skills from 500+ top repos.

Install
npx -y skills add ComeOnOliver/skillshub --skill allra-error-handling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Allra λ°±μ—”λ“œ μ—λŸ¬ 핸듀링 및 μ˜ˆμ™Έ 처리 ν‘œμ€€. Use when handling errors, creating custom exceptions, or implementing error responses.

SKILL.md

10.1 KB, as published. Nobody here has run it

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을 μ²˜λ¦¬ν•˜λŠ”κ°€?

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.