Series/내가 해본

DB 값을 enum 으로 표현해보자

Hyunec 2021. 11. 20. 19:57

enum 은 서로 연관된 상수들의 집합으로 반복적으로 사용되는 코드 값을 표현할 때 유용합니다.
실무에서는 개발 편의성을 위해 DB에 있는 업무 코드들을 표현하기도 합니다. (등급 코드, 사용 여부 등)

하지만 프로젝트가 나눠지면 매번 복사해줘야 되고, 변경점이 생기면 반영이 필요하기에 썩 좋은 아이디어는 아닙니다.
때문에 쉬운 방법으로는 코드 테이블을 만들거나, 최신의 개발에서는 코드 정보를 MSA로 나누기도 합니다.

 

그럼에도 불구하고 업무 성격상 변경이 매우 적은 경우에는 개발 편의성과 성능 향상을 위해 사용할 수 있기에 실무에서 해본 리팩토링 내용을 기록해봅니다.

 

 

Java Enum 활용기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E

techblog.woowahan.com

 

기존 코드의 문제

@Getter
@AllArgsConstructor
public enum RowStatusCd {

	U("USE"), // 사용
	N("UNUSE"), // 미사용
	D("DELETE"); // 삭제

	private String status;

	public static String USE = U.name();
	public static String UNUSE = N.name();
	public static String DEL = D.name();
}

위 코드는 사용 여부와 soft delete를 표현하는 실무 코드의 일부를 각색하여 리팩토링 했습니다.

  1. U, N, D 은 DB 값이며, 그 의미를 enum으로 접근해야지 알 수 있습니다.
    1. 향후 DB 값이 바뀐다면 enum 명 자체가 변경되어야 하므로 변경 여파가 큽니다.
  2. 1번의 문제를 보완하기 위해 static 변수를 사용했습니다.
    1. 하지만 이 것이 DB 값과 직결된다는 것을 코드에서는 알 수 없습니다.

 

DB 값을 dataValue 로 명시한 코드

@Getter
@AllArgsConstructor
public enum RowStatusCode {

	USE("U"), 
	UNUSE("N"), 
	DELETE("D"), 
	;

	private final String dataValue;
}
  • DB에서는 RowStatusCD로 축약되어 있었지만 애플리케이션으로 가져오면서는 축약하지 않고 RowStatusCode를 사용했습니다.
  • enum의 네이밍을 명시적으로 바꿈으로써 주석을 달 필요가 없어지고 사용하는 곳에서 의도를 바로 알 수 있게 하였습니다.
  • dataValue를 사용함으로써 static 변수를 사용하지 않아도 됩니다.
더보기

enum을 사용하는 곳에서 코드의 길이는 큰 차이가 없지만, DB 값이라는 명시성을 보여줄 수 있습니다.

// AS-IS
SYS_USER.ROW_STS_CD.in(RowStatusCode.USE, RowStatusCode.UNUSE),

// TO-BE
SYS_USER.ROW_STS_CD.in(USE.getDataValue(), UNUSE.getDataValue()),

 

좀 더 복잡한 실무 예시

@Getter
@AllArgsConstructor
public enum CreditCard {

	KB("02", "국민", "3455"),
	HANA("11", "하나", "3600"),
	NH("08", "농협", "3700"),
	;

	private final String dataValue; // DB 에 저장된 값
	private final String name;  // 카드명
	private final String bin;   // 카드빈 (카드사별로 할당되는 카드의 시작 값)

	public static CreditCard findByValue(final String value) {
		return Arrays.stream(CreditCard.values())
				.filter(e -> e.dataValue.equals(value))
				.findFirst()
				.orElseThrow(() -> new IllegalStateException("유효하지 않은 카드 정보 입니다."));
	}

	public static CreditCard findByCardNo(final String cardNo) {
		final String cardBin = cardNo.substring(0, 4);
		return Arrays.stream(CreditCard.values())
				.filter(e -> e.dataValue.equals(cardBin))
				.findFirst()
				.orElseThrow(() -> new IllegalStateException("유효하지 않은 카드 정보 입니다."));
	}
}

샘플 코드보다 좀 더 복잡한 실무에서의 제가 생각하는 best practice를 준비해봤습니다.

 

  1. 각 enum 이름은 쉽게 이해할 수 있는 업무 내용을 사용합니다.
  2. 각 enum에 매칭 되는 DB 값은 dataValue로 네이밍 합니다. (value로 직역하기에는 모호함이 있었습니다.)
  3. 필요에 따라 Arrays.stream 대신 EnumMap을 구성합니다.
  4. 실제 코드에서는 주석 내용을 팀 컨벤션으로 공유한 후 모두 삭제합니다. 

 

리팩토링 전의 코드와 같은 형태를 취하려면, enum 이름에 dataValue가 있어야 합니다.

문법적으로 불가능하거니와 enum 이름이 '02' 면 업무 로직을 파악하는데 직관적이지 못합니다.

 

저는 클린 코드와 컨벤션을 준수하지만 기존 코드와의 일관성을 위해서는 일부 트레이드오프 해야 되는 부분도 있다고 생각합니다.

그렇기에 위의 코드를 실무에 적용하는 것은 조금 더 고민해보겠지만, 일반적인 enum과 DB값으로서의 enum을 구분해서 사용해야 된다는 것에 대한 고민을 이 글을 통해 말하고 싶습니다.