본문 바로가기
공부방

[TIL 07/05] Spring MVC, REST API

by hseong 2023. 7. 7.

1. Spring MVC

경로 변수

@PathVariable

@RequestMapping에서 URL 경로를 템플릿화 할 수 있다. 이때, @PathVariable을 사용하면 URL 경로 안에서 변수를 가지고 올 수 있다.

@GetMapping("/customers/{customerId}")
public String customerId(@PathVariable Long customerId) {
    sout(customerId);
    return "ok";
}

변수 이름이 같은 경우 자동으로 매칭되며 일치하지 않는 경우에는 @PathVariable("customerId")와 같이 명시해주어야 한다.

지원되지 않는 타입으로 변환하려는 경우 TypeMismatchException이 발생한다. 만일 원하는 데이터 유형으로 변환되는 것을 원한다면 타입 변환을 위한 컨버터를 정의해서 등록해줄 수 있다.

HTTP 요청 파라미터 - 폼 처리

GET 쿼리 파라미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다.
이것을 요청 파라미터(Request parameter) 조회 라고 한다.

@RequestParam

스프링의 @RequestParam을 사용하면 요청 파라미터를 편리하게 사용할 수 있다.

@RequestMapping("/customers")
public String getCustomer(
        @RequestParam("email") String email
        @RequestParam("name") String name) {
    sout(email + ", " + name);
    return "ok";
}

HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name) 속성을 생략할 수 있다. String, int 등의 단순 타입이면 @RequestParam도 생략 가능하다.

@ModelAttribute

@ModelAttribute를 사용하면 스프링이 요청 파라미터를 객체화하는 과정을 자동화해준다.

@RequestMapping("/customers")
public String getCustomer(@ModelAttribute CustomerFindRequest request) {
    return "ok";
}

@ModelAttribute도 생략 가능하다. 생략하는 경우 단순 타입은 @RequestParam이 적용되고 나머지 타입에 @ModelAttribute가 적용된다.

@ModelAttribute는 요청 파라미터 이름으로 해당 객체의 프로퍼티를 찾고 setter를 호출해서 파라미터 값을 바인딩한다. 만일 기본 생성자가 없다면 요청 파라미터와 동일한 파라미터를 가진 생성자를 이용해서 값을 바인딩한다.

HTTP 요청 메시지

@RequestBody

HTTP 메시지 바디로 데이터가 전송되는 경우 @RequestBody를 이용하여 데이터를 조회할 수 있다.

@PostMapping("/customers")
public String updateCustomer(@RequestBody CustomerCreateRequest request) {
    return "ok";
}

HTTP 메시지 컨버터가 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.

@RequestBody의 경우 요청 파라미터를 처리하기 위한 @RequestParam, @ModelAttribute와 달리 생략할 수 없다.

2. WebApplicationContext

WebApplicationContextApplicationContext를 구현하고 서블릿 컨텍스트에 접근할 수 있는 기능이 추가되었다.

많은 애플리케이션의 경우 단일 WebApplicationContext를 갖는 것으로 충분하다.

다른 구조로는 하나의 루트 WebApplicationContext를 가지고 여러 개의 DispatcherServlet 인스턴스가 루트를 공유하고 고유한 WebApplicationContext를 가지고 컨텍스트 계층 구조를 가질 수도 있다.

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/context-hierarchy.html

루트 WebApplicationContext는 서비스와 리포지토리 같이 여러 디스패처 서블릿이 공유해야 하는 빈들이 포함된다. 이러한 빈들은 개별 디스패처 서블릿의 WebApplicationContext에서 재정의될 수 있다.

개별 WebApplicationContext에는 디스패처 서블릿에 필요한 컨트롤러, 핸들러 맵핑, 핸들러 어댑터, 뷰 리졸버 등과 같은 빈들이 등록된다.

루트 WebApplicationContext를 만들기 위해서는 ContextLoaderListener를 생성하고 서블릿 컨텍스트에 추가해주어야 한다.

WebApplicationInitializer

스프링 MVC는 서블릿 컨테이너 초기화 작업을 위한 인터페이스를 제공한다. 이를 통해 개발자는 서블릿 컨테이너 초기화 과정을 생략하고, 애플리케이션 초기화 코드만 작성하면 된다.

public class SpringMVC implements WebApplicationInitializer {
    @Override
    public void onStartup(servletContext servletContext) {
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(AppConfig.class);

        DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);

        ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("test", dispatcherServlet);

        servletRegistration.addMapping("/");
    }
}

만일 컨텍스트 계층 구조를 구현하고자 한다면 ContextLoaderListener를 사용할 수 있다.

public class SpringMVC implements WebApplicationInitializer {
    @Override
    public void onStartup(servletContext servletContext) {
        AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
        appContext.register(RootConfig.class);
        ContextLoaderListener loaderListener = new ContextLoaderListener(rootAppContext);
        servletContext.addListener(loaderListener);

        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(AppConfig.class);
        DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
        ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("test", dispatcherServlet);

        servletRegistration.addMapping("/");
    }
}

3. REST(ful) API

REST

월드 와이드 웹과 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍처의 한 형식이다. HTTP의 주요 저자 중 한 사람인 로이 필딩의 2000년 박사 학위 논문에서 소개되었다.

엄격한 의미의 REST는 네트워크 아키텍처 원리의 모음이다. '네트워크 아키텍처 원리'란 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫는다.

간단한 의미로는 웹 상의 자료는 HTTP 위에서 SOAP나 쿠키를 통한 세션 트랙킹 같은 별도의 전송 계층 없이 전송하기 위한 아주 간단한 인터페이스를 말한다.

API

API(Application Programming interface)는 두 개 이상의 컴퓨터 프로그램이 서로 통신할 수 있는 방법이다.

API는 프로그래머가 사용할 수 있는 도구 또는 서비스의 역할을 하는 여러 부분으로 구성되는 경우가 많다. 이러한 부분 중 하나를 사용하는 프로그램이나 프로그래머는 API의 해당 부분을 호출한다고 한다.

API를 구성하는 호출은 서브 루틴, 메서드, 요청 또는 엔드포인트라고 한다. API 사양은 이러한 호출을 정의하며 호출을 사용하거나 구현하는 방법을 설명한다.

API의 목적 중 하나는 시스템 작동 방식에 대한 내부 정보를 숨기고 프로그래머에게 필요한 부분만 노출하는 것이다. 나중에 내부 구현이 변겅되더라도 일관성을 유지할 수 있다.

REST 아키텍처 스타일

클라이언트-서버(client-server)

  • 사용자 인터페이스에 대한 관심을 데이터 저장에 대한 관심으로부터 분리함으로써 클라이언트의 이식성과 서버의 규모확장성을 개선한다.

무상태(stateless)

  • 클라이언트와 서버의 통신에 상태가 없다.
  • 모든 요청에는 필요한 모든 정보를 담고 있어 가시성이 좋고 요청 실패시 복원이 쉽기 때문에 신뢰성이 높다.
  • 상태를 저장할 필요가 없어 규모확장성을 개선한다.

캐시(cache)

  • 캐시가 가능해야 한다.
  • HTTP 프로토콜 표준에서 사용하는 Last-Modified 태그나 E-Tag를 이용하면 캐싱 구현이 가능하다.

균일한 인터페이스(uniform interface)

  • URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍처 스타일을 말한다.

계층화된 시스템(layered system)

  • REST 서버는 다중 계층으로 구성될 수 있으며 보안, 로드 밸런싱, 암호화 계층을 추가해 구조상의 유연성을 둘 수 있고 프록시, 게이트웨이 같은 네트워크 기반의 중간매체를 사용할 수 있다.

Richardson Maturity Model

https://martinfowler.com/articles/richardsonMaturityModel.html

레벨3: Hypermedia Controls

  • HATEOAS(Hypertext As The Engine Of Application State)
  • 해당 리소스를 이용하여 다음에 수행할 수 있는 작업과 이를 조작할 수 있는 URI를 알려준다.

레벨 2: HTTP Verbs

  • HTTP 메서드를 도입
  • GET, POST, PUT, PATCH, DELETE

레벨 1: Resources

  • 리소스 단위로 여러 개의 엔드포인트가 존재
  • 고객은 customers/a처럼 a라는 회원에게 접근을 요청할 것이다.
  • 하나의 리소스에 대해 다양한 형태로 표현 가능해야 한다.

레벨 0: The Swamp of POX

  • 단 하나의 엔드포인트만 존재
  • 요청을 정의하고 서버로 날리면 서버가 요청을 날린다.
  • 서버는 요청을 확인하여 그에 따른 메서드를 호출하고 응답한다.
  • 리소스란 것이 존재하지 않는다.

HATEOS

API로 리소스 대한 것을 받았을 때 다른 리소스에 대한 연결성을 가지는 것이다.

food에 관한 리소스를 받으면 이것에 대해 할 수 있는 행위에 대해 기술해 주는 것이고 이는 links라는 필드로 제공될 것이다.

API 설계 원칙

  1. URI는 정보의 자원을 표현해야 한다.(리소스명은 동사보다 명사를 사용한다.)
  2. 자원에 대한 행위는 HTTP Method로 표현한다.
    (X) GET /members/delete/1
    (O) GET /members/1
    (O) DELETE /members/1
    (O) POST /task/1/run //어떠한 행위를 표시해야 하는 경우 마지막에 액션을 전달한다.
  3. 슬래시(/)는 계층 관계를 나타내는 데 사용한다.
  4. URI 마지막 문자로 슬래시를 포함하지 않는다.
  5. 하이픈(-)은 URI 가독성을 높이는데 사용한다.

4. RestController

스프링은 REST API를 개발하기 위한 애노테이션을 제공한다.

@RequestBody는 HTTP 메시지 바디에 담겨져 온 데이터를 HTTP 메시지 컨버터는 우리가 원하는 형태의 객체로 만들어준다.

@ResponseBody는 View 조회를 무시하고, HTTP 메시지 바디에 해당 내용을 직접 입력한다. @RestController가 내부에 @ResponseBody를 포함하고 있다.

HTTP 메시지 컨버터

스프링 부트는 다양한 유형의 메시지 컨버터를 제공하며 HTTP 요청, 응답에 있어서 이를 사용한다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1#

ArgumentResolver

@RequestMapping을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapterArgumentResolver를 호출하여 핸들러가 필요로 하는 다양한 파라미터 값을 생성한다.

파라미터의 값이 모두 준비되면 핸들러(컨트롤러)를 호출하면서 값을 넘겨준다.

ReturnValueHandler

이것은 컨트롤러가 반환하는 응답값을 변환하고 처리한다. 컨트롤러에서 String으로 뷰 이름을 반환해도 동작하는 이유가 바로 이 덕분이다.

HTTP 메시지 컨버터

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1#

요청의 경우 메서드 파라미터에 따른 적절한 ArgumentResolver가 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성한다.

응답의 경우 반환값에 따른 적절한 ReturnValueHandler가 HTTP 메시지 컨버터를 사용해서 응답 결과를 만든다.

참고

프로그래머스 데브코스 백엔드 4기

김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

스프링 공식 문서 DispatcherServlet

REST - wiki

API - wiki

Richardson Maturity Model