REST API

Rest는 get,put,post,delete 등과 같은 동사와 http 헤더, 상태 코드 등 비지니스 상태 변경을 외부에 노출하는 것과 관련된 모든 것을 말한다. 좋은 Rest Api는 Http의 기능을 최대한 활용한다. Rest는 어떤 기술 표준이 아니다, 자원단위의 표시라고 생각되며 Http에 대한 일종의 아키텍처 제약사항이다. 물론 모든 것에 제약사항을 걸며 개발하기 힘들겠지만 최대한 노력해야 한다.

스프링 부트에서 boot-start-web을 추가하면 서블릿 컨테이너와 스프링 mvc의 모들 설정을 가져 온다.(편하다.. 하하)

간단한 RestController 예제를 만들기

@RestController만 붙이면 해당 컨트롤러는 즉각적으로 사용이 가능한다.

SampleController

1
2
3
4
5
6
7
@RestController
class SampleController {
@RequestMapping("/")
String getHello(){
return "Hello World !!"
}
}

너무나도 쉽게 위의 코드면 헬로월드를 얻을 수 있다.

실제 db에 접근하여 CRUD를 구현해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

@RestController
@RequestMapping("/v1/customers")
public class CustomerRestController {

@Autowired
private CustomerRepository customerRepository;

// <1>
@RequestMapping(method = RequestMethod.OPTIONS)
ResponseEntity<?> options() {

//@formatter:off
return ResponseEntity
.ok()
.allow(HttpMethod.GET, HttpMethod.POST,
HttpMethod.HEAD, HttpMethod.OPTIONS,
HttpMethod.PUT, HttpMethod.DELETE)
.build();
//@formatter:on
}

@GetMapping
ResponseEntity<Collection<Customer>> getCollection() {
return ResponseEntity.ok(this.customerRepository.findAll());
}

// <2>
@GetMapping(value = "/{id}")
ResponseEntity<Customer> get(@PathVariable Long id) {
return this.customerRepository.findById(id).map(ResponseEntity::ok)
.orElseThrow(() -> new CustomerNotFoundException(id));
}

@PostMapping
ResponseEntity<Customer> post(@RequestBody Customer c) { // <3>

Customer customer = this.customerRepository.save(new Customer(c
.getFirstName(), c.getLastName()));

URI uri = MvcUriComponentsBuilder.fromController(getClass()).path("/{id}")
.buildAndExpand(customer.getId()).toUri();
return ResponseEntity.created(uri).body(customer);
}

// <4>
@DeleteMapping(value = "/{id}")
ResponseEntity<?> delete(@PathVariable Long id) {
return this.customerRepository.findById(id).map(c -> {
customerRepository.delete(c);
return ResponseEntity.noContent().build();
}).orElseThrow(() -> new CustomerNotFoundException(id));
}

// <5>
@RequestMapping(value = "/{id}", method = RequestMethod.HEAD)
ResponseEntity<?> head(@PathVariable Long id) {
return this.customerRepository.findById(id)
.map(exists -> ResponseEntity.noContent().build())
.orElseThrow(() -> new CustomerNotFoundException(id));
}

// <6>
@PutMapping(value = "/{id}")
ResponseEntity<Customer> put(@PathVariable Long id, @RequestBody Customer c) {
return this.customerRepository
.findById(id)
.map(
existing -> {
Customer customer = this.customerRepository.save(new Customer(existing
.getId(), c.getFirstName(), c.getLastName()));
URI selfLink = URI.create(ServletUriComponentsBuilder.fromCurrentRequest()
.toUriString());
return ResponseEntity.created(selfLink).body(customer);
}).orElseThrow(() -> new CustomerNotFoundException(id));

}
}
  • 요청을 처리하는 핸들러 메소드 -> @RequestMapping
  • GetMapping - method get, 데이터 읽기
  • PostMapping - method post, 데이터 넣기
  • DeleteMapping - method delete, 데이터 삭제(현업에서는 절대 해당 필드삭제를 자제하자, DB에 소프트 델리트가 가능하도록 필드를 설정하는 것을 지향해야한다.)
  • PutMapping - method put - 데이터 갱신

위의코드는 누구든 쉽게 crud를 바로 적용할수 있다.

RestController ControllerAdvice

프로젝트를 진행하며 aop가 활용되어 있는 부분을 볼수 있다. 그중 ControllerAdvice는 스프링 환경에서 default로 쓰인다고 생각할수 있다. 해당 어노테이션이 붙으면 컨트롤러로 request가 들어오기 전에 검증을 할수 있다. 물론 function단위로 exception handler를 달아 하나하나 구현도 가능하지만 공통된 error handling이 가능하다.

  • @ExceoptionHandler 대신 @ControllerAdvice를 사용하자(공통 예외처리)

ControllerAdvice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

@ControllerAdvice(annotations = RestController.class)
public class CustomerControllerAdvice {

// <1>
private final MediaType vndErrorMediaType = MediaType
.parseMediaType("application/vnd.error");

// <2>
@ExceptionHandler(CustomerNotFoundException.class)
ResponseEntity<VndErrors> notFoundException(CustomerNotFoundException e) {
return this.error(e, HttpStatus.NOT_FOUND, e.getCustomerId() + "");
}

@ExceptionHandler(IllegalArgumentException.class)
ResponseEntity<VndErrors> assertionException(IllegalArgumentException ex) {
return this.error(ex, HttpStatus.NOT_FOUND, ex.getLocalizedMessage());
}

// <3>
private <E extends Exception> ResponseEntity<VndErrors> error(E error,
HttpStatus httpStatus, String logref) {
String msg = Optional.of(error.getMessage()).orElse(
error.getClass().getSimpleName());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(this.vndErrorMediaType);
return new ResponseEntity<>(new VndErrors(logref, msg), httpHeaders,
httpStatus);
}
}

<클라우드 네이티브 자바 6장 restapi 참고>