의존성 역전을 통해 패키지 순환 참조 해결하기

공병주(Chris)·2023년 4월 20일
0


2023 글로벌미디어학부 졸업작품 Dandi를 개발하면서 패키지 순환 참조를 해결하기 위해 고민한 기록입니다.

post → member

옷 기록(post) 패키지는 회원(member) 패키지를 참조하고 있습니다.

옷 기록(Post)을 응답해줄 때, 작성자의 정보(Member)도 함께 응답해줘야하기 때문입니다.

member → post?

그런데, 여기서 요구사항이 하나 더 생겼습니다. 회원 정보를 조회할 때, 회원(Member)이 작성한 옷 기록(Post)가 몇개인지 함께 반환해줘야 합니다.

따라서, Member 관련 Service에서 아래처럼 post 패키지에 위치한 DB 영속화 객체를 통해 옷 기록의 개수를 조회한다면 member → post의 패키지 참조가 발생합니다.

@Service
public class MemberService implements MemberUseCase {

    private final MemberPersistencePort memberPersistencePort;
    private final PostPersistencePort postPersistencePort; // post 패키지에 위치한 객체

    // ...

    @Override
    @Transactional(readOnly = true)
    public MemberInfoResponse findMemberInfo(Long memberId) {
        Member member = findMember(memberId);
        int postCount = postPersistencePort.countPostByMemberId(memberId);
        return new MemberInfoResponse(member, postCount, imageAccessUrl);
    }
}

순환참조

이렇게 되면 post와 member 패키지 간의 순환참조가 발생하는데요. 패키지 순환참조는 피해야 합니다.

개발을 하면서 순환참조는 항상 신경써야하는 부분입니다. 예전에 클래스의 순환참조는 신경을 썼지만, 패키지의 순환참조는 크게 신경쓰지 않고 개발을 했었는데요. 이후에 리팩토링/유지보수 하는 데에 변경에 대한 효과를 잡는데에 생각보다 큰 어려움을 겪었습니다.

따라서, 개발을 해나가면서 순환참조가 발생하는 부분이 있는지를 항상 체크하면서 개발을 하려고 노력합니다.

DI를 통해 해결

이렇게 되면 post와 member 패키지 간의 순환참조가 발생하는데요. 패키지 순환참조는 피해야 합니다.

패키지 순환 참조의 해결 방법은 여러가지가 있지만, 이 상황에서 저는 DI를 통해서 이를 해결했습니다.

위의 코드라면 member 패키지에서 MemberService에서 post 패키지의 postPersistencePort를 의존하고 있는데요.

아래처럼 DI를 통해 해결할 수 있습니다. 객체의 이름은 예시이니 좋지않아도 양해부탁드립니다.

@Service
public class MemberService implements MemberUseCase {

    private final MemberPersistencePort memberPersistencePort;
    private final MemberPostPersistencePort memberPostPersistencePort; // post 패키지에 위치한 객체

    // ...

    @Override
    @Transactional(readOnly = true)
    public MemberInfoResponse findMemberInfo(Long memberId) {
        Member member = findMember(memberId);
        int postCount = memberPostPersistencePort.countPostByMemberId(memberId);
        return new MemberInfoResponse(member, postCount, imageAccessUrl);
    }
}

위처럼 member 패키지에 memberPostPersistencePort(interface)를 선언해서 MemberService에서는 memberPostPersistencePort를 참조합니다. 그렇다면 member는 post로의 참조는 없습니다.

// post 패키지

@Component
public class MemberPostPersistenceAdapter implements MemberPostPersistencePort {

    private final PostRepository postRepository;

    public MemberPostPersistenceAdapter(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Override
    public int countPostByMemberId(Long memberId) {
        return postRepository.countByMemberId(memberId);
    }
}

post에서 member의 memberPostPersistencePort에 대한 Adapter(구현체)를 구현하도록 했습니다.

따라서, 원래는 member 패키지가 post 패키지를 참조하는 구조에서 post 패키지가 member 패키지를 참조하도록 의존성을 역전시켰습니다.

위의 방식으로 패키지 간의 의존성이 한방향으로 흐르도록 설정할 수 있었습니다.

0개의 댓글