[잔디일기] 패키지 구조에 대한 고민

잔디 일기 서비스의 경우 간단한 서비스였기 때문에 1차 기능 구현 기간까지는 비교적 수월하게 진행을 해 왔으나,

1. 초반에 팀원 분들과 서로 상의하지 않고 각자 구현했던 패키지 구조로 인해 커져버린 메인 패키지

2. 추가해야 할 기능이 늘어남에 따라 중구난방으로 생성되어버린 패키지

3. 하나의 기능을 수정하려면 여러 패키지를 수정해야 하는 번거로움

해당 부분들 때문에 대규모 패키지 수정이 필요해 보였습니다.

 

그래서 패키지 구조를 어떤 식으로 고민을 했고, 그래서 어떻게 바꿀 것인지에 대해 적어보았습니다.


패키지 구조를 만드는 방식에는 계층형 구조와 도메인형 구조가 있습니다 .

 

계층형 구조는 쉽게 Controller/Service/Domain으로 나누는 구조로, 다음과 같이 표현되어 서로 메시지를 주고 받습니다.

⎿ controller
	⎿ XXXController

⎿ service
	⎿ XXXService
   
⎿ domain
	⎿ User

 

도메인형 구조는 도메인을 기준으로 패키지를 나눈 구조입니다.

⎿ user
	⎿ controller
	⎿ service
	⎿ repository

⎿ diary
	⎿ controller
	⎿ service
	⎿ repository

 

처음 구축할 때 잔디일기 서비스의 패키지 구조는 계층형 구조로 시작하였습니다.

⎿ grassdiary
    ⎿ domain
    ⎿ service
    ⎿ web
    	⎿ controller
        ⎿ dto

 

계층형 구조는 패키지 구조만 보고도 전체적인 구조를 쉽게 파악할 수 있고, 계층별 응집도가 높아진다는 장점이 있습니다.

이는 해당 서비스에서 제공하는 API가 궁금하다면 Controller 패키지 하나만 보고도 흐름이 파악 가능하다는 뜻입니다.

 

하지만 계층형 구조의 경우 아래와 같은 단점을 갖습니다.

  • 도메인의 흐름을 파악하기 힘들고 기능이 변경된다면 변경 범위가 크다.
    • User 도메인의 흐름을 보고 싶을 때, 모든 계층 패키지를 봐야한다.
  • 구현이 한 군데 모여있지 않아, 특정 기능을 모듈로 분리하는 작업이 어렵다.
  • 기능의 내부 구현 디테일을 감추고 바깥에 추상화된 형태로 제공하기 어렵다.
  • 규모가 커지면 하나의 패키지 안에 여러 클래스들이 모여서 구분이 어려워진다.

이러한 단점들 때문에 도메인형 구조로 바꿔야겠다고 생각했습니다.


잔디 일기 서비스의 경우 현재 나눠둔 도메인이 총 5개(color/diary/member/reward)이며, 패키지 구조는 다음과 같습니다.

*중구난방으로 생성되어버린 패키지들을 보실 수 있습니다.

⎿ grassdiary
    ⎿ auth
    	⎿ client/common/config/controller/exception/filter/jwt/service/util
    ⎿ config
    ⎿ domain
    	⎿ base
        ⎿ color
        ⎿ diary
        ⎿ member
        ⎿ reward
    ⎿ global
    ⎿ service
    ⎿ web
        ⎿ controller
        ⎿ dto
        	⎿ diary/main/member/share
        ⎿ exceptions

 

  • auth 패키지의 경우 
    • 패키지 구조에 대한 논의를 진행하지 않았을 때 생성된 패키지
    • 구현하실 때 처음부터 도메인형 구조로 생성하셨기 때문에 내부에 들어있는 성격이 다른 패키지/클래스들을 정리 해주면 될 것 같습니다.
    • 위치 이동 필요
  • 패키지 명에 단수/복수 명사의 혼용
    • '패키지' 자체가 여러가지를 담으려고 생성하는 것이기 때문에 기본적으로는 단수/복수가 구분이 되어서는 안된다고 생각합니다.
    • 단수/복수가 구분 되지 않아도 그 뜻을 명확하게 인지 할 수 있기 때문에 단수로 통일하고자 합니다.(특수한 경우 제외)

고민 해 본 패키지 구조와 설명

  • global/
    • util, common(request/response), auth, config, properties, error(ErrorResponse, ExceptionHandler) ...
    • 전역에서 사용가능한(전역에서 사용해야하는) 클래스들
    • 의존 관계가 없어야 하고, 순수 Java Class만 정의 가능
    • 구현시 주의할 점
      • 수정을 최소화 해야 합니다. 어디서든 사용 가능하다보니 변경이 있을 경우 파급력이 클 수 있습니다. 잦은 수정이 필요한 경우라면 잘못 설계한 경우일 수 있습니다.
      • 특정 클래스에서만 사용되는 기능이거나 비즈니스 로직이라고 불릴 정도의 로직이라면 도메인 등과 같은 위치하기 더 적절한 곳이 있을 수 있습니다. 해당 기능이 정말 util 등의 성격인지 고민 해보는 것이 좋겠습니다.
  • domain/
    • 도메인 각각에 해당하는 controller, domain, dao(← repository(이름 변경)), service, exception ...
    • exception/
      • 해당 도메인이 직접 발생시키는 예외 로직
    • repository 패키지 명을 dao로 변경한 이유
      • dao(data access object)는 데이터에 접근하도록 DB 접근 관련 로직을 모아둔 객체입니다. (비교적 Low level)
      • repository엔티티 객체를 보관하고 관리하는 저장소입니다. (비교적 High level)
        • 도메인 개체에 액세스 하기 위해 컬렉션과 유사한 인터페이스를 사용하여 도메인과 데이터 매핑 계층 사이를 중재합니다.
      • repository는 dao를 사용하여 구현할 수 있지만, 그 반대는 불가능합니다.
      • 같은 로직이었을 때, dao와 repository 의 예시(repository의 경우 엔티티 클래스를 사용하는 것을 볼 수 있습니다.)
        • UserDao
          public interface UserDao {
              void create(User user);
              User read(Long id);
              void update(User user);
              void delete(String userName);
          }
        • UserRepository
          public interface UserRepository {
              User get(Long id);
              void add(User user);
              void update(User user);
              void remove(User user);
          }
      • 현재 '잔디 일기'의  repository 패키지 및 클래스의 구현 방식은 DB와 매핑되고, 쿼리를 사용하여 객체를 반환합니다. 그렇기 때문에 dao의 성격과 더 맞다고 판단했습니다.

위를 바탕으로 수정될 예정인 패키지 구조

⎿ grassdiary
    ⎿ global
    	⎿ auth
    	⎿ util
    	⎿ common 
        	⎿ request
        	⎿ response
    	⎿ config
        	⎿ properties
    	⎿ error		# 예외 핸들링
        	⎿ ExceptionHandeler.java
        	⎿ exception
        		⎿ ErrorCode.java
        		⎿ EntityNotFoundException.java
	        	⎿ InvalidValueException.java
	        	⎿ ...
    ⎿ domain 			# base 패키지를 제외한 도메인별 동일 구조
        ⎿ base 		# 도메인 엔티티가 공통적으로 사용할 클래스
    	⎿ member
        	⎿ controller
        	⎿ domain 	# 도메인 엔티티에 대한 클래스
        	⎿ dao
        	⎿ service
        	⎿ exception
        	⎿ dto
        ⎿ diary
        ⎿ color
        ⎿ reward

모든 클래스가 아닌 간략화하여 나타내 보았습니다.

이미지 api 기능을 추가하던 중, 패키지 수정이 많이 필요해 보여서 적어 본 글이니 좀 더 수정 될 수 있다고 생각합니다.

 

피드백이나 잘못 된 부분 등이 있다면 편하게 댓글 주시면 감사하겠습니다. 😄


레퍼런스


그 외 잡담

아직 DDD에 대한 개념이 덜 잡힌 것 같아 해당 방법이 확실한지는 잘 모르겠습니다.(물론 패키지 구조에 정답은 없다고 하지만...)

그렇기 때문에 좀 더 공부 해 본 뒤에 나중에 해당 게시글을 다시 읽어 봐야겠다는 생각이 듭니다.

 

해당 부분을 공부하면서 더 필요하다고 생각한 부분

  • DTO를 활용중인데, DTO 클래스를 무분별하게 사용 중인 것 같아서 이 부분도 알아 볼 것
  • 한 개의 클래스 내에 많은 기능이 있는 것 같아 분리가 필요해 보이는 클래스들이 있는 것 같음(메서드도 마찬가지)
  • 예외 핸들링에 대한 방법