enum의 장점은?

 

enum의 단점은?

  • code를 찾아내기 위해 복합키를 사용해야 하는 경우
    • vs DB) 코드테이블을 사용한다면 PK를 복합키로 지정하여 constraint를 이용한 무결성을 보장할 수 있다. (개발자가 DB에 새로운 코드를 추가하는 순간 알게된다.)
    • 그러나 enum에 무언가를 추가하는건, enum 이름 기반 unique만 검사하기 때문에... enum 필드에 복합키를 넣어야 한다. 그래서 이에 대한 체크를 넣더라도, 어쨌든 runtime에 가서야 enum에 값이 잘못 추가되었다는 것을 알게된다.
    • vs Map도 마찬가지다. Map은 key 자리에 복합키를 넣어줄 수 있고 이 key가 중복되면 덮어쓰거나 한다. 그러나 enum은 중복된 필드값 그대로 가지고 있기 때문에, 신경써주지 않으면 1개 값이 튀어나올 줄 알았는데 2개가 튀어나온다거나. 하는 에러가 발생할 수 있다.

 

 

code 1 : 현금, 2 : 카드 라는 정보를 관리하는 방법

  • DB에 code table을 만들어서 관리
    • 어드민 메뉴로 새로운 코드를 추가하는 것이 용이함. (삭제/수정은 constraint를 잘 써야 무결성 유지가 가능함)
    • 단점은 코드에서 code, name을 String으로 받아와 관리해야 한다. 로직은 또 따로 두어야 한다.
    • 매번 DB에서 읽어오는게 부담일 수 있다. 고작 이거 구분값 읽어오자고 DB를? 이라는 생각이 들 수도.
  • DB에 있는 코드 표를 enum으로 아예 옮겨버리는 방법 (배민 enum 활용기)
    • 코드 상에서 code와 name을 enum 타입으로 묶어서 들고다닐 수 있다. + 로직까지.
    • 매번 DB에서 읽어오는건 부담스러우나, enum으로 변환하는건 아주 마음이 가볍다.
  • 기타
    • DBMS function을 이용해 코드 변환 (매번 조인보다 낫다)
    • redis에 코드 테이블 올려두고 사용하는 방법
    • 객체 내부에 Holder를 두어 같은 내용으로 두 번 resolve 하지 않도록 캐시처럼 사용하는 방법
      • 하지만 이건 DB가 업데이트 되면 캐시도 업데이트 해주어야 한다는 문제점이 있어 좋지 않다.

 

enum을 쓰든, DB code table 을 참조하든 둘 중 하나만 하는 것이 좋은가?

  • 관리 포인트가 늘어나기 때문에 둘 중 하나만 쓰는게 좋아 보일 수 있다. 수정 사항이 발생했을 때 DB, enum 둘 다 반영해야 하므로.
  • 하지만 운영하다 보면 아래와 같은 상황이 발생하게 되는데...
    • INSERT INTO SELECT 구문을 사용하고 싶을 때. (bulk insert)
    • DB에서 해당 code table을 여기저기서 조인하여 사용해야 하는 경우
      • DB code table이 없다면 바로 query에서 처리 못하고 매번 app단으로 불러와야 함. 매우 불편
    • 해당 code table을 빈번하게 조회해야 하는 경우 (enum이 없다면 매번 code 변환을 위해 query 수행해야 함)
  • 운영 하다 보면 DB table을 사용하는게 더 편할 때도 있고, enum을 사용하는게 더 편할 때도 있다.
  • 억지로 한 쪽에서만 관리하기 보다는 상황에 따라 DB와 enum 둘 다 만들어 선택지를 주는 것이 적절해보인다.

 

enum VS properties

  • enum은 소스코드 내 상수 코드와 관련 로직을 모으기 위해 사용하고, properties는 환경(알파 베타 프로파일)에 따라 달라지는 url, config 값 같은 메타 정보를 지정하는데 사용하기 때문에 전혀 목적이 다르다.
  • enum은 소스코드이기 때문에, 변경하면 다시 컴파일해야 하는 반면 `` .yml, .properties``같이 외부로 빼면 컴파일 없이 jar 패키징만 해도 적용 가능하다...는 점을 properties 쓰는 이유로 대는 사람도 있는데, 사실 둘은 목적이 다르기 때문에 타당해보이지 않는다.
  • 목적이 다르기 때문에 단순 비교는 의미 없을지도 모르지만, 굳이 비교한다면 yml은 아래와 같은 단점이 있다.
    • 자주 변경된다면 DB에 두는 것이 낫고, syntax 힌트나 하이라이트, 링크 등을 생각하면 enum이 yml 보다 낫다.
    • yml로 관리한다해도, 이를 읽어와서 필드로 가지고 있는 객체를 만들어야 되니까 클래스는 어차피 만들어야 한다.
    • 즉, yml로 관리하는게 적절한 케이스는 하는 케이스는 간단한 설정값 등등이고, 복잡한 계층 구조나 연결 구조가 들어간다면 enum으로 빼는 것이 낫다. 

 

enum은 extends는 불가하지만, implements는 가능하다.

  • 공통 interface를 만들 때는, getter의 선언부가 인터페이스에 들어가줘야 해당 인터페이스 타입으로 접근했을 때 .getCode() 같은 메서드를 호출할 수 있다. (클래스 구현부에서는 @Getter를 써도 된다.)
 

예제

@Getter
@RequiredArgsConstructor
public enum PaymentCode {
    CASH(1, "현금"),
    CARD(2, "카드");

    private final int code;
    private final String koName;

    public static PaymentCode fromKoName(String text){
        for (PaymentCode code : values()){
            if (code.getKoName().equals(text)) {
                return code;
            }
        }
        throw new IllegalArgumentException();
    }
}
    /**
     * 위 코드와는 관련 없지만 함수형으로 쓰면 이런 식이 된다.
     */
    public static PaycoResponseCode from(int _code) {
        return Stream.of(values())
            .filter(responseCode -> responseCode.getCode() == _code)
            .findAny()
            .orElseThrow(NoSuchElementException::new);
    }

 

/* 기본 제공 */
log.info("{}", Payment.PaymentCode.valueOf("CARD"));     // CARD 아래와 같다. 이름 문자열 받아서 enum찾을 때 말고는 쓸일 없는 듯.
log.info("{}", Payment.PaymentCode.CARD);                // CARD 위와 같다.
log.info("{}", Payment.PaymentCode.CARD.name());         // CARD

/* @Getter */
log.info("{}", Payment.PaymentCode.CARD.getKoName());    // 카드
log.info("{}", Payment.PaymentCode.CARD.getCode());      // 2

/* 직접 정의. */
log.info("{}", Payment.PaymentCode.fromKoName("현금"));   // CASH

 

(중요) enum의 초기화 시점은, 애플리케이션 시작 시점이 아니라 enum의 최초 호출 시점이다.

  • 기본적으로 JVM에서 compile-time 상수가 아닌 모든 static field는 해당 class 최초 접근 시에 비로소 초기화 된다. Application 실행 시점에 초기화 되는 것이 아니다. (JVM 관련)
  • 그래서 of 연산을 위한 Map을 static 변수에 담아 둘 때, duplicate key 같은 문제는 enum이 최초로 호출되는 시점에서야 발견된다.
  • enum static에서 Duplicate Key 같은 에러가 발생할 수 있다면, 앱이 실행되고 나중에 최초 접근할 때가 되어서야 문제가 있음을 알 수 있기 때문에, TC를 추가해서 미리 CI 레벨에서 검증이 되도록 하는게 좋다.

 

'Java Stack > Java' 카테고리의 다른 글

[Effective Java] 2장 객체 생성과 파괴  (0) 2019.12.02
[Java] LocalDateTime : 날짜 시간 처리 관련  (0) 2019.07.10
[Java] Jackson ObjectMapper Serialization  (0) 2019.05.15
JVM 관련  (0) 2017.05.05
[Java] Stream API 노트  (0) 2017.03.09