스프링부트 계층구조
이 글은 스프링부트3 백엔드 개발자 되기 책을 바탕으로 공부한 내용을 정리한 게시글 입니다.
스프링부트는 각 계층이 양 옆의 계층과 통신하는 구조를 따릅니다. 계층이라는 것은 각자의 역할과 책임이 있는 어떤 소프트 웨어의 구성요소를 의미합니다. 각 계층은 서로 소통 할 수 있지만 다른 계층에 직접 간섭하거나 영향을 미치지 않습니다. 그럼 이제 스프링부트의 계층구조와 각 계층에 대해서 더 자세히 알아보겠습니다.
1. 스프링부트의 계층구조
스프링부트는 위 그림과 같은 계층구조를 이루고 있습니다. 클라이언트에서 요청을 보내게 되며 각 계층에서 적절한 요청을 처리하게 된 뒤 응답으로 클라이언트에게 다시 반환하게 됩니다.
요청의 순서를 간략하게 정리해보면
1. 클라이언트에서 HTTP요청(GET,POST 등)을 보냅니다.
2. 컨트롤러에서 요청을 받고 서비스레이어를 호출합니다.
3. 서비스계층에서 비즈니스 로직을 처리하고 리포지토리를 호출합니다.
4. 리포지토리에서 데이터베이스에 쿼리를 실행합니다.
5. 데이터베이스에서 쿼리 결과를 리포지토리로 반환합니다.
6. 리포지토리에서 결과를 서비스로 다시 반환합니다.
7. 서비스에서 데이터를 가공한 뒤 컨트롤러로 반환합니다.
8. 컨트롤러에서 취종 결과를 클라이언트에게 응답합니다.
그럼 각 계층의 역할에 대해서 더 자세하게 알아보겠습니다.
1. 프레젠테이션 계층
클라이언트의 요청을 받아서 처리하고 적절한 서비스 레이어로 요청을 전달합니다. 컨트롤러가 바로 프레젠테이션 계층 역할을 하게 됩니다.
컨트롤러의 작업순서는 다음과 같습니다.
- 클라이언트로부터 HTTP 요청을 받습니다.
- 요청의 유효성을 검사합니다.(예: 입력 데이터 검증)
- 요청을 처리하기 위해 서비스 레이어의 메서드를 호출합니다.
- 요청을 전달한뒤 다시 서비스 레이어에서 반환된 결과를 클라이언트에게 응답으로 반환합니다.
2. 비지니스 계층
모든 비지니스 로직을 처리합니다. 비즈니스 로직이란 서비스를 만들기 위한 로직을 말합니다. 예를 들어 주문서비스 라고 하면 주문개수, 가격등의 데이터를 처리하기 위한 로직, 주문처리 하다가 발생하는 예외 처리 로직, 주문을 받거나 취소하는 것 같이 프로세스를 구현하기 위한 로직등을 생각하면 됩니다. 서비스가 비즈니스 계층의 역할을 합니다.
서비스계층의 작업순서는 다음과 같습니다.
- 컨트롤러 레이어로부터 요청을 받습니다.
- 비즈니스 로직을 수행합니다.
- 필요한 데이터를 조회하거나 저장하기 위해 리포지토리 레이어의 메서드를 호출합니다.
- 리포지토리 레이어에서 반환된 데이터를 가공하거나 처리합니다.
- 리포지토리에서 받은 처리결과를 컨트롤러 레이어로 반환합니다.
3. 퍼시스턴스 계층
모든 데이터베이스 관련 로직을 처리합니다. 이 과정에서 데이터 베이스에 접근하는 DAO객체를 사용할 수도 있습니다. DAO는 데이터베이스 계층과 상호작용하기 위한 객체라고 이해하면 됩니다. 리포지터리가 퍼시스턴트 계층의 역할을 합니다.
리포지터리의 작업순서는 다음과 같습니다.
- 서비스 레이어로부터 요청을 받습니다.
- 데이터베이스에 쿼리를 실행합니다.
- 데이터베이스로부터 결과를 받아 서비스 레이어로 반환합니다.
4. 데이터베이스
실제 데이터를 저장하고 관리합니다.
데이터베이스의 작업순서는 다음과 같습니다.
- 리포지토리 레이어로부터 쿼리를 받습니다.
- 쿼리를 실행하고 결과를 리포지토리레이어로 반환합니다.
2. 계층 간 상호작용의 특징
각 계층은 위와 같은 역할을 가지고 각 계층끼리 상호작용을 통해 클라이언트의 요청을 처리하게 됩니다. 스프링부트의 계층구조는 위와 같은 구조를 가지고 있으며 다음과 같은 특징을 가지고 있습니다.
- 단방향 흐름: 클라이언트 → 컨트롤러 → 서비스 → 리포지토리 → 데이터베이스 순으로 요청이 전달됩니다.
- 의존성 방향: 각 계층은 하위 계층에만 의존합니다. 예를 들어 서비스는 리포지토리에 의존하지만, 리포지토리는 서비스에 의존하지 않습니다.
- 느슨한 결합: 각 계층은 인터페이스를 통해 연결 될 수 있으며, 이는 테스트와 유지보수를 쉽게 만듭니다.
스프링계층의 계층 구조가 다음과 같은 특징을 가지는 이유는 소프트웨어 설계의 핵심 원칙인 관심사 분리( Separation of Concerns, SoC)와 객체 지향 설계 원칙(SOLID)을 따르기 위함 입니다. 이러한 특징을 통해 애플리케이션의 유지보수성, 확장성 테스트 용이성 등을 극대화 할 수 있습니다.
- 관심사의 분리(Separation of Concerns):
- 각 계층이 특정한 역할만 담당하도록 함으로써 코드의 복잡성을 줄이고, 유지보수성을 높입니다.
- 예: 컨트롤러는 HTTP 요청 처리, 서비스는 비즈니스 로직, 리포지토리는 데이터 접근.
- 객체 지향 설계 원칙(SOLID):
- 단일 책임 원칙(SRP): 각 계층은 하나의 책임만 가집니다.
- 의존성 역전 원칙(DIP): 상위 계층은 하위 계층에 의존하지만, 하위 계층은 상위 계층에 의존하지 않습니다.
- 개방-폐쇄 원칙(OCP): 인터페이스를 통해 확장에는 열려 있고, 변경에는 닫혀 있습니다.
각 계층과의 요청이 단방향으로 처리가 되면 다음과 같은 장점이 있습니다.
- 명확한 책임 분리: 각 계층이 자신의 역할에만 집중 할 수 있습니다. 예를 들어, 컨트롤러는 요청을 받고 응답을 반환하는 데만 집중하며, 비즈니스 로직은 서비스 계층에서 처리합니다.
- 흐름의 단순화: 요청이 한방향으로만 흐르기 때문에 코드를 이해하고 디버깅하기가 쉽습니다.
- 에러 추적 용이: 문제가 발생하였을 때 특정 계층에서만 집중적으로 조사하여 추적할 수 있습니다.
또한 스프링부트 프로젝트의 계층구조는 하위 계층에만 의존을 하게 됩니다. 위에서 설명했던 것처럼 서비스는 리포지토링 의존하지만 리포지토리는 서비스에 의존하지 않습니다.
이렇게 하위계층에만 의존을 하게 되면 계층간 결합도가 감소하게 됩니다. 상위 계층이 하위 계층에만 의존하면, 하위 계층의 변경이 상위 계층에 영향을 미치지 않기 때문입니다. 예를 들어서, 데이터베이스를 변경 하더라도 리포지토리 계층만 수정하면 됩니다.
또한 재사용성이 증가하게 됩니다. 하위 계층은 여러상위 계층에서 재사용 될 수 있기 때문입니다.
스프링의 각 계층은 인터페이스를 통해 연결 될 수 있습니다. 이렇게 되면 구조적으로 다음과 같은 이점들을 얻게 됩니다.
- 유연성: 인터페이스를 사용하면 구현체를 쉽게 교체 할 수 있습니다. 예를 들어 데이터베이스 접근 방식을 변경하더라도 서비스 계층은 영향을 받지 않습니다.
- 테스트 용이성: 인터페이스를 통해 Mock 객체를 사용하여 각 계층을 독립적으로 테스트 할 수 있습니다.
- 유지보수성: 코드의 변경이 다른 부분에 미치는 영향을 최소화 할 수 있습니다.
이러한 계층 구조를 통해 코드의 품질을 높이고 개발생산성을 향상시키며 애플리케이션의 유지보수와 확장을 쉽게 할 수 있습니다. 특히 관심사 분리와 객체지향 설계 원칙은 소프트웨어 설계에 필수적인 요소이며 스프링의 계층구조는 이러한 원칙을 잘 반영하고 있습니다. 스프링부트의 계층구조를 통해 저희는 더 나은 프로젝트의 구조를 설계하여 성공적인 프로젝트를 완성 할 수 있을 겁니다.