Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ“ μ—λŸ¬ μ½”λ“œ 곡톡화 및 μΈν„°νŽ˜μ΄μŠ€ μž‘μ„± #4

Merged
merged 8 commits into from
Mar 12, 2024

Conversation

psychology50
Copy link
Member

@psychology50 psychology50 commented Mar 12, 2024

μž‘μ—… 이유

  • 사전에 μ •μ˜ν•œ Error Document λ¬Έμ„œμ— λŒ€ν•œ μΈν„°νŽ˜μ΄μŠ€ 생성
  • 곡톡 μ—λŸ¬ 응닡 μΈν„°νŽ˜μ΄μŠ€λ₯Ό ν†΅ν•œ μ˜ˆμ™Έ 응닡 λ©”μ‹œμ§€ 톡일화
  • λͺ…μ‹œμ μΈ 주석을 ν†΅ν•œ 개발 생산성 ν–₯상

μž‘μ—… 사항

βœ’οΈ μš”μ•½

  • Base Codeκ°€ 될 StatusCode, ReasonCode μ—΄κ±° νƒ€μž… 클래슀 μž‘μ„±.
  • Common λͺ¨λ“ˆμ—μ„œ 관심사가 μ•„λ‹Œ DomainCode와 FieldCodeλŠ” μΈν„°νŽ˜μ΄μŠ€ν™”.
    • DomainCode : 도메인 λͺ¨λ“ˆμ—μ„œ ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ ν•˜λ‚˜μ˜ μ—΄κ±° νƒ€μž… 클래슀둜 κ΄€λ¦¬ν•œλ‹€.
    • FieldCode : 각 Domain λ³„λ‘œ ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ μ—΄κ±° νƒ€μž… 클래슀λ₯Ό μž‘μ„±ν•˜λ„λ‘ ν•œλ‹€.
  • μ˜ˆμ™Έμ— λŒ€ν•œ 정보λ₯Ό λ‹΄κ³  μžˆλŠ” CausedBy record μž‘μ„±
  • GlobalExceptionHandlerμ—μ„œ 곡톡 μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ GlobalErrorException μž‘μ„±
    • ν•΄λ‹Ή μ˜ˆμ™Έλ₯Ό μ‚¬μš©ν•˜λ €λŠ” λͺ¨λ“  ν•˜μœ„ μ»€μŠ€ν…€ μ˜ˆμ™Έ ν΄λž˜μŠ€λ“€μ€ BaseErrorCodeλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.
    • λͺ…μ‹œμ μœΌλ‘œ μ˜ˆμ™Έλ₯Ό κ΅¬λΆ„ν•˜κΈ° μœ„ν•΄ GlobalErrorException을 상속받을 수 μžˆλ‹€.
      • ex. AuthErrorException, DomainErrorException
  • λͺ¨λ“  μ—λŸ¬λŠ” μž„μ˜λ‘œ μƒμ„±ν•˜μ§€ μ•Šκ³  νŒ€μ›κ³Ό νšŒμ˜ν•œ ν›„ λ°˜λ“œμ‹œ λ¬Έμ„œμ— κΈ°λ‘ν•œλ‹€. (쀑볡 μ—λŸ¬ 제거λ₯Ό μœ„ν•¨)

1️⃣ Base Code : StatusCode

/**
 * HTTP μƒνƒœ μ½”λ“œ
 */
@Getter
@RequiredArgsConstructor
public enum StatusCode {
    SUCCESS(200),
    CREATED(201),
    NO_CONTENT(204),

    BAD_REQUEST(400),
    UNAUTHORIZED(401),
    FORBIDDEN(403),
    NOT_FOUND(404),
    METHOD_NOT_ALLOWED(405),
    NOT_ACCEPTABLE(406),
    CONFLICT(409),
    PRECONDITION_FAILED(412),
    UNPROCESSABLE_CONTENT(422),

    INTERNAL_SERVER_ERROR(500),
    SERVICE_UNAVAILABLE(503);

    private final int code;
}
  • 전체 응닡 μ½”λ“œ 7bit 쀑 μƒμœ„ 3bit에 ν•΄λ‹Ήν•˜λŠ” μƒνƒœκ°’μ— ν•΄λ‹Ήν•œλ‹€.
  • κ°€μž₯ λŒ€ν‘œμ μΈ 14가지 μƒνƒœκ°’μ΄ ν¬ν•¨λ˜μ–΄ μžˆλ‹€.
  • ν΄λΌμ΄μ–ΈνŠΈκ°€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ κ³„μΈ΅μ—μ„œ μƒνƒœλ₯Ό 확인 κ°€λŠ₯ν•˜λ„λ‘ ν•˜κΈ° μœ„ν•œ 정보.

2️⃣ Base Code : ReasonCode

/**
 * μ—λŸ¬ λ°œμƒ 이유 μ½”λ“œ
 */
@Getter
@RequiredArgsConstructor
public enum ReasonCode {
    /* 400_BAD_REQUEST */
    INVALID_REQUEST_SYNTAX(0),
    MISSING_REQUIRED_PARAMETER(1),
    MALFORMED_PARAMETER(2),
    MALFORMED_REQUEST_BODY(3),

    /* 401_UNAUTHORIZED */
    MISSING_OR_INVALID_AUTHENTICATION_CREDENTIALS(0),
    EXPIRED_OR_REVOKED_TOKEN(1),
    INSUFFICIENT_PERMISSIONS(2),
    TAMPERED_OR_MALFORMED_TOKEN(3),

    /* 403_FORBIDDEN */
    ACCESS_TO_THE_REQUESTED_RESOURCE_IS_FORBIDDEN(0),
    IP_ADDRESS_BLOCKED(1),
    USER_ACCOUNT_SUSPENDED_OR_BANNED(2),
    ACCESS_TO_RESOURCE_NOT_ALLOWED_FOR_USER_ROLE(3),

    /* 404_NOT_FOUND */
    REQUESTED_RESOURCE_NOT_FOUND(0),
    INVALID_URL_OR_ENDPOINT(1),
    RESOURCE_DELETED_OR_MOVED(2),

    /* 405_METHOD_NOT_ALLOWED */
    REQUEST_METHOD_NOT_SUPPORTED(0),
    ATTEMPTED_TO_ACCESS_UNSUPPORTED_METHOD(1),

    /* 406_NOT_ACCEPTABLE */
    REQUESTED_RESPONSE_FORMAT_NOT_SUPPORTED(0),

    /* 409_CONFLICT */
    REQUEST_CONFLICTS_WITH_CURRENT_STATE_OF_RESOURCE(0),
    RESOURCE_ALREADY_EXISTS(1),
    CONCURRENT_MODIFICATION_CONFLICT(2),

    /* 412_PRECONDITION_FAILED */
    PRECONDITION_REQUEST_HEADER_NOT_MATCHED(0),
    IF_MATCH_HEADER_OR_IF_NONE_MATCH_HEADER_NOT_MATCHED(1),

    /* 422_UNPROCESSABLE_CONTENT */
    REQUIRED_PARAMETERS_MISSING_IN_REQUEST_BODY(0),
    VALIDATION_ERROR_IN_REQUEST_BODY(1),
    ;

    private final int code;

    @Override
    public String toString() {
        return name();
    }
}
  • 사전에 λͺ…μ‹œν•œ Error Convention을 λ”°λ₯Έλ‹€.
  • μ˜ˆμ™Έ μ’…λ₯˜μ— 따라 Domain Code와 Field Code 확인 ν•„μš” μ—¬λΆ€κ°€ λ‹€λ₯Ό 수 μžˆλ‹€.
  • μΆ”κ°€ ν˜Ήμ€ μˆ˜μ • 및 μ‚­μ œ μ˜ˆμ™Έ ν•­λͺ©μ΄ λ°œμƒν•  경우, νŒ€ 회의λ₯Ό 톡해 κ²°μ •ν•˜λ„λ‘ ν•œλ‹€.

3️⃣ Base Code : DomainCode

/**
 * 도메인 μ½”λ“œ
 * <br>
 * 도메인 μ½”λ“œλŠ” 각 λ„λ©”μΈλ³„λ‘œ κ³ μœ ν•œ μ½”λ“œλ₯Ό 가지고 있으며, ν•΄λ‹Ή μ½”λ“œλŠ” 각 λ„λ©”μΈλ³„λ‘œ κ³ μœ ν•œ μ—λŸ¬λ₯Ό κ΅¬λΆ„ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€.
 * <br>
 * ZERO(0)λŠ” κΈ°λ³Έ μ½”λ“œλ‘œ μ‚¬μš©λ˜λ©°, 도메인 μ½”λ“œλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” κ²½μš°μ— μ‚¬μš©ν•œλ‹€.
 * <pre>
 * ZERO ν•„λ“œλŠ” λͺ¨λ“  BaseErrorCodeλ₯Ό κ΅¬ν˜„ν•˜λŠ” λͺ¨λ“  도메인 μ½”λ“œμ—μ„œ μ •μ˜λ˜μ–΄μ•Ό ν•œλ‹€.
 * {@code
 * @RequiredArgsConstructor
 * public enum DomainBitCode implements DomainCode {
 *      ZERO(0),
 *      USER(1),
 *      PRODUCT(2),
 *      ORDER(3);
 *
 *      private final int code;
 *
 *      @Override
 *      public int getCode() {
 *          return code;
 *      }
 *
 *      @Override
 *      public String getDomainName() {
 *          return name().toLowerCase();
 *      }
 * }
 * }
 * </pre>
 * @author YANG JAESEO
 */
public interface DomainCode {
    int getCode();
    String getDomainName();
}
  • Domain Codeλ₯Ό ꡬ뢄해야 ν•˜λŠ” μ—λŸ¬ 응닡에 ν•„μˆ˜μ μœΌλ‘œ ν¬ν•¨λ˜μ–΄μ•Ό ν•œλ‹€.
  • Domain bitλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” μ—λŸ¬ 응닡을 μœ„ν•΄ λ°˜λ“œμ‹œ ZERO(0)을 ν¬ν•¨ν•œλ‹€.
  • Domain λͺ¨λ“ˆμ—μ„œ ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ—¬, Entity bitλ₯Ό μ„€μ •ν•œλ‹€.
    public enum DomainBitCode implements DomainCode {
        ZERO(0), USER(1), FEED(2), NOTIFICATION(3), ...; // Entity λ³„λ‘œ bitκ°’ κ²°μ •
    
        private final int code;
        // ... ν•˜μœ„ λ©”μ„œλ“œ μΆ”κ°€
    }

4️⃣ Base Code : FieldCode

/**
 * λ„λ©”μΈμ˜ ν•„λ“œ μ½”λ“œ
 * <pre>
 * {@code
 * @RequiredArgsConstructor
 * public enum UserFieldCode implements FieldCode {
 *    ZERO(0),
 *    ID(1),
 *    PASSWORD(2),
 *    NAME(3);
 *
 *    private final int code;
 *
 *    @Override
 *    public int getCode() {
 *      return code;
 *    }
 *
 *    @Override
 *    public String getFieldName() {
 *      return name().toLowerCase();
 *    }
 * }
 * }
 * </pre>
 * @author YANG JAESEO
 */
public interface FieldCode {
    int getCode();
    String getFieldName();
}
  • Domain λͺ¨λ“ˆμ—μ„œ ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ—¬, Entity λ³„λ‘œ μ˜ˆμ™Έ μ²˜λ¦¬μ— ν•„μš”ν•œ bit값을 μ„€μ •ν•œλ‹€.
  • Entity λ‚΄μ˜ λͺ¨λ“  속성이 ν¬ν•¨λ˜μ§€ μ•Šμ„ 수 있으며, ν‘œν˜„ κ°€λŠ₯ν•œ 수의 λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λŠ” 경우 16μ§„μˆ˜λ‘œ 변경될 수 μžˆλ‹€.
  • bitλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” μ—λŸ¬ 응닡을 μœ„ν•΄ λ°˜λ“œμ‹œ ZERO(0)을 ν¬ν•¨ν•œλ‹€.
    public enum UserCode implements FieldCode {
        ZERO(0), NAME(1), USERNAME(2), PASSWORD(3), ...; // Attribute λ³„λ‘œ bitκ°’ κ²°μ •
    
        private final int code;
        // ... ν•˜μœ„ λ©”μ„œλ“œ μΆ”κ°€
    }

5️⃣ CausedBy

/**
 * μ—λŸ¬ μ½”λ“œλ₯Ό κ΅¬μ„±ν•˜λŠ” 상세 μ½”λ“œ
 *
 * @param statusCode {@link StatusCode} μƒνƒœ μ½”λ“œ
 * @param reasonCode {@link ReasonCode} 이유 μ½”λ“œ
 * @param domainCode {@link DomainCode} 도메인 μ½”λ“œ
 * @param fieldCode {@link FieldCode} ν•„λ“œ μ½”λ“œ
 *
 * - see also: {@link StatusCode}, {@link ReasonCode}, {@link DomainCode}, {@link FieldCode}
 */
public record CausedBy(
    StatusCode statusCode,
    ReasonCode reasonCode,
    DomainCode domainCode,
    FieldCode fieldCode
) {
    private static final int STATUS_CODE_MULTIPLIER = 10000;
    private static final int REASON_CODE_MULTIPLIER = 1000;
    private static final int DOMAIN_CODE_MULTIPLIER = 10;

    public CausedBy {
        Objects.requireNonNull(statusCode, "statusCode must not be null");
        Objects.requireNonNull(reasonCode, "reasonCode must not be null");
        Objects.requireNonNull(domainCode, "domainCode must not be null");
        Objects.requireNonNull(fieldCode, "fieldCode must not be null");

        if (!isValidCodes(statusCode.getCode(), reasonCode.getCode(), domainCode.getCode(), fieldCode.getCode())) {
            throw new IllegalArgumentException("Invalid bit count");
        }
    }

    /**
     * CausedBy 객체λ₯Ό μƒμ„±ν•˜λŠ” 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ
     * <br/>
     * λͺ¨λ“  μ½”λ“œμ˜ μ‘°ν•©μœΌλ‘œ μƒμ„±λœ μ΅œμ’… μ½”λ“œλŠ” 7자리의 λ¬Έμžμ—΄λ‘œ κ΅¬μ„±λœλ‹€.
     * @param statusCode {@link StatusCode} μƒνƒœ μ½”λ“œ (3자리)
     * @param reasonCode {@link ReasonCode} 이유 μ½”λ“œ (1자리)
     * @param domainCode {@link DomainCode} 도메인 μ½”λ“œ (1자리 or 2자리)
     * @param fieldCode {@link FieldCode} ν•„λ“œ μ½”λ“œ (1자리)
     * @throws IllegalArgumentException 전체 μ½”λ“œκ°€ 7μžλ¦¬κ°€ μ•„λ‹Œ 경우, ν˜Ήμ€ 각 μƒνƒœ μ½”λ“œκ°€ 자릿수λ₯Ό μ€€μˆ˜ν•˜μ§€ μ•Šμ€ 경우
     * @throws NullPointerException μΈμžκ°€ null인 경우
     * @return CausedBy
     */
    public static CausedBy of(StatusCode statusCode, ReasonCode reasonCode, DomainCode domainCode, FieldCode fieldCode) {
        return new CausedBy(statusCode, reasonCode, domainCode, fieldCode);
    }

    /**
     * status code, reason code, domain code, field codeλ₯Ό μ‘°ν•©ν•˜μ—¬ μ—λŸ¬ μ½”λ“œλ₯Ό μƒμ„±ν•œλ‹€.
     * @return String : 7자리 μ •μˆ˜λ‘œ κ΅¬μ„±λœ μ—λŸ¬ μ½”λ“œ
     */
    public String getCode() {
        return generateCode();
    }

    /**
     * μ—λŸ¬κ°€ λ°œμƒν•œ 이유λ₯Ό λ°˜ν™˜ν•œλ‹€.
     * <br/>
     * Reason은 사전에 μ˜ˆμ™Έ λ¬Έμ„œμ— λͺ…μ‹œν•œ 정보λ₯Ό λ°˜ν™˜ν•œλ‹€.
     * @return String : μ—λŸ¬κ°€ λ°œμƒν•œ 이유
     */
    public String getReason() {
        return reasonCode.name();
    }

    private String generateCode() {
        return String.valueOf(statusCode.getCode() * STATUS_CODE_MULTIPLIER + reasonCode.getCode() * REASON_CODE_MULTIPLIER + domainCode.getCode() * DOMAIN_CODE_MULTIPLIER + fieldCode.getCode());
    }

    private boolean isValidCodes(int statusCode, int reasonCode, int domainCode, int fieldCode) {
        return isValidDigit(statusCode, 3) && isValidDigit(reasonCode, 1) && (isValidDigit(domainCode, 1) || isValidDigit(domainCode, 2)) && isValidDigit(fieldCode, 1);
    }

    private boolean isValidDigit(int number, long expectedDigit) {
        return calcDigit(number) == expectedDigit;
    }

    private long calcDigit(int number) {
        if (number == 0) return 1;
        return Stream.iterate(number, n -> n > 0, n -> n / 10)
            .count();
    }
}
  • 곡톡 μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€λŠ” μΈν„°νŽ˜μ΄μŠ€μ— λ‹΄μ•„μ•Ό ν•˜λŠ” μ˜ˆμ™Έ 정보 ν•­λͺ©.
  • ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ λ°˜ν™˜ν•˜κΈ° μœ„ν•œ μ˜ˆμ™Έ 응닡 μ½”λ“œ 7bit 정보λ₯Ό λ‹΄κ³  μžˆλ‹€.
  • 속성 쀑 ν•˜λ‚˜λΌλ„ null이 λ“€μ–΄μ˜€λ©΄ NPEκ°€ λ°œμƒν•œλ‹€.
  • 전체 code μ‘°ν•© λ¬Έμžμ—΄μ΄ 7자리λ₯Ό λ§Œμ‘±ν•˜μ§€ μ•ŠλŠ” 경우, ν˜Ήμ€ 각 ν•„λ“œ 쀑 ν•˜λ‚˜λΌλ„ 자릿수λ₯Ό μ€€μˆ˜ν•˜μ§€ μ•ŠλŠ” 경우 IllegalArgumentException이 λ°œμƒν•œλ‹€.
    • statusCode : μ–Έμ œλ‚˜ 3자리 μ •μˆ˜κ°’μ„ κ°–λŠ”λ‹€.
    • reasonCode : μ–Έμ œλ‚˜ 1자리 μ •μˆ˜κ°’μ„ κ°–λŠ”λ‹€.
    • domainCode : 1자리 ν˜Ήμ€ 2자리 μ •μˆ˜κ°’μ„ κ°–λŠ”λ‹€.
    • fieldCode : μ–Έμ œλ‚˜ 1자리 μ •μˆ˜κ°’μ„ κ°–λŠ”λ‹€.
  • CausedBy 객체λ₯Ό 생성할 λ•ŒλŠ” μƒμ„±μž ν˜Ήμ€ 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•œλ‹€.
    CausedBy causedBy = new CausedBy(...);
    // ν˜Ήμ€
    CausedBy causedBy = CausedBy.of(...);

6️⃣ BaseErrorCode

/**
 * μ»€μŠ€ν…€ μ—λŸ¬ μ½”λ“œλ₯Ό μ •μ˜ν•˜κΈ° μœ„ν•œ μΈν„°νŽ˜μ΄μŠ€
 * <br>
 * μ—λŸ¬ μ½”λ“œλŠ” λ‹€μŒκ³Ό 같은 ν˜•μ‹μœΌλ‘œ μ •μ˜ν•œλ‹€.
 * <br>
 * <pre>
 * {@code
 * @Getter
 * @RequiredArgsConstructor
 * public enum UserErrorCode implements BaseErrorCode {
 *      NOT_FOUND_EMAIL(StatusCode.NOT_FOUND, ReasonCode.ACCESS_TO_THE_REQUESTED_RESOURCE_IS_FORBIDDEN, FieldCode.EMAIL, "ν•΄λ‹Ή μœ μ €μ˜ 이메일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."),
 *      NOT_FOUND_USER(StatusCode.NOT_FOUND, ReasonCode.ACCESS_TO_THE_REQUESTED_RESOURCE_IS_FORBIDDEN, FieldCode.ZERO, "ν•΄λ‹Ή μœ μ €κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."),
 *
 *      REQUIRED_EMAIL(StatusCode.UNPROCESSABLE_CONTENT, ReasonCode.REQUIRED_PARAMETERS_MISSING_IN_REQUEST_BODY, FieldCode.EMAIL, "이메일은 ν•„μˆ˜ μž…λ ₯κ°’μž…λ‹ˆλ‹€."),
 *
 *      private final StatusCode statusCode;
 *      private final ReasonCode reasonCode;
 *      private final FieldCode fieldCode;
 *      private final String message;
 *
 *      @Override
 *      public CausedBy causedBy() {
 *          return CausedBy.valueOf(statusCode, reasonCode, DomainCode.USER, FieldCode.COMMON);
 *      }
 *
 *      @Override
 *      public String getExplainError() throws NoSuchFieldError {
 *          return message;
 *      }
 * }
 * }
 * </pre>
 * μ—λŸ¬ μ½”λ“œλŠ” λ‹€μŒκ³Ό 같은 ν˜•μ‹μœΌλ‘œ μ‚¬μš©ν•œλ‹€.
 * <pre>
 * {@code throw new GlobalErrorException(UserErrorCode.NOT_FOUND_USER); }
 * </pre>
 * See Also:
 * {@link CausedBy}, {@link StatusCode}, {@link ReasonCode}, {@link DomainCode}, {@link FieldCode}, {@link GlobalErrorException}
 * @author YANG JAESEO
 */
public interface BaseErrorCode {
    CausedBy causedBy();
    String getExplainError() throws NoSuchFieldError;
}
  • GlobalErrorException으둜 μ˜ˆμ™Έλ₯Ό λ˜μ§€κΈ° μœ„ν•œ μ»€μŠ€ν…€ μ˜ˆμ™Έκ°€ λ°˜λ“œμ‹œ κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€
  • 2가지 λ©”μ„œλ“œλ‘œ κ΅¬μ„±λ˜μ–΄ μžˆλ‹€.
    • causedBy() : μ˜ˆμ™Έ 정보인 CausedBy 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œ
    • getExplainError() : μ—λŸ¬ 둜그 ν˜Ήμ€ ν΄λΌμ΄μ–ΈνŠΈ 츑에 μ—λŸ¬ 이유λ₯Ό μ„€λͺ…ν•˜κΈ° μœ„ν•œ λ©”μ„œλ“œ.
  • ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” λͺ¨λ“  μ»€μŠ€ν…€ μ˜ˆμ™ΈλŠ” λ°˜λ“œμ‹œ 두 가지 λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.
  • explainError의 μš©λ„λŠ” μΆ”ν›„ νŒ€ 회의λ₯Ό 톡해 λͺ…ν™•νžˆ ν•˜λ„λ‘ ν•œλ‹€.
    • ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ λ°˜ν™˜ν•˜λŠ” λ©”μ‹œμ§€κ°€ 될 경우, μ˜ˆμ™Έ λ¬Έμ„œλ₯Ό ν™•μΈν•˜μ§€ μ•Šμ•„λ„ μ—λŸ¬ 이유λ₯Ό 확인 κ°€λŠ₯ν•˜λ„λ‘ λͺ…μ‹œμ μΈ μ—λŸ¬ 문ꡬλ₯Ό μž‘μ„±ν•œλ‹€.
    • μ„œλ²„μ—μ„œ λ‘œκΉ…μ„ μœ„ν•œ 정보가 될 경우, μ—λŸ¬ λ°œμƒ κ²½λ‘œμ™€ μ‚¬μœ  등을 ꡬ체적으둜 λͺ…μ‹œν•˜λ„λ‘ ν•œλ‹€.

7️⃣ GlobalErrorException

/**
 * μ „μ—­ μ—λŸ¬ 처리λ₯Ό μœ„ν•œ μ˜ˆμ™Έ 클래슀
 * <br>
 * μ „μ—­ μ—λŸ¬ 처리λ₯Ό λͺ…μ‹œμ μΈ μ—λŸ¬ 처리둜 λ³€κ²½ν•˜κ³  μ‹Άλ‹€λ©΄ λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•œλ‹€.
 * <pre>
 * {@code
 * public class DomainErrorException extends GlobalErrorException {
 *    private final DomainErrorCode errorCode;
 *
 *    public DomainErrorException(DomainErrorCode errorCode) {
 *      super(errorCode);
 *      this.errorCode = errorCode;
 *    }
 *
 *    public CausedBy causedBy() { return errorCode.causedBy(); }
 *    public BaseErrorCode getErrorCode() { return errorCode; }
 * }
 * }
 * </pre>
 * @author YANG JAESEO
 */
@Getter
public class GlobalErrorException extends RuntimeException {
    private final BaseErrorCode baseErrorCode;

    public GlobalErrorException(BaseErrorCode baseErrorCode) {
        super(baseErrorCode.causedBy().reasonCode().name());
        this.baseErrorCode = baseErrorCode;
    }

    public CausedBy causedBy() {
        return baseErrorCode.causedBy();
    }

    @Override
    public String toString() {
        return "GlobalErrorException(code=" + baseErrorCode.causedBy().getCode()
                + ", message=" + baseErrorCode.getExplainError() + ")";
    }
}
  • μΆ”ν›„ @RestControllerAdviceλ₯Ό 톡해 μ „μ—­ μ—λŸ¬λ₯Ό ν•Έλ“€λ§ν•˜κΈ° μœ„ν•œ μ˜ˆμ™Έ 객체.
  • BaseErrorCodeλ₯Ό κ΅¬ν˜„ν•˜λŠ” λͺ¨λ“  μ»€μŠ€ν…€ μ—λŸ¬κ°€ κ³΅ν†΅μ μœΌλ‘œ μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.
  • λͺ…μ‹œμ μœΌλ‘œ μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚€λŠ” 이유λ₯Ό κ΅¬λΆ„ν•˜κ³  μ‹Άλ‹€λ©΄, ν•΄λ‹Ή 클래슀λ₯Ό μƒμ†ν•œλ‹€.
    • AuthErrorException
    • DomainErrorException
    • 기타 λ“±λ“±.

리뷰어가 μ€‘μ μ μœΌλ‘œ 확인해야 ν•˜λŠ” λΆ€λΆ„

  • 전체적인 흐름 νŒŒμ•… 및 μ‚¬μš©λ²•μ„ μ΄ν•΄ν•˜μ˜€λŠ”μ§€?
  • CausedBy의 Compact Constructor λ‚΄ μ˜ˆμ™Έ μ²˜λ¦¬κ°€ νƒ€λ‹Ήν•œμ§€?
  • 이보닀 더 λ‚˜μ€ μΈν„°νŽ˜μ΄μŠ€ 섀계 방식이 μ‘΄μž¬ν•˜λŠ”μ§€?
  • BaseErrorCodeλ₯Ό κ΅¬ν˜„ν•˜λŠ” μ»€μŠ€ν…€ μ˜ˆμ™Έ ν΄λž˜μŠ€μ—μ„œ λ‹€λ£° explainError의 μ‚¬μš©μ²˜μ— λŒ€ν•œ 아이디어 ν•„μš”.
  • μΆ”κ°€ ν…ŒμŠ€νŠΈκ°€ ν•„μš”ν•œ μΌ€μ΄μŠ€κ°€ μ‘΄μž¬ν•˜λŠ”μ§€?

λ°œκ²¬ν•œ 이슈

  • λ‹€λ“€ λ°”μœ κ΄€κ³„λ‘œ 일단 혼자 μž„μ˜λ‘œ μ—λŸ¬ μ½”λ“œ μ»¨λ²€μ…˜ μ •ν•˜κ³  μΈν„°νŽ˜μ΄μŠ€ λ§Œλ“  거라, μΆ”ν›„ 회의 ν›„ μž¬μ‘°μ •λ  수 있음.

🟑 ν…ŒμŠ€νŠΈ μ½”λ“œ μΆ”κ°€
총 6가지 ν…ŒμŠ€νŠΈλ₯Ό μˆ˜ν–‰ν•˜μ˜€κ³ , μ •μƒμ μœΌλ‘œ ν†΅κ³Όν–ˆμŒμ„ ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.

  1. λͺ¨λ‘ 정상적인 인자둜 생성할 수 μžˆμŒμ„ ν™•μΈν•œλ‹€.
  2. null 인자둜 생성할 수 μ—†μŒμ„ ν™•μΈν•œλ‹€.
  3. μƒνƒœ μ½”λ“œ(3), 이유 μ½”λ“œ(1), 도메인 μ½”λ“œ(2), ν•„λ“œ μ½”λ“œ(1)κ°€ μ•„λ‹ˆλ©΄ 생성할 수 μ—†μŒμ„ ν™•μΈν•œλ‹€.
  4. μƒμ„±λœ μ½”λ“œκ°€ μ˜ˆμƒκ°’κ³Ό μΌμΉ˜ν•˜λŠ” 지 ν™•μΈν•œλ‹€.
  5. 두 자리 수의 도메인 μ½”λ“œκ°€ μ˜ˆμƒκ°’κ³Ό μΌμΉ˜ν•˜λŠ” 지 ν™•μΈν•œλ‹€.
  6. μ—λŸ¬κ°€ λ°œμƒν•œ μ˜¬λ°”λ₯Έ 이유λ₯Ό λ°˜ν™˜ν•œλ‹€.

@psychology50 psychology50 added the enhancement New feature or request label Mar 12, 2024
@psychology50 psychology50 requested a review from a team March 12, 2024 02:30
@psychology50 psychology50 self-assigned this Mar 12, 2024
@psychology50
Copy link
Member Author

λ‹€μ‹œ ν™•μΈν•΄λ³΄λ‹ˆ CausedBy 클래슀 λ²”μœ„ 주석에 μžˆλŠ” see also ν•­λͺ©μ€ ν•„μš”μ—†μ„ κ±° κ°™λ„€μš”.

Copy link
Member

@jinlee1703 jinlee1703 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ—λŸ¬ μ½”λ“œμ— λŒ€ν•œ ν…ŒμŠ€νŠΈκΉŒμ§€ μž‘μ„±ν•΄μ£Όμ‹€ 쀄 λͺ°λžλŠ”데, 고생 λ§ŽμœΌμ…¨μŠ΅λ‹ˆλ‹€!

Copy link
Collaborator

@asn6878 asn6878 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μ΄λ ‡κ²Œ μƒμ„Έν•œ μ—λŸ¬μ½”λ“œλ₯Ό μƒμ„±ν•˜λŠ” 방법은 μ²˜μŒλ΄μ„œ λ†€λžμŠ΅λ‹ˆλ‹€!
많이 λ°°μ›Œκ°‘λ‹ˆλ‹€~

@psychology50 psychology50 merged commit 23127bf into dev Mar 12, 2024
@psychology50 psychology50 deleted the feat/PW-124-common-error branch March 12, 2024 06:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants