아래는 **"스프링부트 웹플럭스 시리즈"**의 **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장의 데이터베이스 연동을 활용했습니다. 추가 기능이나 조정이 필요하면 말씀해주세요!