상황 1) 외부 API 요청에 대한 응답 수신 코드로 enum을 쓰는게 좋을까?

요약 ) 외부 API 요청에 대한 응답 코드나, 내 API에 대한 요청 코드의 타입은

enum으로 정의하는 것 보다

String으로 정의하고 enum 변환하는게 더 유연하다. (fault tolerance)

 

enum에 정의 되어 있지 않은 값이 응답 코드로 들어올 수 있기 때문에 이에 대한 처리를 생각해 주어야 한다. (e.g., 예고 없이 갑자기 추가된 응답 코드)

Exception 발생 해도 상관 없는 경우) 기본적으로 Exception 발생하게 되어 있다.

기본 Exception 메시지에서 @JsonValue 기준 값이 로깅된다.
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.naver.nfcard.support.externalclients.point.dto.PgId` from String "95005": not one of the values accepted for Enum class: [80133, 80006]

 

Exception 발생 안하고 넘어가야 하는 경우) `` @JsonEnumDefaultValue``로 디폴트 enum value로 매핑할 수 있다.

public enum EnumType {
    A, B,
    @JsonEnumDefaultValue UNKNOWN;
}

하지만 디폴트값 매핑하는 경우 응답 코드에 대한 정보가 Enum으로 변환되면서 사라진다. (Enum에 정의되지 않은 응답 코드가 들어왔을 때 로깅 없이 default Enum으로 매핑된다.)

 

Exception 발생 안하고 넘어가면서, 들어온 값에 대한 로깅이 필요한 경우 ) 

  • 응답 코드 타입을 Enum으로 유지하면서, 로깅이 필요하다면 아래 세 가지 방법 사용 할 수 있다.
    1. 일단 응답을 Map으로 받아서 로깅 후 객체 변환.
      즉, [json -> Obj] 바로 매핑하면 유실되니, 로깅을 위해 [json -> Map -> Obj] 
    2. Enum Deserializer를 직접 구현해 로깅을 끼워 넣는 방법
  • 위 두 가지 방법 사용하는 것 보다, DTO에서 필드의 타입을 Enum이 아닌 String으로 변경하는 방법이 더 낫다.
    • String으로 받는 경우 [json -> Obj] 가 가능하다.
    • DTO를 로깅하고, Domain Model로 변환 할 때 ``kt Enum.valueOf(String)`` 해주면 된다.

 

이 처럼 external layer에 대한 DTO 분리는 다양한 상황에서 매우 유용하다.
[Spring] MVC Layering Architecture : DTO와 Domain Model을 분리해야 하는 이유

 

애초에 응답 코드를 enum으로 처리해야 하는 경우가 많지는 않다.

enum을 정의한다는 것은 응답 코드와 관련된 여러가지 데이터와 로직(람다)을 묶어주고 다형성을 사용하겠다는 의미인데,

응답 코드를 그렇게 까지 다루어야 하는 경우가 잘 없다.

 

응답 코드에 따른 처리 로직을 묶어주어야 한다면, 처리 로직을 DTO 안에 method로 정의하는 정도로도 충분한 경우가 대부분이다.

물론 이렇게 처리하면 분기는 생기지만, 보통은 분기를 DTO 안으로 숨길 수 있다.

 

상황 2 ) method param Type 자체를 Enum으로 정의할 것인지?
아니면 String으로 받고 validation check를 할 것인지?

상황 : loan을 실행할 대상은 반드시 Wallet이 존재해야 한다. & loan 대상은 Wallet의 subset이다.

 

방법 1. LoanTarget이라는 Enum을 정의하고 이를 파라미터로 받는 방법

enum class LoanTarget(val wallet: Wallet, ...) {
    A(Wallet.A, ...)
}

fun loan(target: LoanTarget) { ... }

 

방법 2. Enum 정의 없이, param은 String으로 받고 validation check를 하는 방법

fun loan(target: String) {
    if (!target in Wallet.names) { ... }
    ...
}

 

Wallet이나 Symbol에 대한 in check를 loan 메서드에서 수행하는 방법2 가 아니라,

(중복에도 불구하고) enum에 link 하도록 설계하는 방법1을 사용한 이유는

  1. 컴파일 타임에 실수를 알아챌 수 있다.
    • 예를 들어 Loan 대상인데 Wallet에는 누락되어 있는 경우. (LoanTarget은 반드시 Wallet의 subset이어야 한다는 제약조건)
    • 방법 1은 enum 추가하면서 알아챌 수 있지만, 방법 2는 런타임에 Exception 발생해야 알 수 있다. (심지어 if 안에서 적절히 알림을 주지 않는다면, 영영 알 수 없을 수도.)
  2. String을 Wallet, Symbol 로 캐스팅 하지 않고 member로 접근할 수 있기 때문에 코드 자체가 조금 더 의미를 가지게 된다.
  3. if 로직의 경우 이름으로 연결하겠다는 얘기인데, 상대적으로 fragile 하며 이름을 다르게 가져가야 하는 경우 수용이 안된다.