SERVER SIDE/JAVA
WebFlux를 이용한 RESTful API 샘플
하리하라
2025. 5. 14. 14:54
/**
* WebFlux를 이용한 RESTful API 샘플
*
* 필요한 의존성:
* - spring-boot-starter-webflux
* - spring-boot-starter-data-mongodb-reactive (MongoDB 사용)
* - lombok
*/
// 1. 모델 클래스
package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "products")
public class Product {
@Id
private String id;
private String name;
private String description;
private Double price;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
// 2. 리포지토리 인터페이스
package com.example.demo.repository;
import com.example.demo.model.Product;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductRepository extends ReactiveMongoRepository<Product, String> {
// ReactiveMongoRepository는 기본적인 CRUD 연산을 제공합니다.
// 필요한 추가 쿼리 메서드는 여기에 정의할 수 있습니다.
}
// 3. 서비스 클래스
package com.example.demo.service;
import com.example.demo.model.Product;
import com.example.demo.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
@Service
public class ProductService {
private final ProductRepository productRepository;
@Autowired
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// 모든 상품 조회
public Flux<Product> getAllProducts() {
return productRepository.findAll();
}
// ID로 상품 조회
public Mono<Product> getProductById(String id) {
return productRepository.findById(id);
}
// 상품 생성
public Mono<Product> createProduct(Product product) {
product.setCreatedAt(LocalDateTime.now());
product.setUpdatedAt(LocalDateTime.now());
return productRepository.save(product);
}
// 상품 수정
public Mono<Product> updateProduct(String id, Product product) {
return productRepository.findById(id)
.flatMap(existingProduct -> {
existingProduct.setName(product.getName());
existingProduct.setDescription(product.getDescription());
existingProduct.setPrice(product.getPrice());
existingProduct.setUpdatedAt(LocalDateTime.now());
return productRepository.save(existingProduct);
});
}
// 상품 삭제
public Mono<Void> deleteProduct(String id) {
return productRepository.deleteById(id);
}
}
// 4. 컨트롤러 클래스
package com.example.demo.controller;
import com.example.demo.model.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
// 모든 상품 조회 (GET)
@GetMapping
public Flux<Product> getAllProducts() {
return productService.getAllProducts();
}
// ID로 상품 조회 (GET)
@GetMapping("/{id}")
public Mono<ResponseEntity<Product>> getProductById(@PathVariable String id) {
return productService.getProductById(id)
.map(product -> ResponseEntity.ok(product))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
// 상품 생성 (POST)
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<Product> createProduct(@RequestBody Product product) {
return productService.createProduct(product);
}
// 상품 수정 (PUT)
@PutMapping("/{id}")
public Mono<ResponseEntity<Product>> updateProduct(@PathVariable String id, @RequestBody Product product) {
return productService.updateProduct(id, product)
.map(updatedProduct -> ResponseEntity.ok(updatedProduct))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
// 상품 삭제 (DELETE)
@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deleteProduct(@PathVariable String id) {
return productService.deleteProduct(id)
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.NO_CONTENT)))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
}
// 5. 애플리케이션 메인 클래스
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebfluxRestfulApiApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxRestfulApiApplication.class, args);
}
}
// 6. 애플리케이션 설정 파일 (src/main/resources/application.properties)
// application.properties
server.port=8080
spring.data.mongodb.uri=mongodb://localhost:27017/productdb
// 테스트를 위한 WebClient 샘플
package com.example.demo.client;
import com.example.demo.model.Product;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class ProductWebClient {
private final WebClient webClient;
private final String baseUrl = "http://localhost:8080/api/products";
public ProductWebClient() {
this.webClient = WebClient.create();
}
// 모든 상품 조회
public Flux<Product> getAllProducts() {
return webClient.get()
.uri(baseUrl)
.retrieve()
.bodyToFlux(Product.class);
}
// ID로 상품 조회
public Mono<Product> getProductById(String id) {
return webClient.get()
.uri(baseUrl + "/{id}", id)
.retrieve()
.bodyToMono(Product.class);
}
// 상품 생성
public Mono<Product> createProduct(Product product) {
return webClient.post()
.uri(baseUrl)
.bodyValue(product)
.retrieve()
.bodyToMono(Product.class);
}
// 상품 수정
public Mono<Product> updateProduct(String id, Product product) {
return webClient.put()
.uri(baseUrl + "/{id}", id)
.bodyValue(product)
.retrieve()
.bodyToMono(Product.class);
}
// 상품 삭제
public Mono<Void> deleteProduct(String id) {
return webClient.delete()
.uri(baseUrl + "/{id}", id)
.retrieve()
.bodyToMono(Void.class);
}
}
WebFlux를 이용한 RESTful API 샘플. WebFlux는 Spring Framework의 반응형(reactive) 웹 프레임워크로, 비동기 논블로킹 방식으로 작동.
이 샘플 코드는 Spring WebFlux를 사용하여 간단한 제품(Product) 관리 API를 구현. 코드에 다음과 같은 주요 구성요소가 포함.
주요 구성요소
- 모델 클래스 (Product)
- MongoDB에 저장될 제품 정보를 표현하는 엔티티 클래스
- ID, 이름, 설명, 가격, 생성/수정 시간을 포함
- 리포지토리 인터페이스 (ProductRepository)
- ReactiveMongoRepository를 확장하여 반응형 데이터 액세스 제공
- 기본 CRUD 작업을 위한 메서드 자동 생성
- 서비스 클래스 (ProductService)
- 비즈니스 로직 처리
- Mono와 Flux를 반환하는 반응형 메서드 구현
- 컨트롤러 클래스 (ProductController)
- RESTful API 엔드포인트 정의
- WebFlux를 사용한 비동기 요청 처리
- WebClient 예제
- API 호출을 위한 반응형 클라이언트 구현
API 엔드포인트
- GET /api/products: 모든 제품 조회
- GET /api/products/{id}: ID로 특정 제품 조회
- POST /api/products: 새 제품 생성
- PUT /api/products/{id}: 기존 제품 수정
- DELETE /api/products/{id}: 제품 삭제
이 샘플의 특징
- 비동기 논블로킹 방식으로 구현되어 높은 동시성 처리가 가능
- Mono와 Flux 타입을 사용하여 반응형 프로그래밍 구현
- 함수형 스타일의 코드로 작성되어 가독성과 유지보수성 향상
- MongoDB와 같은 반응형 데이터베이스와 연동 가능
이 코드를 실행하려면 MongoDB가 설치되어 있거나 애플리케이션 속성에서 MongoDB URI를 적절히 수정해야 한다. 코드의 의존성으로는 spring-boot-starter-webflux, spring-boot-starter-data-mongodb-reactive, lombok이 필요.