Spring Boot로 이해하는 REST와 RESTful API

Jang990·2023년 2월 11일
0

제가 봤을 때 이해가 되는 수준으로 정리한 글입니다.
제가 배운 Spring과 함께 예시를 들면서 좀 억지스러운 부분도 있고 틀린 부분도 있을 것 같습니다.
혹시라도 더 좋은 예시가 있거나 틀린 부분이 있다면 지적해주시면 감사하겠습니다.

결과적으로 이 글을 작성하면서, RESTful API를 설계할 때는 REST 제약 조건을 준수해야 하고, REST의 구성 요소를 기반으로 RESTful API 서버와 클라이언트가 통신을 한다고 이해했습니다.


Rest(Representational State Transfer)란?

REST는 Representational State Transfer의 약자입니다. 소프트웨어 아키텍처의 한 형식입니다. REST는 네트워크 아키텍처 원리의 모음입니다. 여기서 '네트워크 아키텍처 원리'란 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫습니다.

REST의 구성요소를 살펴보고 REST의 제약 조건에 대해 살펴보겠습니다.



REST의 구성 요소

REST의 구성요소는 3가지가 있습니다.

  • 자원(Resource): URI
  • 행위(Verb): HTTP Method
  • 표현(Representation of Resource)

스프링에서의 날씨 웹 사이트를 예시로 각각의 구성요소를 살펴보겠습니다.

자원(Resource): URI

API를 통해 액세스할 수 있는 리소스를 식별하는 데 사용됩니다.

Client는 URI를 이용해서 자원을 지정하고 해당 자원의 상태(정보)에 대한 조작을 Server에 요청합니다.

Spring에서는 @RequestMapping 등을 사용해서 URI를 지정할 수 있습니다.

행위(Verb): HTTP Method

HTTP 프로토콜의 Method를 사용합니다.

다음 예시에서는 GET, POST, DELETE만을 사용하고 있습니다.

GET /api/weather/{city} - 특정 도시의 날씨를 가져옵니다.
POST /api/weather - 특정 도시의 날씨를 업데이트 합니다.
DELETE /api/weather/{city} - 특정 도시의 날씨를 삭제합니다.

특정 도시의 날씨를 삭제하는 것은 좀 이상하지만 DELETE의 예시를 위해 사용한 점 양해 부탁드립니다.

Spring에서는 @GetMapping, @PostMapping, @DeleteMapping 등의 어노테이션을 사용하여 Method를 지정할 수 있습니다.

표현(Representation of Resource)

요청에 대한 응답으로 API에서 반환하는 데이터입니다. 리소스 표현은 일반적으로 JSON, XML 등의 구조화된 형식이며 클라이언트에 반환됩니다.

Client가 자원의 상태(정보)에 대한 조작을 요청하면 Server는 이에 적절한 응답(Representation)을 보냅니다.

Spring에서는 ResponseEntity<>를 사용하면 JSON, XML 등등의 형식의 데이터를 반환 받을 수 있을 것입니다.

클라이언트의 요청 헤더에 Accept에 따라 JSOM 형식 또는 XML 형식 등으로 응답을 반환 받을 수 있습니다. 헤더에 Accept 요청을 지정하지 않는다면 디폴트 형식(일반적으로 JSON)으로 응답을 반환합니다.

 Accept 요청
application/json -JSON 형식
application/xml -XML 형식
text/html - HTML 형식
...

Spring 코드 예시

다음 코드에서는 ResponseEntity<>를 통해 클라이언트에게 JSON, XML 등의 구조화된 형식의 데이터를 반환하고,
@GetMapping, @PostMapping, @DeleteMapping 어노테이션을 통해 행위를 지정하고 있습니다.
또한 @RequestMapping, @GetMapping, @DeleteMapping 어노테이션을 통해 URI를 지정합니다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/weather")
public class WeatherController {
  
  private final WeatherService weatherService;
  
  // 특정 도시{city}의 날씨를 가져옵니다.
  @GetMapping("/{city}")
  public ResponseEntity<Weather> getWeather(@PathVariable String city) {
    Weather weather = weatherService.getWeather(city);
    return ResponseEntity.ok(weather);
  }
  
  // 특정 도시의 Weather를 받아서 날씨를 업데이트 합니다.
  @PostMapping
  public ResponseEntity<Weather> updateWeather(@RequestBody Weather weather) {
    Weather updatedWeather = weatherService.updateWeather(weather);
    return ResponseEntity.ok(updatedWeather);
  }
  
  // 특정 도시{city}의 날씨를 삭제합니다.
  @DeleteMapping("/{city}")
  public ResponseEntity<Void> deleteWeather(@PathVariable String city) {
    weatherService.deleteWeather(city);
    return ResponseEntity.noContent().build();
  }
}


REST 제약 조건

REST의 제약 조건은 다음과 같이 6가지가 존재합니다.

  • 원칙 1. 클라이언트-서버 구조
  • 원칙 2. 무상태(Stateless)
  • 원칙 3. 캐시 처리 가능 (Cachealble)
  • 원칙 4. 일관된 인터페이스(Uniform Interface)
  • 원칙 5. 계층화(Layered System)
  • 원칙 6. Code on demand (optional)

원칙 6의 경우 optional이기 때문에 일단 제외하고 나중에 추가하도록 하겠습니다.

원칙 1. 클라이언트-서버 구조

REST는 각각 고유한 책임과 인터페이스가 있는 클라이언트와 서버 간의 관심사 분리를 정의합니다.
관심사 분리(Separation of concerns)는 클라이언트-서버 제약 조건의 기본 원칙으로 데이터 스토리지 문제에서 사용자 인터페이스를 분리합니다.

날씨 웹 사이트를 예로 들어보겠습니다. 이 경우 클라이언트는 날씨를 확인하려는 사용자의 웹 브라우저입니다. 이 경우 서버는 현재 날씨 정보를 제공하는 날씨 API입니다.

사용자가 날씨 웹사이트를 열면 브라우저는 날씨 API 서버에 현재 날씨 정보에 대한 요청을 보냅니다. 날씨 API 서버는 요청을 수신하고 처리한 후 현재 날씨 정보와 함께 브라우저에 응답을 보냅니다. 그런 다음 브라우저는 사용자가 볼 수 있도록 날씨 웹사이트에 대한 정보를 표시합니다.

브라우저는 정보 요청을 보내고 서버는 데이터로 응답합니다. 클라이언트와 서버 간의 책임 분리는 명확합니다. 브라우저는 요청 전송 및 데이터 표시만 담당하고 weather API 서버는 요청 처리 및 데이터 제공을 담당합니다.


원칙 2. 무상태(Stateless)

각 요청 간 클라이언트의 정보가 서버에 저장되어서는 안 됩니다.

즉 사용자의 세션 또는 쿠키와 같은 특정 정보는 서버에 저장되어서는 안 됩니다.

다시 날씨 웹사이트로 돌아가보겠습니다. 예를 들어 사용자가 현재 '서울'에 있는 사용자가 현재 지역의 날씨 정보를 원한다면 사용자의 위치를 파라미터로 넣어서 다음과 같은 요청을 보내야 합니다.

GET /api/weather/current?location={사용자의위치}
GET /api/weather/current?location=서울

서버는 모든 요청에 사용자의 위치에 대한 정보가 포함되어 있으므로 사용자의 위치에 대한 정보를 서버에서 저장할 필요가 없습니다.

다음과 같은 형식으로 요청을 주고 받으면 각 요청이 독립적이며 이전 요청이나 응답에 의존하지 않게 됩니다. 그러므로 Server는 각각의 요청을 완전히 별개의 것으로 인식하고 처리할 수 있습니다.

이 예시는 아주 간단한 예시이고, 서버에서 사용자의 로그인과 관련된 정보를 가지지 않는 방법으로는 JWT 인증 방식이 있습니다.


원칙 3. 캐시 처리 가능 (Cachealble)

효율성을 향상시키기 위해 응답을 캐싱할 수 있어야 합니다.

캐시는 효율성, 확장성을 향상시키고 평균 대기 시간을 줄임으로써 사용자가 느끼는 성능을 향상시킬 수 있습니다.

Spring에서 캐싱을 하기 위해서는 헤더에 캐시 관련 값 추가, 스프링 캐시 사용 등이 있습니다.

먼저 @Cacheable 사용 예시를 살펴보겠습니다.
@Service
public class WeatherService {
@Cacheable(value = "products", key = "#city", expire = 3600)
public Weather getWeather(String city) {
// retrieve the Weather from the database
...
return weather;
}
}
위와 같이 설정하면 WeatherService에서 들어온 String city에 해당하는 값에 대한 반환값을 3600초간 캐싱합니다. 처음 cityId값이 들어온 시점에 캐싱되고 3600초 이후 다시 getWeather(String cityId) 메서드 안에 코드를 실행시킵니다.


다음은 헤더에 캐시 관련 값을 추가하는 예시를 살펴보겠습니다.

@GetMapping("/{city}")
public ResponseEntity<Weather> getWeather(@PathVariable String city) {
    Weather weather = weatherService.getWeather(city);
    HttpHeaders headers = new HttpHeaders();
    headers.add("Cache-Control", "max-age=3600");
    return new ResponseEntity<>(weather, headers, HttpStatus.OK);
}

해당 코드는 헤더에 Cache-Control에max-age=3600값을 설정한 후 헤더에 실어서 클라이언트에게 보냅니다. 이 응답을 받은 클라이언트는 해당 반환 값을 3600초간 캐싱하도록 따로 설정합니다.

두 코드의 차이는 @Cacheable관련 코드는 캐시를 서버 내에서 처리하고, 헤더를 이용한 캐시 처리는 클라이언트에서 캐시를 처리하도록 하는 것입니다.


_해당 예시에서는 Weather를 3600동안이나 캐싱하고 있습니다. 하지만 날씨는 지속적으로 바뀌는데 3600초나 캐싱하는 것은 바람직하지 않은 것 같습니다. 이렇게 자주 값이 바뀌는 경우에는 최신 데이터를 DB에서 검색하는 것이 더 나을 수 있습니다. 성능과 정확도를 잘 비교하여 상황에 따라 절충해서 사용해야 할 것입니다._

원칙 4. 일관된 인터페이스(Uniform Interface)

URI로 지정한 Resource에 대한 요청을 통일되고, 한정적으로 수행하는 아키텍처 스타일을 의미합니다. HTTP 표준 프로토콜에 따르는 모든 플랫폼에서 사용이 가능합니다. 따라서 특정 언어나 기술에 종속되지 않습니다.

쉽게 말하자면, 모든 요청과 응답이 균일한 구조를 가져야 클라이언트 애플리케이션이 API를 더 쉽게 이해하고 사용할 수 있습니다.

이것은 앞서 살펴본 날씨 앱의 Weather 요청을 보면 알 수 있습니다. 앞서 본 REST 원칙을 지킨 URI와 REST 원칙을 지키지 않은 URI를 확인해보겠습니다.

REST 원칙을 지킨 URI
GET /api/weather/{city} - 특정 도시의 날씨를 가져옵니다.
POST /api/weather - 특정 도시의 날씨를 업데이트 합니다.
DELETE /api/weather/{city} - 특정 도시의 날씨를 삭제합니다.
REST 원칙을 지키지 않은 URI
GET /weather-info/get-weather?dosi={city} - 특정 도시 날씨 가져오기
POST /update/weather/weather-info - 특정 도시 날씨 업데이트하기
POST /delete-weather/weather-info?dosi={city} - 특정 도시 날씨 삭제하기

위 URI와 아래 URI의 차이를 명확하게 확인할 수 있을 것입니다.

REST를 지킨 해당 URI를 통해 클라이언트는 서버 측에 특정 설정 정보에 관계없이
GET Method를 사용하면 URI에 해당하는 특정 정보를 얻을 수 있고,
POST Method를 사용하면 URI에 해당하는 특정 정보를 업데이트할 수 있고,
DELETE Method를 사용하면 URI에 해당하는 특정 정보를 삭제할 수 있다고 이해할 수 있습니다.


원칙 5. 계층화(Layered System)

REST Server는 다중 계층으로 구성될 수 있다.
API Server는 순수 비즈니스 로직을 수행하고 그 앞단에 보안, 로드밸런싱, 암호화, 사용자 인증 등을 추가하여 구조상의 유연성을 줄 수 있다.
또한 로드밸런싱, 공유 캐시 등을 통해 확장성과 보안성을 향상시킬 수 있다.

이 부분은 아직 정확하게 모르겠습니다...


원칙 6. Code on demand (optional)

추후 작성 ...



RESTful API란?

RESTful API는 결국 웹 서비스 구축을 위한 REST(Representational State Transfer) 아키텍처 패턴을 따르는 API 유형입니다. 즉 REST 제약 조건(원칙)을 준수하는 API라 할 수 있습니다.

REST의 5가지 원칙을 지켜야만 RESTful API라 할 수 있나?

앞서 본 제약 조건을 모두 따라야 RESTful API이다.

VS

개발자가 실제 API를 구축할 때 절충해야 하는 등 다양한 이유가 있기 때문에 RESTful API 디자인의 목표는 확장 가능하고 유지 관리가 가능하며 유연하며 사용자의 요구를 충족할 수 있는 API를 만드는 것입니다. API가 REST의 모든 원칙을 엄격하게 준수하는지 여부는 두 번째 문제입니다.

참고한 내용

https://ko.wikipedia.org/wiki/REST
https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
https://restfulapi.net/rest-architectural-constraints/
https://gmlwjd9405.github.io/2018/09/21/rest-and-restful.html

profile
공부한 내용을 적지 말고 이해한 내용을 설명하자

0개의 댓글