
REST(Representational State Transfer)란 분산 하이퍼미디어 시스템 아케틱처(ex.월드 와이드 웹)의 한 형식으로, 주고받은 resource에 이름을 규정하고 URI에 명시해 HTTP 메서드(GET, POST, PUT, DELETE)를 통해 해당 resource의 상태를 주고받은 것을 의미한다.
REST API는 REST 아키테처를 따르는 시스템 또는 애플리케이션 인터페이스라고 할 수 있다.
REST API 설계 시에는 다음과 같은 규칙을 따라야 한다.
- URI의 마지막에는 '/'를 포함하지 않는다
ex) http://localhost.com/product/ (x)- 언더바(_) 사용x, 하이픈(-) 사용
ex) http://localhost.com/product_company_name (x)- URL에는 행위가 아닌 결과를 포함한다
ex) http://localhost.com/get-product (x) http://localhost.com/product (o)- URI는 소문자로 작성한다
- 파일의 확장자는 URI에 포함하지 않는다 -> HTTP에서 제공하는 Accept 헤더를 사용하는 것이 좋다
GET, POST, PUT, DELETE 순으로 알아볼 것인데 Controller만 사용하여 외부의 요청에 응답하는 기능을 구현해보자.
외부에서 서버에 값을 가져올 때 사용하는 API이다.
RESTful 웹서비스를 제공하는 Controller를 정의할 때에는 @Controller가 아닌 @RestController를 사용한다. 따라서 클래스 앞에 @RestController를 추가해주어야 한다.
@RestController
@RequestMapping("/test/get")
public class GetController {
}
클래스 수준에서 @RequestMapping을 설정하면 클래스 내부에서 선언한 메서드 URL 앞에 지정한 URL이 공통값으로 적용된다.
예를 들어, GetController 내부에 메서드를 정의하고 요청 URL을 /method1으로 지정한다면 이 메서드를 호출하기 위해서는 /test/get/method1을 호출해야 한다는 것이다.
@RequestMapping은 인수인 method = 설정을 하지 않을 경우 모든 HTTP 요청을 받는다.
GET으로 사용할 경우 method = RequestMethod.GET으로 설정하면 된다. 그 외 POST나 PUT 등으로 사용하려면 method = RequestMethod.POST, method = RequestMethod.PUT으로 설정하면 된다.
@RestController
@RequestMapping("/test/get")
public class GetController {
@RequestMapping(value = "/requestMapping", method = RequestMethod.GET)
public String getValue(){
return "Value";
}
}
value로 URL을 설정하고 method로 HTTP 요청을 선택할 수 있다.
이 메서드는 GET API이기 때문에 요청하면 데이터를 받기만 할 뿐 수정하거나 삭제할 수는 없다.
Talend API Tester를 사용하여 결과를 확인해보자.
요청에 대한 데이터를 BODY 즉, ResponseBody로 응답을 하였는데, @RestController는 이 ResponseBody와 Controller의 기능을 합친 것이라 생각하면 된다.
@RequestMapping은 스프링 4.3 이후부터 @GetMapping, @PostMapping 등이 등장하면서 사용되지 않는다.
@RequestMapping의 method = RequestMethod.GET로 설정할 필요없이 @GetMapping으로 사용하는 것이 더 좋은 방법이며 앞으로도 이 방법을 사용할 것이다.
@GetMapping으로 매개변수가 없는 GET API를 구현해보자.
@RestController
@RequestMapping("/test/get")
public class GetController {
@GetMapping("/getValue")
public String getValue(){
return "value";
}
}
하지만 실무에서는 매개변수를 받지 않는 메서드는 거의 없기 때문에 매개변수를 받는 메서드를 구현할 수 있어야 한다.
이번에는 매개변수를 받는 GET 메서드를 구현해볼건데 URL 자체에 값을 담아 요청하면 이를 매개변수로 받는 @PathVariable을 사용해보자.
@RestController
@RequestMapping("/test/get")
public class GetController {
@GetMapping("/getValue/{varible}")
public String getValue(@PathVariable String variable){
return variable;
}
}
중괄호 안에 들어가 있는 변수와 @PathVariable로 지정한 변수는 이름이 동일해야 하며 실제 요청 시에는 중괄호는 들어가지 않는다.
이해를 위해 Talend API Tester를 사용하여 요청을 해보자.
중괄호 자리에 요청할 값을 입력하면 서버에서 입력한 값을 반환한 것을 알 수 있다.
이 외에 @PathVariable로 지정한 변수를 중괄호 안에 있는 변수와 이름을 동일하게 하지 않는 방법도 있다.
@RestController
@RequestMapping("/test/get")
public class GetController {
@GetMapping("/getValue/{variable}")
public String getValue(@PathVariable("variable") String str){
return str;
}
}
중괄호의 변수명과 동일한 변수명을 @PathVariable 뒤에 괄호를 열어 입력하면 @PathVariable로 지정한 변수는 이름이 동일하지 않아도 된다.
URL 경로에 값을 담아 요청을 보내는 방법 외에도 쿼리 형식으로 값을 전달할 수 있다.
매개변수를 @RequestParam으로 지정하면 URI에서 ? 기준 우측에 key=value 형태로 구성된 요청을 보내는 것이다.
@RestController
@RequestMapping("/test/get")
public class GetController {
@GetMapping("/getValue")
public String getValue(@RequestParam String name, @RequestParam int age, @RequestParam String location){
return "name : " + name + "\n" + "age : " + age + "\n" + "location : " + location;
}
}
매개변수로 name, age, location 3개를 받는데 모두 @RequestParam으로 지정하였다.
URI 요청 시 key=value 형태로 입력하면 입력한 value 값이 매개변수로 들어가게 되는 것이다.
각각의 key는 매개변수명과 동일하며, 입력하고자 하는 값을 value로 넣고 &를 기준으로 구분된다.
만약, 쿼리스트링에 어떤 값이 들어올 지 정해져있지 않을 경우 Map을 활용하여 매개변수를 받는 것이 효율적이다.
@RestController
@RequestMapping("/test/get")
public class GetController {
@GetMapping("/getValue")
public String getValue(@RequestParam Map<String,String> param){
StringBuilder sb = new StringBuilder();
param.entrySet().forEach(map -> sb.append(map.getKey() + " : " + map.getValue() + "\n"));
return sb.toString();
}
}
Map의 요소를 Set으로 바꾸고 각 요소에서 key-value를 추출하여 StringBuilder에 추가하는 메서드인데 매개변수를 Map으로 받기 때문에 쿼리스트링에 몇 개가 오든 상관없다.
DTO란 Data Transfer Object의 약자로, 데이터 전달 객체다. 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용된다.
매개변수로 DTO를 사용할 것이기 때문에 DTO를 먼저 만들어야 한다.
학생의 이름과 나이, 주소를 필드값으로 가지는 클래스를 생성하겠다.
public class StudentDTO {
String name;
int age;
String address;
public StudentDTO(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "StudentDTO{" + "\n" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
필드값과 생성자, getter, toString()을 정의하고 이를 매개변수로 받는 GET API를 정의한다.
@RestController
@RequestMapping("/test/get")
public class GetController {
@GetMapping("/getValue")
public String getValue(StudentDTO studentDTO){
return studentDTO.toString();
}
}
이제 Talend API Tester를 사용하여 데이터를 요청할 것인데, 쿼리스트링으로 앞에서와 동일하게 ? 기준으로 우측에 key=value 형식으로 입력하면 된다.
DTO 클래스에 선언된 필드는 쿼리 파라미터의 key와 매핑되기 때문에 URI에는 순서와 상관없이 key=value에 해당하는 값으로 입력이 된다.
받아야 할 파라미터가 많을 경우 이와 같이 DTO 객체를 활용하면 코드의 가독성을 높일 수 있다.
GET API에서는 URL의 경로나 파라미터에 변수를 넣어 요청을 보냈지만 이번에는 POST API를 통해 저장하고자 하는 리소스나 값을 HTTP Body에 담아 서버에 전달할 것이다.
회원 정보를 예를 들어 설명하자면, 회원의 이름을 알고자 서버에 요청을 하면 GET API가 호출되는 것이고 회원 정보를 등록할 경우에는 POST API를 호출해야 하는 것이다.
POST API에서는 HTTP Body에 리소스를 담아야 하기 때문에 매개변수에 @RequestBody를 추가해야 한다.
앞에서 언급한 Map과 DTO를 활용한 방식을 적용해보겠다.
@RestController
@RequestMapping("/test/post")
public class PostController {
@PostMapping("/postValue")
public String postValue(@RequestBody Map<String,String> body){
StringBuilder sb = new StringBuilder();
body.entrySet().forEach(map -> sb.append(map.getKey() + " : " + map.getValue() + "\n"));
return sb.toString();
}
}
HTTP Body에 담을 내용을 Map으로 정의하면 key-value 형태만 맞춘다면 제약없이 매핑이 가능하다.
Map에 매핑할 리소스를 JSON 형식으로 입력하면 key,value에 맞게 매핑이 되어 전달이 된다.
다음은 DTO를 매개변수로 삼아 작성해보겠다.
앞에서 사용한 StudentDTO를 다시 사용하겠다.
@RestController
@RequestMapping("/test/post")
public class PostController {
@PostMapping("/postValue")
public String postValue(@RequestBody StudentDTO studentDTO){
return studentDTO.toString();
}
}
마찬가지로 key,value에 맞게 매핑이 되었다.
다시 정리하자면, GET API에서는 Map이든 DTO든 URI에 리소스를 입력하여 요청을 하였지만, POST API에서는 JSON 형식으로 데이터를 입력하고 이는 HTTP Body를 통해 서버에 전달되는 방식이다.
HTTP Body를 사용하여 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트(수정)하는 데 사용되는 API이다.
POST API와 컨트롤러 클래스를 구현하는 방법은 거의 동일하다.
@RestController
@RequestMapping("/test/put")
public class PutController {
@PutMapping("/putValue")
public String putValue(@RequestBody Map<String,String> body){
StringBuilder sb = new StringBuilder();
body.entrySet().forEach(map -> sb.append(map.getKey() + " : " + map.getValue() + "\n"));
return sb.toString();
}
}
컨트롤러 클래스를 구현하는 방법은 POST API와 거의 동일하기 때문에 Talend API Tester의 결과도 동일하다.
서버에서 리소스를 식별할 수 있는 값을 받아 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제까지 하는 역할을 한다.
요청방식은 GET API와 동일하게 URL에 값을 넣는 방식이다.
GET API와 동일한 방식이기 때문에 코드만 보고 가자.
@RestController
@RequestMapping("/test/delete")
public class DeleteController {
@DeleteMapping("/deleteValue/{Variable}")
public String deleteValue(@PathVariable String Variable){
return "Delete " + Variable;
}
}
@PathVariable로 지정된 변수와 중괄호 안에 변수명을 동일하게 하거나 변수명을 다르게 하고 싶으면
@RestController
@RequestMapping("/test/delete")
public class DeleteController {
@DeleteMapping("/deleteValue/{Variable}")
public String deleteValue(@PathVariable("Variable") String value){
return "Delete " + value;
}
}
@PathVariable 옆에 소괄호로 중괄호 안에 있는 변수와 이름을 동일하게 지정하면 된다.
결과는 동일하게 출력된다.
마찬가지로 GET API와 방법은 동일하다.
@RestController
@RequestMapping("/test/delete")
public class DeleteController {
@DeleteMapping("/deleteParam")
public String deleteParam(@RequestParam String name, @RequestParam int age, @RequestParam String address){
return "name = " + name + "\n" + "age = " + age + "\n" + "address = " + address;
}
}
URL에 리소스 구분자로 ?을 사용하면 된다.
GET API는 DELETE API와 요청 방식이 동일하며 차이는 조회 후 삭제 유무이다.
데이터를 등록하거나 수정할 경우 @ResponseBody를 사용하지만 데이터를 조회하는 경우에는 @RequestParam으로 URL에 리소스를 식별할 수 있는 값을 받는다고 이해하면 된다.
위의 내용을 바탕으로 학생 정보를 저장 -> 조회 -> 수정 -> 삭제 순으로 관리해보자.
우선, DTO와 Repository를 생성한다.
//학생 DTO 클래스
public class Student {
long id;
String name;
int age;
String address;
public Student(long id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "StudentInfo" + "\n" + "[" + "id = " + id + "name = " + name + "age = " + age + "address = " + address;
}
}
//학생 Repository 클래스
public class StudentMemoryRepository implements StudentRepository{
private Map<Long, Student> studentMap = new HashMap<>();
public Student findById(long id){
return studentMap.get(id);
}
public void saveStudent(long id, Student student){
studentMap.put(id,student);
}
public void updateStudent(long id, Student student){
studentMap.replace(id,student);
}
public void deleteStudent(long id){
studentMap.remove(id);
}
}
여기서 StudentMemoryRepository는 구현체 클래스이며 메모리에 저장하는 방식이기 때문에 스프링 애플리케이션을 종료하면 저장된 정보가 사라지는 휘발성이다.
원래라면 데이터베이스에 저장하는 방식으로 구성해야 하는데 아직 DB 연동을 하지 않았기 때문에 메모리에 저장하는 방식으로 진행하겠다.
//학생 Controller 클래스
@RestController
@RequestMapping("/student")
public class StudentController {
StudentRepository studentRepository = new StudentMemoryRepository();
//학생id로 학생 정보를 조회
@GetMapping("/findStudent/{id}")
public Student findById(@PathVariable("id") long id){
return studentRepository.findById(id);
}
//학생 정보 저장
@PostMapping("/saveStudent")
public void saveStudent(@RequestBody Student student){
studentRepository.saveStudent(student.getId(), student);
}
//학생 정보 수정
@PutMapping("/updateStudent")
public void updateStuent(@RequestBody Student student){
studentRepository.updateStudent(student.getId(),student);
}
//학생 정보 삭제
@DeleteMapping("/deleteStudent")
public void deleteStudent(@RequestParam long id){
studentRepository.deleteStudent(id);
}
}
Controller는 Repository의 메서드를 호출해서 Map에 정보를 저장하거나 조회하는 방식으로 작동한다.
가장 먼저, 학생 정보를 저장해보자.
@ResponseBody로 학생 정보를 받아 호출된 URL로 POST API가 호출되고 메모리에 저장이 된다.
학생 DTO 필드값에 맞게 JSON 형식으로 정보를 입력하면 된다.
이제 제대로 저장되었는지 GET API를 통해 확인해보자. id로 학생을 구분하기 때문에 리소스를 식별할 수 있는 값으로 id를 입력하고 @PathVairable로 URL에 입력한 값이 매개변수로 들어온다.
정상적으로 저장이 되었다.
학생이 인천에서 제주도로 이사를 가서 주소 정보를 변경할건데 PUT API를 호출해서 정보를 수정해보자.
POST API와 마찬가지로 JSON 형식으로 입력을 하는데 id값은 변경하면 안된다. 저장소인 Map의 key로 id를 사용하고 있기 때문에 id값은 불변해야 한다.
다시 조회해보면
주소가 변경된 것을 볼 수 있다.
마지막으로 학생 정보를 삭제해보겠다.
DELETE API를 호출하여 정보를 삭제할건데, GET API와 동일하게 id값으로 조회를 하고 조회된 정보가 삭제되는 방식이다.
@RequestParam으로 매개변수를 지정했기 때문에 URL 우측에 ?를 사용하여 매개변수와 동일한 이름으로 리소스 식별 값을 입력하였다.
마지막으로 학생 정보를 조회하면
삭제된 것을 알 수 있다.