너굴 개발 일지

[Spring Boot] REST API 에 대해서.. 본문

SPRING BOOT

[Spring Boot] REST API 에 대해서..

너굴냥 2021. 10. 9. 15:33

Rest API가 대충 어떤건지만 알고 정확한 개념이나 설계하는 법을 자세히 몰랐기에 인프런에서 들은 강의를 토대로 배운 내용을 정리한 것입니다. 참고로 강의는 Dowon Lee 님의 Spring Boot를 이용한 RESTful Web Services 개발 입니다.

 

 

[REST 그리고 SOAP]

- RESTSOAP각기 다른 두 가지의 온라인 데이터 전송 방식

- 둘 다 웹 애플리케이션 간 데이터 통신을 허용하는 애플리케이션 프로그래밍 인터페이스(Application Programming Interface, API)를 구축하는 방법을 정의

- 하지만 SOAP프로토콜, REST아키텍쳐 스타일로 본질적으로 서로 다른 기술로 애플리케이션을 개발하는 데 있어서 각각의 방식에 장단점이 있음

 

[API란?]

- 어떤 서버의 특정한 부분에 접속해서 그 안에 있는 데이터와 서비스를 이용할 수 있게 해주는 소프트웨어 도구

- 두 개의 소프트웨어가 서로 통신을 주고받을 수 있으며, 이는 우리가 모바일을 이용해서 하는 모든 행동들의 기반

- API 존재로 IT 아키텍처가 간단하게 만들어질 수 있고, 마케팅에 힘을 실어주며, 데이터 공유가 더 쉬워짐

즉, API는 프로그램들이 서로 상호작용하는 것을 도와주는 매개체 역할입니다.

 

그러면 API를 사용하면 어떤 이점이 있을까?

- Private API 이용시 => 개발자들이 애플리케이션 코드를 작성하는 방법을 표준화함

                                 => 간소화되고 빠른 프로세스 처리가 가능

                                 => 소프트 웨어를 통합하고자 할 때는 개발자들 간의 협업을 용이하게 함

 

[SOAP 이란?]

- Simple Object Access Protocol 의 약어로 말그대로 프로토콜을 의미

- REST 보다 더 많은 표준들이 정해져 있어서 조금 더 복잡함

- 보안 수준이 엄격해, 은행용 모바일 앱처럼 보안 수준이 높아야 하거나 신뢰할 수 있는 메시징 앱, 또는 ACID를 준수해야 하는 경우에 선호함

- XML에만 의존하기 때문에 REST에 비해 무거움

 

[REST란?]

- Representational State Transfer의 약어로 네트워크 상에서 Client와 Server 사이의 통신 방식 중 하나로 자원(Resource)의 표현(Representation)에 의한 상태 전달을 의미

      - 자원: 해당 소프트웨어가 관리하는 모든 것 (문서, 그림, 데이터 등)

      - 자원의 표현 : 그 자원을 표현하기 위한 이름 (DB의 게시판 정보가 자원일때, 'boards'를 자원의 표현

      - 상태 전달 : 데이터가 요청되어지는 시점에서 자원의 상태를 전달 (JSON 혹은 XML을 통해 데이터 주고 받는 것이 일반적)

 

- 기본적으로 웹의 기존 기술HTTP 프로토콜을 그대로 활용하기 때문에 웹의 장점을 최대한 활용할 수 있는 아키텍처 스타일

 

구체적인 의미는 ?

하나의 URI -> 하나의 고유한 리소스(Resource)를 대표하도록 설계된다는 개념에 전송방식을 결합해서 원하는 작업을 지정

예를 들어 '/boards/123'은 게시물 중에서 123번이라는 고유한 의미를 가지도록 설계하고, 이에 대한 처리는 GET, POST 방식과 같이 추가적인 정보를 통해 결정함

 

참고로 URI & URL의 차이는 ?

더보기

URL (Uniform Resource Locator) : 이곳에 가면 당신이 원하는 것을 찾을 수 있다는 상징적 의미

URI (Uniform Resource Identifier) : 당신이 원하는 곳의 주소는 여기다 라는 현실적이고 구체적인 의미

URL은 URI의 하위 개념으로 URI의 I는 마치 데이터베이스의 PK 같은 의미로 사용되는 셈

 

[REST의 장단점은?]

장점

  • HTTP 프로토콜의 인프라를 그대로 사용하므로 REST API 사용을 위한 별도의 인프라를 구출할 필요가 없음
  • HTTP 표준 프로토콜에 따르는 모든 플랫폼에서 사용이 가능
  • REST API 메시지가 의도하는 바를 명확하게 나타내므로 의도하는 바를 쉽게 파악 가능
  • 서버와 클라이언트의 역할을 명확하게 분리

단점

  • 표준이 존재하지 않음
  • 사용할 수 있는 메소드가 한정적 (4가지)
  • 브라우저를 통해 테스트할 일이 많은 서비스라면 쉽게 고칠 수 있는 URL보다 Header 값이 왠지 더 어렵게 느껴짐
  • 구형 브라우저가 아직 제대로 지원해주지 못하는 부분이 존재 (PUT, DELETE를 사용하지 못하는 점)

 

[REST가 필요한 이유?]

  • 애플리케이션 분리 및 통합
  • 다양한 클라이언트의 등장
  • 최근 서버 프로그램은 모바일 디바이스에서도 통신을 할 수 있어야 함
  • 멀티 플랫폼에 대한 자원을 위해 서비스 자원에 대한 아키텍처를 세우고 이용하는 방법을 모색한 결과, REST에 관심을 가지게 됨

 

[REST 구성 요소]

자원(Resource) : URL

  • 모든 자원에는 고유한 ID가 존재, 이 자원은 Server에 존재
  • 자원을 구별하는 ID는 'groups/group_id'와 같은 HTTP URL
  • Client는 URL을 이용해 자원을 지정, 해당 자원의 상태에 대한 조작을 Server에 요청

행위(Verb) : HTTP Method

  • HTTP 프로토콜의 Method를 사용
  • HTTP 프로토콜은 GET, POST, PUT, DELETE와 같은 메서드 제공

표현(Representation of Resource)

  • Client가 자원의 상태에 대한 조작을 요청하면 Server는 이에 적절한 응답을 보냄
  • REST에서 하나의 자원은 JSON, XML, TEXT 등 여러 형태의 Representation으로 나타내짐
  • JSON 혹은 XML을 통한 데이터 교환이 일반적

 

[REST API는 뭐지?]

  • REST를 기반으로 서비스 API를 구현한 것
  • 최근 Open API, 마이크로 서비스 등을 제공하는 업체 대부분은 REST API를 제공

 

[REST API의 특징은?]

  • 사내 시스템들도 확장성, 재사용성을 높여 유지보수 및 운용을 편리하게 할 수 있음
  • REST는 HTTP 표준을 기반으로 구현, 따라서 HTTP를 지원하는 프로그램 언어로 클라이언트, 서버를 구현할 수 있음
  • 즉, REST API를 제작하면 자바, C# 등을 이용해 클라이언트를 제작할 수 있음

 

[RESTful은?]

  • REST API를 제공하는 웹 서비스
    • (Web Service :  네트워크 상에서 서로 다른 종류의 컴퓨터들 간에 상호작용하기 위한 소프트웨어 시스템 )
  • 즉, REST 원리를 따르는 시스템은 RESTful이란 용어로 지칭

 

[RESTful은 왜 쓰는 거지?]

  • 이해하기 쉽고 사용하기 쉬운 REST API를 만드는 것
  • RESTful한 API를 구현하는 근본적인 목적은 일관적인 컨벤션을 통한 API의 이해도 및 호환성을 높이는 것, 따라서 성능이 중요한 상황이라면 굳이 RESTful한 API를 구현할 필요는 없음

 

[그러면 어떤 것이 RESTful하지 못할까?]

  • CRUD 기능을 모두 POST로만  처리하는 API
  • route에 resource, id 외의 정보가 들어가는 경우(/students/updateName)

그러면 SpringBoot로 REST API 설계에 대해 알아보자, postman을 사용하여 return 값을 확인한다.

 

[예제 1 - HelloWorldBean]

package com.example.restful.helloworld;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class HelloWorldBean {
    private String message;
}
package com.example.restful.helloworld;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
    @GetMapping(path = "/hello-world")
    public String helloWorld(){
        return "Hello World";
    }

    @GetMapping(path = "/hello-world-bean")
    public HelloWorldBean helloWorldBean(){
        return new HelloWorldBean("Hello World");
    }

    @GetMapping(path = "/hello-world-bean/path-variable/{name}")
    public HelloWorldBean helloWorldBean(@PathVariable String name){
        return new HelloWorldBean(String.format("Hello World, %s", name));
    }
}
  • @RestController : @Controller + @ResponseBody의 역할로 반환시키고자 하는 데이터 값을 ResponseBody에 저장하지 않아도 자동으로 json 포맷으로 반환시킴
  • @PathVariable : URL에서 각 구분자에 들어오는 값을 처리할 때 사용 (null이나 공백값이 들어가는 parameter라면 적용하지 않기), 원하는 타입으로 자동으로 바인딩된다.

결과값 확인하기

더보기
hello-world라는 end-point의 get 방식
HelloWorldBean 객체가 json 타입으로 반환
url에서 name 값을 raccoon으로 했을 때의 결과값

 

[예제2 - User Service API 구현]

- 사용자 목록 조회를 위한 API 구현 : GET HTTP Method

package com.example.restful.user;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Date joinDate;
}
package com.example.restful.user;

import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

@Service
public class UserDaoService {
    private static List<User> users = new ArrayList<User>();

    private static int userCount = 3;
    static {
        users.add(new User(1, "racoon", new Date()));
        users.add(new User(2, "cat", new Date()));
        users.add(new User(3, "dog", new Date()));
    }

    public List<User> findAll(){
        return users;
    }

    public User findOne(int id){
        for(User user : users){
            if(user.getId().equals(id)){
                return user;
            }
        }
        return  null;
    }
}
package com.example.restful.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

@RestController
public class UserController {
    private UserDaoService service;

    public UserController(UserDaoService service){
        this.service = service;
    }

    @GetMapping("/users")
    public List<User> retrieveAllUsers(){
        return service.findAll();
    }

    @GetMapping("/users/{id}")
    public User retrieveUser(@PathVariable int id){
        User user = service.findOne(id);

        if(user == null){
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }
        return user;
    }
}
package com.example.restful.user;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

 

 

GET Method 실행시 모든 사용자 조회, 특정 사용자 조회 결과 성공

  • 모든 사용자 조회, 특정 사용자 조회 결과인데 200번대의 Status Code가 뜨면서 존재하는 사용자일 경우 정상적으로 결과가 출력됨

특정 사용자 조회 실패 결과

  • 하지만 존재하지 않는 특정 사용자를 조회할 경우 404 Status Code와 에러 메세지가 뜨는데 이는 UserNotFoundException의 @ResponseStatus(HttpStatus.NOT_FOUND)로 인해 500번대의 상태 코드가 아닌 404 코드가 발생
  • 에러의 원인도 다양하므로 이처럼 예외 클래스를 만들어서 에러가 나는 원인을 표시해주는 것이 좋음

- 사용자 등록을 위한 API 구현 - POST HTTP Method

package com.example.restful.user;

import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

@Service
public class UserDaoService {
    ... 위 생략 ...

    public User save(User user){
        if(user.getId() == null){
            user.setId(++userCount);
        }
        users.add(user);
        return user;
    }
}
package com.example.restful.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.List;

@RestController
public class UserController {
    private UserDaoService service;

    public UserController(UserDaoService service){
        this.service = service;
    }

	... 중간 생략
    
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody User user){
        User savedUser = service.save(user);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(savedUser.getId())
                .toUri();

        return ResponseEntity.created(location).build();
    }
}

결과

  • POST Method를 이용하여 Body에 JSON 타입으로 User 객체에 대한 정보를 넣고 사용자를 추가시킨 결과 201의 상태 코드를 나타내었고 모든 사용자 조회시 정상적으로 사용자 추가된 것을 확인할 수 있다.
  • @RequestBody : 클라이언트로부터 (json,xml 등)오브젝트 형태의 타입을 받을 때 사용