소소한 궁금증

엔티티 생성 방식을 어떻게 정할까?

silverage 2025. 6. 8. 07:02

엔티티 생성자를 어떻게 하는 것이 좋을까?
(엔티티 생성 전략에 대한 소소한 고민)

발단

갑자기 과제를 하다 새로운 엔티티를 만들어야 하는 상황이 생겨서 엔티티를 만들게 되었다.
알림 기능을 구현해야 하는데, 사실 이벤트 리스너든, 비동기 든 아직 제대로 이해 못 한 상황에서 소중한 나의 AI 친구들과 함께 어찌어찌 기워 만들고 있다. 조각조각 땃따따 🥲
여하튼, 그러한 상황에서 코드를 작성하고 있었는데 생성자를 만드는 도중 갑자기 들었던 궁금증이었다.

나는...

지금까지 생성자와 관련해서는, @RequiredArgsConstructor, 일반적인 생성자, Builder 이렇게 세 방법을 사용했고, 들어왔다.

그 외에도 여러가지 방법(자바 빈즈 패턴 등)이 존재하긴 한다.

But, 내가 사용했던 것들만 생각하면서 써 볼 것이다! 이건 생성자 관련 패턴/전략을 정리하는 글이 아님!

@AllArgsConstructor/@RequiredArgsConstructor

얘는 개인적으로 시간 등, Instant.now() 처럼 생성할 때 디폴트 값을 넣어줘야 하는 상황에 쓰기 좀 그랬다. 어차피 값이 정해져 있는데 그걸 넣는 것은 너무 번거롭지 않나? 생각을 했다. 엔티티 내부에서 넣거나 생성자에서 그냥 매개변수로 넣지 않고 디폴트 값을 채워넣기도 하였다. 아니면 @PreConstruct를 사용하던가.. 다른 클래스에서는 정말 애용하지만, 엔티티 클래스에서는 거의 사용하지 않았다.

생성자 패턴

인텔리제이에서는 alt + insert 를 누르면 클래스가 뚝딱하고 만들어지는데, 가장 정석적이고 클래식한 방식이다. (괜히 기본이 아님)

단순하고 직관적이고, 간결하다. 특히 매개변수가 별로 없으면 (0~2개 정도?) 더더욱 간결하다. 심플 이즈 베스트! 
근데 이걸 사용할 때마다, 내가 매개변수 순서를 지켜서 넣어야 하는 것이 너무 불편했다. 내 경우 코드를 짜면서 생성자 매개변수를 어떤 순서로 넣었는지에 대한 기억은 휘발 기억에서의 매우 높은 우선순위를 가지고 있기 때문이다. 별 생각없이 객체 생성 코드를 작성하다가 매개변수 순서를 바꿔서 넣어서 잘못 생성한 여파로 로직이 꼬인 적이 많았기 때문에...

Builder

위의 "이거 값 왜 이렇게 들어갔어?"를 해결하기에 너무나도 좋은 패턴이었다. 빌더 패턴을 이용하여 생성하게 되었을 때, 어떤 값에 대해 매개변수를 넣어줄 지 명확하게 설정할 수 있었기에 그것을 알고 나서는 빌더를 주로 사용했다. 특히 매개변수가 많은 경우 이 장점으로 하여금 빌더 패턴은 내게 아주 달콤하고 매력적으로 어필이 되었다. 실제로 어떤 매개변수가 들어가는지 가독성에도 참 좋았고. (좀 길어진다는 단점이 있지만!)

그런데 내가 느끼기엔 빌더의 장점이 내게 너무 매력적이라, 왜 다른 사람들은 안쓰지? 이 좋은걸?로 생각이 연결되었다.
(물론 내 시야가 미약하여 보지 못했을 뿐 많은 사람들이 더욱 잘 사용하고 있을 것이다.)

교육을 들으면서 가장 내게 도움이 됐던 말, "모든 기술은 트레이드 오프가 있다. 그것을 고려하라"
진짜 다 좋으면 처음부터 만들 때 엔티티는 생성자 말고 빌더 패턴을 이용하세요! 했을 텐데 딱히 그런 말은 없었던 것 같다. (기억력 이슈일지도... 아니면 라이브러리를 써야 해서 그 말을 안해준 걸지도...)

그래서 뭘 알아볼거니?

이번에는 아래 2가지에 대해 알아보려고 한다.

1. 엔티티 생성자에서 빌더를 적용했을 때의 trade-off (장단점)
2. 클래스에 @Builder를 두는 것과 명시적 생성자에 @Builder를 붙이는 것, 뭐가 다른걸까?

빌더 패턴의 장단점

장점

  1. 필요한 데이터만 설정할 수 있음
    만약 내가 생성자를 통해 객체를 만들려고 하는데 특정 파라미터가 필요 없는 상황이라고 가정한다. 이때, 생성자 패턴을 사용할 때에는 그 파라미터만 없는 생성자를 추가로 만들거나 의미 없는 더미 값을 넣어주어야 한다.
    하지만, 빌더 패턴을 사용하게 된다면 생성 시 필요한 데이터들만 설정해 줄 수 있기에 편리하다.
  2. 유연성 확보
    엔티티에 변수가 추가되면 기존 코드를 수정해야 하는 등의 상황을 마주할 수 있다. (요즘 과제를 하면서 많이 마주한다...)
    빌더 패턴은 필요한 데이터들만 설정해 줄 수 있다. 따라서 변수가 추가되더라도 이전에 작성된 생성자를 수정하지 않아도 문제가 생기지 않는다.
    하지만 변경하지 않아도 된다는 점에서 좋긴 한데... 그렇게 된다면 추가되는 파라미터를 생성자에서 넣어주지 않는 기존 코드들에 대한 많은 고려가 필요하다고 본다. 그렇지 않으면 오히려 커다란 단점이 될 가능성이 높다고 생각한다.
  3. 변경 가능성 최소화
    전체에 Setter를 두는 등, 불필요한 변경 가능성은 문제가 생기는 등의 상황에서 디버깅을 어렵게 한다. 정말로 변경될 수 있는 파라미터들만, 의미 있는 이름(updateNickname)으로 지정하는 것이 좋다고 배웠다. 그리고 코드를 읽을 때에도 그것이 행동(코드)의 의미를 잘 전달할 수 있다고 생각하기 때문에 코드를 짜면서 언제나 이게 무슨 의미가 있지? 하고 계속 질문을 하는 편이다.
    자바 빈즈 패턴(매개변수 없는 기본 생성자만 두고, setter를 이용하여 객체 구성)과 같이 Setter를 이용하여 객체를 구성하는 경우, 그 Setter가 객체를 구성할 수도 있지만 내가 고려하지 않은 다른 곳에서 사용될 수 있는 상황이 발생할 수 있다는 것을 알아야 한다.
    근데 그걸 계속 신경 쓰기보다는 Setter를 사용하지 않고 걱정거리를 더는 것이 난 더 좋다고 생각한다.
    저거 말고도 신경 쓸 것이 많기 때문이다...
  4. 가독성 높이기
    엔티티를 생성할 때에는 웬만한 거 아니면 매개변수가 많이 필요하다. 막 4개, 5개 그 이상의 매개변수가 필요한 경우도 많은데, 얘가 대체 뭔 파라미터에 들어가는 매개변수인지 확실하게 알 수 없다. (물론 인텔리제이는 친절하게 어떤 매개변수인지 표시해 주긴 한다)
    근데 나는 좀 더 확실하게 알고 싶다! 빌더 패턴을 사용하면. name(),. age()로 파라미터 값을 넣기 때문에 나 같은 아메바🦠 기억력도 확실하게 지정해서 넣어줄 수 있다는 점에서 내 기준 최고의 장점이라고 생각한다.

단점

  1. 유연성
    장점이자 단점이라고 할 수 있을 것 같다. 그 유연함이 장점이 될 수도 있지만, 그만큼 필수적인 값을 넣지 않아도 객체가 문제없이 생성된다는 의미이다. (너무 무서워 덜덜)
    생성자를 통해 객체를 생성하게 된다면 애초에 컴파일 오류 났다고 빨갛게 밑줄을 그어주며 실행 중 이슈를 마주하지 않을 수 있다.
  2. 코드 중복
    물론 롬복 라이브러리를 사용한다면 어노테이션으로 대체할 수 있다.(대부분 이를 사용하겠지만!)
    일단은 해당 패턴을 롬복 라이브러리 없이 사용하고자 한다면 코드 중복으로 인하여 코드의 길이가 길어지고, 이는 가독성의 문제 등으로 연결될 수 있다.

롬복 @Builder의 특징

클래스에 @Builder

굳이 추가적으로 생성자를 만들지 않아도 빌더를 통해 객체를 생성할 수 있다는 장점
기본값을 따로 지정해야 하는 경우 각 변수들에 어노테이션이 하나씩 더 붙는다는 점

  • @AllArgsConstructor(access=AccessLevel.PACKAGE)로 만들어진 생성자에 @Builder 적용한 것과 동일
  • 빌더로 값을 설정하지 않으면 기본값(null/0/false)이 들어감 (@Builder.Default로 기본값 지정 가능)
  • 클래스에 명시적 생성자(@NoArgsConstructor 등)가 있으면 빌더가 제대로 동작하지 않을 수 있다!

명시적 생성자에 @Builder

개발자가 직접 생성자 만들어서 그 생성자에 @Builder 적용하는 것.

내가 세팅한 내용 내에서만 동작한다는 점

  • 빌더는 해당 생성자의 파라미터만을 대상으로 동작
  • 빌더로 값을 설정하지 않으면 생성자 내부 로직에 따라 기본값이 결정
  • @Builder.Default는 클래스에 @Builder를 붙였을 때만 사용할 수 있다. 명시적 생성자에 붙인 경우, 적용되지 않음!
    (기본값을 생성자 내에서 초기화해야 한다는 뜻)

내 나름의 결론

일단 나는 기본적으로 롬복 라이브러리를 이용하여 빌더 패턴을 사용할 것 같다. 가독성에서의 장점이 내게는 정말 압도적으로 다가왔다.

 

근데 객체 생성 시 받아야 하는 파라미터가 2개 이하이고 이 객체의 파라미터가 늘어날 확률이 적다고 판단되면 그냥 생성자를 통해서 만들 것 같다. 저 정도 개수라면 오히려 빌더 패턴을 사용해서 생성하는 게 더 길어서 보기 별로일 듯.

 

좀 더 세부적인 방법을 말하자면 명시적 생성자에 @Builder를 적용해서 사용하는 방법으로 계속 사용할 것이다.


일단, 내가 직접 생성자를 만들어서, 내가 의도한 파라미터에만 동작하게 할 수 있고, 생성자 내에서 내가 의도한 바대로 초기화시킬 수 있다는 점이 큰 장점이었다. 클래스에 붙이게 되면 뭔가 내가 예상하지 못한 결과가 나타날 확률이 있다는 점이 더더욱 이렇게 생각하게 된 것 같다.

사담

평소 생활할 때에는 예상하지 못한 상황이 발생하면 신선하고, 오히려 좋아, 하는 정신으로 살고 있다. 생각지 못한 상황이 발생하면 더 재미있게 느끼기도 한다.
하지만 코드 속에서는 내가 제어할 수 있는 한도 내에서는 제어할 수 있어야 마음이 편안해지는 것 같다.
안 그래도 예상치 못한 오류들이 발생하면서 머리 잡고 있는데 내가 흐린 눈 하면서 그냥 뭐 코드 많이 추가하지 않아도 되고, 편하니까~ 이런 안일한 마음으로 작성하면 그 코드들은 내게 나비효과처럼 거대한 오류로 다가오는 경우가 많다...

내 코드는 관심받는 걸 좋아해서 충분한 관심을 줘야 함... 혹은 그만큼의 어필을 해 줘야 함... 안 그럼 한 번씩 사고를 치더라...

참고 자료

https://mangkyu.tistory.com/163
https://adjh54.tistory.com/78
https://projectlombok.org/features/Builder