스프링의 Layered Architecture와 폴더 구조
[ 스프링의 Layered Architecture ]
React는 라이브러리, Vue는 프레임워크, 스프링도 프레임워크라고 한다. 라이브러리와 프레임워크의 기본적인 차이는 제어의 권한을 누가 가지고 있는가에 따라 구분할 수 있다. 일반적인 Java 코드에서 Jackson라이브러리의 경우 그 코드는 도구이며 전체적인 흐름은 개발자가 직접 선택해서 정한다.
반면 스프링에서는 전체적인 흐름은 스프링에서 정해져 있고 최대한 역할을 분리할 수 있게 layered architecture 구조로 코드를 짜게 강제한다. 이런 구조의 장점 중 하나를 꼽아보라고 생각하면 컨트롤러 층이 가장 먼저 떠오른다. 특히 컨트롤러에서 우리는 @Request와 @Response를 어떻게 처리할 지만 따로 처리만 한다. 개발자가 직접 HTTP 스펙을 어떻게 처리하고 오류가 났을 때도 몰라도 되고 객체를 내보낼 때 따로 Json 형태로 놓지 않아도 된다. 그것이 가능한 이유는 DispatcherServlet이 그 처리를 돕기 때문이다. 이렇게 흐름제어가 프레임워크인 스프링에게 가있어 불편할 것 같지만 원하는 로직과 객체 간의 연관관계를 유저가 자유롭게 설정할 수 있게 하기 위해 의존성 주입이라는 방법으로 이 문제를 해결한다.
아무튼 그렇게 스프링이 선택한 방식은 아래의 구조로 총 4가지 종류로 구성이 되어있다. 모델을 언급하지 않는 경우도 있는데 여기서 언급되는 모델은 JPA 같은 ORM으로 데이터베이스 테이블과의 매핑 정보 객체를 의미한다. 컨트롤러 계층은 클라이언트에게 서비스 및 응답 상태를 제공하는 역할을 담당한다. 상위 계층으로 컨트롤러 계층은 서비스 계층 또는 리포지토리 계층에 의존한다. 또한 서비스 계층은 비즈니스 로직을 구현하기 위한 곳으로 데이터에 관한 연산과 객체 변환 등이 일어난다. 서비스 계층은 리포지토리 계층에 의존한다. 마지막으로 리포지토리 계층은 데이터베이스와 관련된 부분으로 개발자가 선택한 DB의 기술에 의존한다.
이렇게 구조를 강제화하면 얻을 수 있는 장점은 개발자로 하여금, 역할의 분리를 조금 더 강제할 수 있다. 계층을 나누고 상위 계층은 하위 계층에 의존하게 만든다면 유지보수를 하기 쉽게 코드를 작성할 수 있다. 이에 관한 글은 따로 강의내용을 정리하며 작성을 하도록 하겠다.
아무튼 이번 편에서는 폴더 구조에 관해서 이야기를 해보려고 한다. 사실 폴더 구조에 관해서는 굉장히 고민이 많이 됐다. 강의나 여러 개발자마다 놓는 방식이 다 다르고 정답이 없는 문제라 "나는 어떻게 해야 하는가?"에 대해 고민을 많이 했다. 프로젝트에서도 일반적으로 계층별로 나누듯이 컨트롤러, 서비스, 리포지토리 이렇게 나누어 진행을 해본 적도 있고 한 번은 로그인, 채팅, 게시글 같이 서비스 별로 나누어본 적도 있다. 각자 장단점이 있었지만 Dto, Config 같은 파일이 추가되면 굉장히찾기 어려워졌고 찾기 힘든 건 똑같았다. 그러던 중, 테스트에 관한 지식이 없어 인프런의 "Practical Testing: 실용적인 테스트 가이드"라는 강의를 보면서 이분이 놓은 구조가 되게 좋은 것 같다는 생각이 들었고 정리를 해보고자 한다. 제목에 비해 내용은 별 것 없지만, 많은 생각을 해보게 된 것 같다.
[ 폴더 구조 ]
제일 큰 단위는 메인 프로젝트 아래에 api, config, domain 세 개로 나뉘어져 있었다. 이렇게 폴더를 놓으니 어떤 부분이 DB와 관련이 있는지 요청과 컨트롤러와 관련이 있는 지 잘 구분이 잘 되어 있었다.
Domain에는 모델과 리포지토리가 있었다. 예전에는 리포지토리와 엔티티로 폴더 구조를 했을 때에는 서비스 별로 구분을 해놓아도 프로젝트가 커지면 리포지토리랑 엔티티 폴더 사이의 간격이 커져서 불편했었다.
이 구조로 나누어지니 서비스 별 내용을 찾기 되게 쉽다고 느껴졌다. 또한 내부에는 DB 테이블 별로 하위 폴더를 설정하니 확실하게 Data Access와 관련된 부분만 깔끔하게 딱 분리되어 모여있다는 느낌을 받았다.
api에는 컨트롤러 계층과 서비스 계층으로 구분이 되어있었다. 특히 이걸 보며든 생각은 컨트롤러와 서비스로 분리는 했지만 서비스 별로 하위 폴더를 구성하고 그 아래에 dto를 놓고 거기서 request와 response에 따라 구분을 했다는 점이다. 이전에는 dto를 계층과 동일하게 놓았었는데, 시간이 지나니 어떤 컨트롤러와 연관이 있는지 헷갈렸었다. 이렇게 구조를 놓으니 서비스 별 처리를 할 때에 다른 부분은 다 접어놓고 각각 찾기도 쉽고 어떤 부분이 요청과 응답과 관련된 부분인지 알 수 있어서 좋았다.
또한 서비스 계층에서도 request와 response를 두어 컨트롤러가 서비스에 의존하고 서비스가 컨트롤러 계층에 의존하는 문제를 끊어내는 방법에 관해 설명한 내용도 되게 좋은 내용이라고 생각했다.
사실 뭔가 거창하게 쓴 것 같지만 이런 구조로 다음에 프로젝트를 한 번 만들어보는 건 어떨까 생각을 했고 다음에 활용하기 위해 기록을 남긴다.
[ 참고 자료 ]
https://levelup.gitconnected.com/understanding-spring-boot-architecture-6083e2631bc6
Practical Testing: 실용적인 테스트 가이드 : 박우빈