개발일지

@Embeddable 활용기

gilssang97 2021. 12. 31. 21:58

@Embeddable

이번 프로젝트에서 엔티티를 구성하는데 있어 @Embeddable을 활용하였다.

이번에 이를 활용하면서 임베디드 타입을 언제 활용하면 좋은지, 어떤 장점이 존재하는지 좀 더 자세하게 알게 되었다.

이를 이번 포스트에서 정리해보려고 한다.

먼저, 사용한 배경에 대해 먼저 설명하려고 한다.

스터디 커뮤니티에서 스터디라는 엔티티를 구성하기 위한 다양한 요소가 존재했다.

  • 스터디 작성자
  • 스터디 제목
  • 스터디 태그
  • 스터디 썸네일
  • 스터디 관련 학과
  • 스터디 시작 일자
  • 스터디 종료 일자
  • 스터디 모집 일정
  • 스터디 진행 여부
  • 스터디 진행 방식

적어도 위와 같은 요소들을 포함해야했다.

수 많은 요소들이 존재하는데 복잡한 현실 세계 속에서 이를 조금 더 추상화해보기로 했다.

그래서 공통 요소로 추출할 수 있다고 생각한 부분에 대해서는 같은 색으로 묶었다.

먼저, 파란색을 보자.

우리는 스터디를 진행할 때, 정확하지는 않지만 언제부터 언제까지 진행할지에 대한 일정을 정하곤 한다.

모두 이미 시작 전에 눈치 챘을텐데, 시작 일자와 종료 일자는 보통 묶어 일정이라고 표현한다.

이를 간단하게 묶어서 표현하면 더욱 좋을 것이다.

다음으로, 빨간색을 보자.

우리는 스터디를 모집하는데 있어 이미 모집이 끝났는지, 스터디가 진행 중인지, 스터디가 어떻게 진행되고 있는지 스터디가 어떤 옵션으로 이루어져 진행되고 있는지에 대해 명시해둬야 한다.

물론 이러한 묶음 자체가 다소 관련 없다고 여겨질 수 있는 요소기도 하지만 이러한 부분은 스터디에 대한 옵션이라는 부분에서 충분히 밀접한 관련이 있다고 생각한다.

그러한 부분에서 이를 묶어서 표현한다면 더욱 좋을 것이다.

이러한 부분이 어떤 장점이 존재하냐라는 부분은 다음과 같다.

재사용이 가능하다.

우리는 스터디 일정 뿐 아니라 시작 일자와 종료 일자가 존재하는 어떤 일정에서도 다음과 같은 부분을 적용할 수 있을 것이다.

높은 응집도를 이룬다.

비슷한 요소들끼리 뭉쳐있기에 응집도는 자연스럽게 올라가며 이러한 요소들끼리 처리해야되는 메소드가 있다면 응집된 객체에서 처리하면 되기에 자연스럽게 높은 응집도를 유지한다.

의미 있는 메소드를 만들 수 있다.

위에서 언급한 것처럼 유사한 요소들끼리 뭉쳐있기에 유사한 요소들끼리의 의미 있는 메소드를 구성할 수 있다.

생명주기를 엔티티에 의존한다.

유사한 요소들은 유사한 생명주기를 지닌다. 그러기에 엔티티에 생명주기를 의존하기에 유사한 생명주기를 공유하면서 이를 처리할 수 있게 된다.

그런데 자바에서는 위에서와 같은 여러가지 원시타입의 요소들을 포함한 객체의 형태로 이루어지지만 DB에서는 해당 요소들을 풀어서 저장한다.

이를 실제로 알아보자.

기존에 임베디드를 사용하지 않은 코드는 아래와 같던 코드였다.

public class Study extends EntityDate {    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "study_id")
    private Long id;
    private String title;
    @ElementCollection
    @CollectionTable(name = "STUDY_TAG", joinColumns = @JoinColumn(name = "study_id"))
    @Column(name = "TAG_NAME")
    private List<String> tags = new ArrayList<>();
    private String content;
    private String profileImgUrl;
    @Enumerated(EnumType.STRING)
    private StudyState studyState;
    @Enumerated(EnumType.STRING)
    private RecruitState recruitState;
    @Enumerated(EnumType.STRING)
    private StudyMethod studyMethod;
    private Long headCount;
    private String schedule;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
}

하지만 위에서 언급했던 것처럼 임베디드 타입을 이용한다면 다음과 같아진다.

public class Study extends EntityDate {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "study_id")
    private Long id;
    private String title;
    private String content;
    private String profileImgUrl;
    private String department;
    private Long headCount;
    @Embedded
    private StudyOptions studyOptions;
    @Embedded
    private Schedule schedule;
    @ElementCollection
    @CollectionTable(name = "STUDY_TAG", joinColumns = @JoinColumn(name = "study_id"))
    @Column(name = "TAG_NAME")
    private List<String> tags = new ArrayList<>();
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    ...

}

실제로 위에서 enum으로 표현된 부분들(StudyState, StudyMethod, RecruitState)이 한 객체(StudyOptions)에 포함되어 저장되면서 임베디드 타입으로 저장되면서 높은 응집도를 유지하고 가독성이 훨씬 좋아졌다.

그리고 해당 StudyOptions는 Setter를 제거하고 생성자로만 새로운 객체 인스턴스를 생성할 수 있게 하여 불변 클래스로 구성하였기에 동시성 문제에서도 안전할 수 있다.

이뿐 아니라 일정 (Schedule) 또한 마찬가지다.

그리고 실제 DB에 저장하는 부분을 살펴보면 다음과 같다.

모든 요소들이 풀어져서 저장되는 모습을 확인할 수 있다.

이러한 부분들을 직접 적용해보면서 임베디드 타입의 효율성을 알 수 있었따.

단순 이렇게 적용할 뿐만 아니라 엔티티에서의 일급 컬렉션을 활용하기 위해서도 임베디드 타입을 활용할 수 있다.

이 부분에 대해서는 추후 포스트에서 작성해보려고한다.