Spring에 대한 이해
Spring Framework(스프링 프레임워크)은 자바 기반의 웹 애플리케이션 개발을 위한 프레임워크이다. 스프링은 객체 지향 개발을 매우 용이하게 할 수 있도록 도와주며, 애플리케이션을 빠르고 효율적으로 개발할 수 있도록 기본 틀과 공통 프로그래밍 모델, API 등을 제공한다. 스프링 개발자라면 최소한 이 3가지 프로그래밍 모델만큼은 잘 이해하고 자유롭게 응용할 수 있어야 한다.
Spring Container(스프링 컨테이너)
스프링은 스프링 컨테이너 또는 애플리케이션 컨텍스트라고 불리는 런타인 엔진을 제공한다. 스프링 컨테이너는 설정 정보를 참고하여 애플리케이션을 구성하는 오브젝트(빈)을 생성하고 관리한다. 그리고 의존성을 갖는 객체에 대해 의존성 주입(Dependency Injection, DI)를 통해 두 객체의 결합도를 낮추도록 도와준다.
공통 프로그래밍 모델
프레임워크는 애플리케이션을 구성하는 객체(Bean, 빈)가 생성되고 동작하는 틀을 제공해줄 뿐만 아니라, 애플리케이션 코드를 어떻게 작성해야 하는지에 대한 기준도 제공한다. 이를 일반적으로 프로그래밍 모델이라고 부르는데, 스프링에서는 크게 3가지 핵심 프로그래밍 모델을 지원하며, 이에 맞춰 개발할 것을 권장하고 있다.
- IoC/DI
- PSA (서비스 추상화)
- AOP
IoC/DI
IoC/DI는 오브젝트의 생명주기와 의존 관계에 대한 프로그래밍 모델이다.
스프링은 유연하고 확장성이 뛰어난 코드를 작성하도록 도와주는 객체지향 설계 원칙과 디자인 패턴의 핵심 원리를 담고 있는 IoC/DI를 프레임워크의 근간으로 삼는다. 그렇기 때문에 스프링에서 개발되는 코드들은 IoC/DI 방식에 따라 작성되어야 가치를 누릴 수 있다. Spring 프레임워크에서 제공하는 DI를 적용함으로써 객체의 책임을 분리하고 협력하도록 하여 유연하고 확장성이 뛰어난 설계를 할 수 있다.
PSA(Portable Service Abstraction, 휴대 가능한 서비스 추상화)
스프링을 사용하면 서비스 추상화를 통해 특정 환경이나 서버, 기술에 종속되지 않으며 유연한 애플리케이션을 개발할 수 있다. 스프링에서는 추상화 계층을 통해 구체적인 기술과 환경에 종속되지 않도록 한다.
예를 들어 MyBatis나 JPA 등 세부 기술에 종속적인 에러들을 추상화하여 기술에 종속적이지 않은 에러들로 처리할 수 있도록 도와준다.
스프링을 자세히 공부하다 보면 추상화를 위해 프록시(Proxy) 패턴과 같은 디자인 패턴이 매우 자주 사용됨을 파악할 수 있다.
AOP
AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)는 애플리케이션에 공통적으로 나타나는 부가적인 기능들을 독립적으로 모듈화하는 프로그래밍 모델이다.
애플리케이션의 요구 사항이 점점 복잡해져가고, 기술적인 난해함이 더해지면 모든 책임과 관심사를 분리하는 것이 상당히 어렵다. 그래서 스프링은 AOP를 지원하여 다양한 엔터프라이즈 서비스를 적용하고도 책임을 분리하여 깔끔한 코드를 유지할 수 있고, 객체지향스럽게 개발할 수 있도록 도와준다.
예를 들어 비즈니스 로직과 트랜잭션 관리를 위한 로직이 결합된다면 로직이 중복되고, 여러 책임을 갖는 등의 단점이 존재하게 된다. Spring에서는 AOP를 적용하여 @Transactional이라는 선언적 트랜잭션 기능을 구현하여 큰 장점을 누릴 수 있도록 도와준다.
API
스프링은 엔터프라이즈 애플리케이션을 개발할 때, 다양한 영역에 바로 활용할 수 있는 API를 제공한다.
스프링은 UI 작성부터 웹 프레젠테이션 계층, 비지니스 서비스 계층, 데이터 엑세스 계층 등에서 필요한 주요 기술을 일관된 방식으로 사용할 수 있도록 기능과 클래스 등을 제공한다. 그 외에 다양한 오픈소스 기술도 이용할 수 있도록 지원해주고 있다.
예를 들어 클라이언트로부터 Get 요청을 받아 처리하기 위해 Spring은 @GetMapping과 같은 API들을 제공하고 있다.
Spring을 사용한다는 것은 바로 이 세 가지 요소를 적극적으로 활용해서 애플리케이션을 개발한다는 뜻이다. Spring은 객체지향 개발을 극대화할 수 있도록 도와주는 프레임워크이므로, 스프링이 제공하는 핵심 프로그래밍 모델을 따라 개발하여 그 가치를 누릴 수 있어야 한다.
계층형 아키텍처(Layered Architecture)와 3가지 계층
[ 계층형 아키텍처(Layered architecture)와 3가지 계층
관심, 책임, 성격 등이 서로 다른 것들을 분리하면 분리된 각 요소의 응집도가 높아지고 결합도는 낮아진다. 만약 서로 다른 모듈을 분리하지 않으면 1가지 변경 사항에 대해 다른 요소들까지 영향을 받게 된다. 이러한 문제를 해결하고자 Spring은 요청을 처리하기 위한 계층을 3가지로 나누었다.
- Presentation Layer(프레젠테이션 계층): 웹 기반으로 요청과 응답을 처리하는 계층
- Service Layer(서비스 계층): 비지니스 로직을 담고있는 계층
- Data Access Layer(데이터 접근 계층): DB와 연동되어 데이터에 접근하는 계층
Presentation Layer(프레젠테이션 계층)
프레젠테이션 계층에서는 클라이언트로부터 HTTP 요청을 수신하고 그에 맞는 응답을 돌려주는 계층이다. 프레젠테이션 계층에서는 어떠한 요청을 받고, 어떠한 응답 상태와 데이터를 반환하는지에 대한 책임이 있다.
우리가 작성한 클래스를 프레젠테이션 계층으로 명시하기 위해서는 @Controller 또는 @RestController 어노테이션을 사용해줘야 한다. 그리고 Spring은 요청을 수신하기 위해 Restful API에 맞는 Mapping 어노테이션들을 제공하고 있다.
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- 기타 등등
Service Layer(서비스 계층)
서비스 계층에서는 요구 사항에 맞게 비지니스 로직을 작성하는 계층이다. 그렇기 때문에 일반적으로 서비스의 핵심 로직들이 주로 담겨 있다. 우리가 작성한 클래스를 서비스 계층으로 명시하기 위해서는 @Service 어노테이션을 사용해줘야 한다.
또한 트랜잭션에 대한 관리 역시 서비스 계층에서 처리하는 것이 일반적인 패턴이다.
(최근에는 도메인 중심의 개발을 하면서 핵심 비지니스 로직은 도메인 객체 또는 계층에 존재하며, 서비스 계층은 위임의 역할을 하거나 다른 도메인과 함께 처리되어야 하는 비지니스 로직이 많이 작성되기도 한다.)
Data Access Layer(데이터 접근 계층)
데이터 접근 계층은 데이터를 저장하거나 조회하기 위해 DB에 접근하는 계층이다. 우리가 작성한 클래스를 데이터 접근 계층으로 명시하기 위해서는 @Repository 어노테이션을 사용해줘야 한다. 그러면 Spring은 @Repository가 붙은 어노테이션을 빈으로 등록하며, 추가적으로 에러 추상화와 같은 부가적인 기능을 제공해준다.
계층의 역할/책임에 충실한 개발
각 계층은 자신의 계층이 갖는 책임에만 충실하도록 개발해야 한다.
예를 들어 데이터 접근은 데이터 액세스 계층에서만 처리해야 한다. 그렇지 않으면 Spring이 각각의 책임에 맞게 3가지 계층을 나눈 의미가 떨어지게 된다. 그리고 이에 대한 결과로 유연성이 떨어지고 이해하기 힘든 구조를 갖게 될 것이다.
흔히 저지르는 실수 중 하나로 프레젠테이션 계층에 종속적인 객체를 그대로 서비스 계층으로 전달하는 것이 있다. 서블릿의 HttpServletRequest나 HttpServletResponse, HttpSession과 같은 객체들은 프레젠테이션에 종속적이므로 서비스 계층에 넘기지 않는 것이 좋다. 만약 이러한 코드를 넘기면 서비스 계층의 코드들은 프레젠테이션 계층에 대해 결합도가 높아 재사용이 불가능하며, 테스트 코드를 작성하는 것도 어렵다. 또한 서비스 계층에서 웹과 관련된 예외가 발생할 수도 있을 것이고, 이러한 것들은 예외를 찾기 더욱 힘들게 만들 것이다.