171 lines
7.2 KiB
Markdown
171 lines
7.2 KiB
Markdown
아래는 **"스프링부트 웹플럭스 시리즈"**의 **7장: 웹플럭스와 REST API 설계"**에 대한 초안입니다. 6장에서 MariaDB와의 연동을 바탕으로 REST API를 설계하며, 상태 코드와 에러 처리를 포함한 실습을 진행합니다. 코드도 간략히 유지하며 초보자가 따라 하기 쉽게 자연스러운 문체로 작성했습니다.
|
|
|
|
---
|
|
|
|
## 7. 웹플럭스와 REST API 설계
|
|
|
|
6장에서 MariaDB와 리액티브하게 연동하며 데이터를 다룰 준비를 마쳤습니다. 이제 이 데이터를 기반으로 실전에서 사용할 수 있는 REST API를 설계해보겠습니다. 단순히 데이터를 주고받는 데 그치지 않고, 상태 코드와 에러 처리까지 추가해 클라이언트와의 비동기 통신을 완성도 있게 구현해볼게요. 시작합시다!
|
|
|
|
### RESTful 엔드포인트 구현
|
|
|
|
REST API는 자원을 중심으로 설계되며, HTTP 메서드(GET, POST, PUT, DELETE)를 사용해 CRUD 작업을 처리합니다. 6장의 `UserController`를 확장해 RESTful 방식으로 개선해보겠습니다. 기존 코드를 조금 수정하고 기능을 추가합니다.
|
|
|
|
`src/main/java/com/example/demo/UserController.java`를 아래처럼 업데이트하세요:
|
|
|
|
```java
|
|
package com.example.demo;
|
|
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.web.bind.annotation.*;
|
|
import reactor.core.publisher.Flux;
|
|
import reactor.core.publisher.Mono;
|
|
|
|
@RestController
|
|
@RequestMapping("/users")
|
|
public class UserController {
|
|
|
|
private final UserRepository userRepository;
|
|
|
|
public UserController(UserRepository userRepository) {
|
|
this.userRepository = userRepository;
|
|
}
|
|
|
|
@PostMapping
|
|
@ResponseStatus(HttpStatus.CREATED)
|
|
public Mono<User> createUser(@RequestBody User user) {
|
|
return userRepository.save(user);
|
|
}
|
|
|
|
@GetMapping("/{id}")
|
|
public Mono<User> getUserById(@PathVariable Long id) {
|
|
return userRepository.findById(id);
|
|
}
|
|
|
|
@GetMapping
|
|
public Flux<User> getAllUsers() {
|
|
return userRepository.findAll();
|
|
}
|
|
|
|
@PutMapping("/{id}")
|
|
public Mono<User> updateUser(@PathVariable Long id, @RequestBody User user) {
|
|
return userRepository.findById(id)
|
|
.flatMap(existingUser -> {
|
|
existingUser.setName(user.getName());
|
|
existingUser.setEmail(user.getEmail());
|
|
return userRepository.save(existingUser);
|
|
});
|
|
}
|
|
|
|
@DeleteMapping("/{id}")
|
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
public Mono<Void> deleteUser(@PathVariable Long id) {
|
|
return userRepository.deleteById(id);
|
|
}
|
|
}
|
|
```
|
|
|
|
- `POST /users`: 새 사용자 생성 (201 Created 상태 코드 반환).
|
|
- `GET /users/{id}`: ID로 사용자 조회.
|
|
- `GET /users`: 모든 사용자 목록 조회.
|
|
- `PUT /users/{id}`: 기존 사용자 정보 업데이트.
|
|
- `DELETE /users/{id}`: 사용자 삭제 (204 No Content 반환).
|
|
|
|
이제 API가 RESTful 원칙을 따르며, 각 엔드포인트가 명확한 역할을 수행합니다.
|
|
|
|
### 상태 코드와 에러 처리
|
|
|
|
REST API에서 상태 코드는 클라이언트에게 작업 결과를 알려주는 중요한 신호입니다. 하지만 데이터가 없거나 오류가 발생할 때도 적절히 대응해야죠. 예를 들어, 존재하지 않는 사용자를 조회하려 하면 404 Not Found를 반환하도록 해보겠습니다.
|
|
|
|
`UserController`에 에러 처리를 추가합니다:
|
|
|
|
```java
|
|
@GetMapping("/{id}")
|
|
public Mono<User> getUserById(@PathVariable Long id) {
|
|
return userRepository.findById(id)
|
|
.switchIfEmpty(Mono.error(new UserNotFoundException("User with ID " + id + " not found")));
|
|
}
|
|
|
|
@ExceptionHandler(UserNotFoundException.class)
|
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
|
public Mono<String> handleUserNotFound(UserNotFoundException ex) {
|
|
return Mono.just(ex.getMessage());
|
|
}
|
|
```
|
|
|
|
그리고 `UserNotFoundException` 클래스를 새로 만듭니다:
|
|
|
|
```java
|
|
package com.example.demo;
|
|
|
|
public class UserNotFoundException extends RuntimeException {
|
|
public UserNotFoundException(String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
```
|
|
|
|
- `switchIfEmpty`: 데이터가 없으면 예외를 발생시킵니다.
|
|
- `@ExceptionHandler`: 예외를 캐치해 404 상태 코드와 메시지를 반환.
|
|
|
|
이제 `http://localhost:8080/users/999` (존재하지 않는 ID)로 접속하면 "User with ID 999 not found" 메시지와 404 상태 코드가 반환됩니다.
|
|
|
|
삭제 시에도 비슷하게 처리할 수 있습니다:
|
|
|
|
```java
|
|
@DeleteMapping("/{id}")
|
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
public Mono<Void> deleteUser(@PathVariable Long id) {
|
|
return userRepository.findById(id)
|
|
.switchIfEmpty(Mono.error(new UserNotFoundException("User with ID " + id + " not found")))
|
|
.flatMap(user -> userRepository.deleteById(id));
|
|
}
|
|
```
|
|
|
|
존재하지 않는 ID로 삭제를 시도하면 404가 반환됩니다.
|
|
|
|
### 클라이언트와의 비동기 통신
|
|
|
|
웹플럭스의 REST API는 비동기적으로 동작하므로, 클라이언트도 이를 활용할 수 있어야 합니다. 예를 들어, Postman으로 테스트할 때 응답이 바로 오지 않고 스트림 형태로 전달될 수 있습니다. 특히 `Flux`를 반환하는 `/users` 엔드포인트는 여러 데이터를 순차적으로 보냅니다.
|
|
|
|
클라이언트에서 비동기 요청을 테스트하려면 curl로 해보세요:
|
|
|
|
```bash
|
|
curl -v http://localhost:8080/users
|
|
```
|
|
|
|
데이터가 많다면 스트림처럼 순차적으로 출력되는 걸 볼 수 있습니다. 실제 환경에서는 JavaScript(예: Fetch API나 Axios) 같은 클라이언트 라이브러리로 비동기 호출을 처리할 수 있습니다.
|
|
|
|
간단한 JavaScript 예제:
|
|
|
|
```javascript
|
|
fetch('http://localhost:8080/users')
|
|
.then(response => response.json())
|
|
.then(data => console.log(data));
|
|
```
|
|
|
|
`Flux`의 경우 Server-Sent Events(SSE)를 활용하면 실시간 스트리밍도 가능합니다. 이를 위해 컨트롤러에 SSE 지원을 추가할 수 있습니다:
|
|
|
|
```java
|
|
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
|
public Flux<User> streamUsers() {
|
|
return userRepository.findAll().delayElements(Duration.ofSeconds(1));
|
|
}
|
|
```
|
|
|
|
`http://localhost:8080/users/stream`에 접속하면 1초 간격으로 데이터가 스트리밍됩니다.
|
|
|
|
### 테스트해보기
|
|
|
|
- **생성**: `POST /users`에 `{"name": "Bob", "email": "bob@example.com"}` 전송 → 201 확인.
|
|
- **조회**: `GET /users/1` → Bob의 데이터 확인.
|
|
- **업데이트**: `PUT /users/1`에 `{"name": "Bobby", "email": "bobby@example.com"}` → 수정된 데이터 확인.
|
|
- **삭제**: `DELETE /users/1` → 204 확인.
|
|
- **에러**: `GET /users/999` → 404 확인.
|
|
|
|
### 마무리
|
|
|
|
이제 웹플럭스로 완전한 REST API를 설계해봤습니다. 상태 코드와 에러 처리를 추가하며 클라이언트와의 비동기 통신도 자연스럽게 연결했죠. 다음 장에서는 이 API를 테스트하는 방법을 다루며, 웹플럭스 애플리케이션의 안정성을 높이는 법을 배워보겠습니다. REST API 설계의 맛을 느끼셨길 바랍니다!
|
|
|
|
---
|
|
|
|
이 장은 REST API의 기본 설계와 실무적인 에러 처리를 다루며, 6장의 데이터베이스 연동을 활용했습니다. 추가 기능이나 조정이 필요하면 말씀해주세요! |