RESTful WebService (2) - With Spring Boot

박정민·2021년 3월 13일
0

RESTful

목록 보기
2/4

1. SpringBoot로 개발하는 RESTfulService

HelloWorld Controller 추가

<HelloController class>

@RestController
public class HelloWorldController {
    //GET
    // /hello-world (endpoint)
    @GetMapping("/hello-world")
    public String helloWorld(){
        return "Hello Wolrd";
    }
}
  • @RestController
    • @Controller + @ResponseBody
    • View를 갖지 않는 REST Data(JSON/XML)를 반환
      • @ReponseBody 어노테이션을 붙이지 않아도 문자열과 JSON등을 전송할 수 있다.



HelloWorld Bean 추가

<HelloWorldBean class>

@Data
@AllArgsConstructor
@NoArgsConstructor
public class HelloWorldBean {
    private String message;
}
  • @Data
    • Lombok 라이브러리에서 제공
    • 다음 어노테이션들을 모두 한번에 처리
      • @ToString
      • @EqualsAndHashCode
      • @Getter, @Setter
      • @RequiredArgsConstructor

  • 참고
    • 생성자 자동 생성(Lombok 라이브러리에서 제공)
    • @AllArgsConstructor
      • 모든 필드 값을 파라미터로 받는 생성자 생성
    • @NoArgsConstructor
      • 파라미터가 없는 기본 생성자 생성
    • @RequiredArgsConstructor
      - final, @NoNull인 필드 값만 파라미터로 받는 생성자 생성

<HelloWorldController class>

// HelloWorldController class
@GetMapping("/hello-world-bean")
public HelloWorldBean helloWorldBean(){
    return new HelloWorldBean("Hello World");
} 
  • HelloWorldController에 @RestController 어노테이션이 붙어있기 때문에 ViewResolver가 동작하지 않고 HTTPMessageConverter가 실행되어 객체(json형태)나 String을 @ResponseBody 없이 반환할 수 있다.

DispathcerServlet과 프로젝트 동작의 이해

  • DispathcerServlet
    • 클라이언트의 모든 요청을 한곳으로 받아서 처리
    • 요청에 맞는 Handler로 요청을 전달
    • Handler의 실행결과를 HTTP Response 형태로 만들어서 반환
  • 프로젝트 동작 과정


Path Variable사용

  • HelloWorldController 코드 추가
@GetMapping("/hello-world-bean/path-variable/{name}")
public HelloWorldBean helloWorldBean(@PathVariable String name) {
    return new HelloWorldBean(String.format("Hello World, %s", name));
}
  • Get /hello-world-bean/path-variable/{변수}
    • @PathVariable 어노테이션을 이용해서 {변수}와 동일한 이름을 갖는 파라미터(name) 추가
    • 어떤 변수가 들어와도 helloWorldBean을 호출 할 수 있다.(null, 공백 제외)

2. UserService API 구현

User domain class 추가

  • User Domain 생성
@Data
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Date joinDate;
}
  • User Service 생성
@Service
public class UserDaoService {
    private static List<User> users = new ArrayList<>();

    private static int usersCount = 3;

    static {
        users.add(new User(1, "Kenneth", new Date()));
        users.add(new User(2, "Alice", new Date()));
        users.add(new User(3, "Elena", new Date()));
    }

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

    public User save(User user) {
        if (user.getId() == null) {
            user.setId(++usersCount);
        }

        users.add(user);
        return user;
    }

    public User findOne(int id) {
        for (User user : users) {
            if (user.getId() == id) return user;
        }
        return null;
    }
}

User Service API

  • User Controller 생성
    • User 목록 조회 - GET HTTP Method
    • User(한 개) 조회 - GET HTTP Method
    • User 등록 - Post HTTP Method
@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);
        return user;
    }

    @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(); 
    }
}
  • createUser() : User등록
    • User생성 완료에 대한 Http Status Code를 201 created로 반환
      • ResponseEntity Class
        • ResponseEntity.created()를 통해 Status Code는 HttpStatus.CREATED
        • client의 요청 결과에 맞는 Status Code를 생성하는게 좋은 REST api 설계 방식

    • Response Header Location에 생성된 User URI 지정
      • ServletUriComponentsBuilder Class
        • ServletUriComponentsBuilder.fromCurrentRequest(): 요청이 들어온 위치
        • .path("/{id}").buildAndExpand(savedUser.getId()): 요청이 들어온 url뒤에 생성된 user의 id포함
        • .toUri(): Uri로 변환
        • ResponseEntity.created(location)을 통해 Response Header Location에 custom한 location이 지정됨
        • client에서 생성된 사용자를 알 수 있기 때문에 요청의 수를 줄여 네트워크 트래픽이 감소됨

HTTP Status Code제어를 위한 Exception Handling

  • Get /users/[존재하지 않는 ID]
    • retrieveUser()가 호출, HTTP Status Code는 200 OK
    • 요청 결과에 맞는 응답코드가 아니므로 예외처리를 해줘야함

  • Exception Handling
    • retrieveUser() 수정
    • UserNotFoundException class추가
//UserController class
@GetMapping("/users/{id}")
public User retrieveUser(@PathVariable int id) { 
    User user = service.findOne(id);
     
    // HTTP Status Code 제어 -> Exception Handling
    if(user == null) {
        throw new UserNotFoundException(String.format("ID[%s] not found", id));
    }
    return user;
}

//UserNotFoundException class
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}
  • 수정된 코드로 실행 시 예외 처리가 되지만 Status code는 500이고, 예외 trace가 화면에 표시됨

  • trace와 적절한 응답코드를 반환하기 위해 UserNotFoundException class 수정
// HTTP Status code
// 2xx -> Ok
// 4xx -> Client 오류
// 5xx -> Server 오류
@ResponseStatus(HttpStatus.NOT_FOUND)  // 404 -> 사용자가 요청한 리소스 찾지 못함
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

Spring의 AOP를 이용한 Exception Handling

  • 일반화된 예외 클래스 선언

  • ExceptionResponse 클래스
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
    private Date timestamp;
    private String message;
    private String details;
}

-CustomizeReponseEntityExceptionHandler 클래스: 모든 예외상황을 handling

@RestController
@ControllerAdvice 
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
        ExceptionResponse exceptionResponse =
                new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));

        return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // UserNotFoundException Handler
    @ExceptionHandler(UserNotFoundException.class)
    public final ResponseEntity<Object> handleUserNotFoundException(Exception ex, WebRequest request) {
        ExceptionResponse exceptionResponse =
                new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));

        return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}
  • 모든 Exception은 handleAllExceptions()함수에서 처리

  • UserNotFoundException은 handleUserNotFoundException()에서 처리

  • ExceptionResponse객체를 통해 Exception message생성

profile
화이팅!

0개의 댓글