스마일 패키지 구조 수술~

khyojun·2024년 5월 22일
4

시행착오

목록 보기
7/11
post-thumbnail

현재 졸업작품 프로젝트를 진행하다보니 일단 어느정도 구현에 집중하여 빠르게 개발을 하고 있었다.

그런데..!

어느 순간부터 눈이 침침해지기 시작했습니다.(같은 파트 동료도)

문제를 찾아보니... 다음과 같은 패키지 구조였습니다.

음.. 이걸 보시면? 이게 왜? 눈이 침침해지지? 라고 느낄 수 있다.

그런데... 하나씩 들어가보니

... 다시 눈이 흐려지는?

어째 눈이 다시 흐려지고 있다..

그래서 바꿔야겠다 계층형 -> 도메인 패키지로의 변경 수술

그래서 슬슬 구조를 변경하기 시작하려고 한다. 주로 참고하였던 글은 아래의 글을 작성하신 분이다.

https://cheese10yun.github.io/spring-guide-directory/#null

해당 블로그에 작성하신 일부분을 인용해본다면

개인적으로 계층형보다 도메인형이 더 매력적이게 느껴지기 시작하고 더 효과적이라고 확신이 들게 되었습니다.

사실 정답은 없지만 나도 눈이 흐려지는 것을 알게 된 이후부터 더 도메인형이 매력적이기 시작하였다.

도메인형은 어떻게?

이전 계층형과 달리 도메인별로 분리함으로써 패키지 구조만 봐도 응집성이 더 올라간것처럼 보이게 된다.

나도 이제 해보자..!

여러 도메인으로 구분하기 위해 다음과 같은 절차를 지키며 진행을 해봤다.

  • 엔티티를 가진 친구들 기준으로 분리하자.
  • 패키지를 크게 api, domain으로 분리한다.
    • api : domain의 로직들을 조합하여 사용되거나 api 쪽의 성격이 가능한 친구들의 모임
    • domain : 도메인의 비즈니스 로직들
  • global 한 친구들도 분리한다.
    • ex) config, exception, security 등등

그런데 조금 애매한 친구들에 대해서...

사실 이렇게 예쁘게 분리할 수 있으면 정말 좋겠지만 과정을 진행하다가 너무 애매한 친구들이 몇개가 있어서 조언을 구해보았다.

실제 있는 클래스다.

이 친구에게는

public String userA()
{
	return "유저를 위한 메서드";
}


public String majorLanguageB(){

	return "majorLanguage를 위한 메서드";
}

이런 식으로 메서드가 존재한다. userA는 user 도메인에서만 사용하는 메서드 majorLanguageB는 MajorLanguage 도메인에서만 사용하는 메서드다.

문제가 발생하는것이 바로 이것이다.

@Service
@RequiredArgsContructor
public class UserService{
	private final GraphqlService graphqlService;
    .
    .
    .
}


@Service
@RequiredArgsContructor
public class MajorLanguageService{
	private final GraphqlService graphqlService;
    .
    .
    .
}

사실 각 도메인에서는 하나의 메서드씩 사용하는데 GraphQLService 를 받아버린다면 필요없는 메서드 하나를 받아버리는것과 같아진다.

즉, ISP 원칙에 위배하게 되는 것이다.

ISP(인터페이스 분리원칙): 객체는 자신이 호출하지 않는 메소드에 의존하지 않아야한다.

지금 사용하고 있지 않는 하나의 메서드 때문에 이런 일들이 벌어졌기 때문에 이런일 일어났으면 해결을 해줘야한다.

//user 도메인에 

public interface UserAService{
    String userA();
}

//majorLanguage 도메인에
public interface MajorLanguageBService{
	String majorLanguageB();
}

이렇게 한 이후 인터페이스의 구현체가 되게 하여 아래와 같은 형태로 바꿔준다.

@Service
public class GraphqlService implements UserAService, MajorLanguageBService {

@Override
public String userA()
{
	return "유저를 위한 메서드";
}

@Override
public String majorLanguageB(){

	return "majorLanguage를 위한 메서드";
}

구현체쪽에 이런식으로 하면은 이런식으로 이제 사용이 가능해진다.

@Service
@RequiredArgsContructor
public class UserService{
	private final UserAService userAService;
    .
    .
    .
}


@Service
@RequiredArgsContructor
public class MajorLanguageService{
	private final MajorLanguageBService majorLanguageBService;
    .
    .
    .
}

이런식으로 분명하게 도메인을 나누고 구현체인 친구를 밖으로 꺼내는 방향으로 해서 개선이 가능하고 이를 통해 ISP 원칙을 지키는 효과도 얻을 수 있었다.

패키지 구조를 개선하다.. 좋은 객체지향원칙을 지켜나갈 수 있게 되었다.

그런 수정과정들을 여럿 거치고 난 이후 이런 결과물을 얻게 된다.

이렇게 보면 너무 패키지가 많아 눈이 흐려지지만..

뚜껑을 열고 보니?

BEFORE

AFTER

After Image

왼쪽에 비해서 오른쪽을 보니 점점 선명해진다.

결론은... 바꾸길 잘했다.

이전과 달리 조금 더 응집도가 있어진 느낌도 들고 더 프로젝트 구조를 확인하기 편한 구조가 된 거 같다.

이처럼 만약 나와 같이 개발을 진행하다가 프로젝트 구조를 보고 뭔가 싸해지거나 했다면 꼭 패키지 구조를 고쳐보는 작업을 진행해보면 좋을 거 같다는 것으로 스마일 패키지 구조 수술을 마친다.

출처

profile
코드를 씹고 뜯고 맛보고 즐기는 것을 지향하는 개발자가 되고 싶습니다

3개의 댓글

comment-user-thumbnail
2024년 5월 24일

GraphqlService 이 클래스는 어느 디렉토리에 있나요?

1개의 답글
comment-user-thumbnail
2024년 6월 11일

저도 해볼게요 !

답글 달기