Hchanghyeon / dev-troubleshooting

개발하며 마주쳤던 크고 작은 문제들과 고민들
1 stars 0 forks source link

[JPA] 엔티티 간 연관 관계에 대한 고민 #5

Closed Hchanghyeon closed 8 months ago

Hchanghyeon commented 8 months ago

고민

프로젝트를 하며 엔티티 간 많은 연관 관계로 인해 아래와 같은 문제가 발생하여, 연관 관계를 맺는 것이 좋은 것인지 다시 고민해보게되었습니다.

1. Entity가 거대해진다.

Member.class

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = "id", callSuper = false)
public class Member extends BaseEntity {

    ...

    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "address_depth1_id")
    private AddressDepth1 addressDepth1;

    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "address_depth2_id")
    private AddressDepth2 addressDepth2;

    @Embedded
    private MemberPositions memberPositions = new MemberPositions();

    @Embedded
    private MemberCrews memberCrews = new MemberCrews();

    @Embedded
    private MemberGames memberGames = new MemberGames();

    ...

    private void updateMemberPositions(final List<Position> positions) {
        memberPositions.updateMemberPositions(this, positions);
    }

    private void setDefaultIntroduction(final String nickname) {
        this.introduction = MessageFormat.format("안녕하세요. {0}입니다.", nickname);
    }

    public RegistrationStatus findCrewRegistrationStatus(final Crew crew) {
        return memberCrews.findCrewRegistrationStatus(crew);
    }

    public RegistrationStatus findGameRegistrationStatus(final Game game) {
        return memberGames.findGameRegistrationStatus(game);
    }

   ... 연관 관계 관련 메서드 10개 이상
}

위 Member 엔티티에 남겨져 있는 필드는 모두 연관 관계로 맺어진 필드입니다. 주로 Member의 경우 꽤나 많은 엔티티와 연관성이 있기 때문에 많은 연관 관계를 맺을 수 있습니다. 이렇게 Member와 같이 다른 엔티티와 연관 관계를 많이 맺을 경우 하나의 엔티티에 연관 관계를 위한 많은 메서드가 생성되고, 이는 한 엔티티에 많은 책임이 부여되며 거대해지게됩니다.

2. 코드가 수평으로 읽혀 가독성이 떨어진다.

MemberSerivce.class

final RegistrationStatus memberRegistrationStatus = member.findGameRegistrationStatus(game);

이 메서드는 member가 매개변수로 들어가는 game에 등록된 상태를 확인하는 메서드 입니다. 위 메서드를 한 번 따라 가볼까요?

Member.class

@Entity
public class Member extends BaseEntity {

    ...

    @Embedded
    private MemberGames memberGames = new MemberGames();

    ...

    public RegistrationStatus findGameRegistrationStatus(final Game game) {
        return memberGames.findGameRegistrationStatus(game);
    }

   ... 
}

game에 등록된 상태를 확인하는 로직인데, 저는 또 Embedded로 설정된 memberGames로 접근해서 확인해야합니다. 그럼 또 메서드로 타고 들어가볼까요?

MemberGames.class

@Embeddable
public class MemberGames {

    @OneToMany(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
    private List<GameMember> memberGames = new ArrayList<>();

    public RegistrationStatus findGameRegistrationStatus(final Game game) {
        return memberGames.stream()
                .filter(memberGame -> memberGame.equalsGame(game))
                .findFirst()
                .map(GameMember::getStatus)
                .orElse(RegistrationStatus.NONE);
    }
}

드디어 game에 등록된 상태를 확인하기 위한 구현 코드를 확인해볼 수 있습니다. 근데 여기서 보면 또 연관 관계로 맺어진 memberGame에 접근하여 equalsGame이라는 메서드를 찾아야하고, 아래 반환된 결과 값인 GameMember에서 getStatus를 이용하여 결과 값을 반환 하는 것을 볼 수 있습니다.

물론 서비스에서 한줄로 호출하여 비즈니스 로직이 간단하게 읽힙니다. 또한 member 엔티티, 즉 도메인 안에 구현 코드를 숨김으로써 캡슐화가 된다고 볼 수도 있습니다.

하지만 코드를 읽기위해 MemberService -> Member -> MemberGames -> MemberGame -> GameMember의 흐름으로 따라가야하며, 이는 결국 하나의 비즈니스 로직을 구현하기 위한 코드를 한 곳에 모아서 보지 못하게되어 가독성을 떨어트리게 됩니다.

3. 연관 관계로 맺어진 Entity가 강결합되어 유지보수하기 어렵다.

MemberService -> Member -> MemberGames -> MemberGame -> GameMember

방금 2번에서 흐름을 따라간 것 처럼 결국 Member부터 GameMember까지 엔티티 간 강결합 되어있어 GameMember를 수정하는 경우 MemberService 까지 영향을 미치기 쉽습니다. 이렇게 될 경우 코드를 따라가며 읽기도 어려운데 하나를 고쳤을 때 연관된 엔티티를 모두 타고 들어가서 메서드를 수정해야하는, 유지보수가 어려운 상황에 처하게 됩니다.

Hchanghyeon commented 8 months ago

해결: [JPA] 엔티티 간 연관 관계에 대한 고민