반응형
들어가며
설명
- WebFlux는 요청을 받는 부분을 정의하는 방식이 두 가지 있다.(Annotated Controllers, Functional Endpoints)
- Annotated Controllers : 기존 스프링 MVC와 비슷하게 컨트롤러를 정의하는 방법
- Functional Endpoints : 라우팅 룰을 설정하여 정의하는 방법
- Reactive Web + Reactive MongoDB를 활용한 예제로 이 두 가지에 대해 설명한다.
사전 준비 및 공통 코드
docker-compose.yml
- 아래처럼 설정하고 docker-compose를 미리 실행
version: "3.3"
services:
mongodb:
image: mongo:4.4.5
ports:
- 27017:27017
environment:
TZ: Asia/Seoul
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: 123456
Dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
application.properties
spring.data.mongodb.uri=mongodb://root:123456@localhost:27017/dev?authSource=admin
Student
@AllArgsConstructor
@NoArgsConstructor
@With
@Data
@Document("student")
public class Student {
@Id
private String id;
private String name;
private int age;
}
StudentRepository
@Repository
public interface StudentRepository extends ReactiveMongoRepository<Student, String> {
}
StudentService
@RequiredArgsConstructor
@Service
public class StudentService {
private final StudentRepository studentRepository;
public Flux<Student> getStudents() {
return studentRepository.findAll();
}
public Mono<Student> getStudent(String id) {
return studentRepository.findById(id)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
}
public Mono<Student> addStudent(Student student) {
return studentRepository.save(student.withId(null));
}
public Mono<Student> updateStudent(String id, Student student) {
return getStudent(id).flatMap((dbStudent) -> studentRepository.save(student.withId(id)));
}
public Mono<Void> deleteStudent(String id) {
return getStudent(id).flatMap((dbStudent) -> studentRepository.deleteById(id));
}
}
Annotated Controllers
StudentController
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/students")
public class StudentController {
private final StudentService studentService;
@GetMapping
public Flux<Student> getStudents() {
return studentService.getStudents();
}
@GetMapping(value = "/stream", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<Student> getStudentsStream() {
return studentService.getStudents();
}
@GetMapping("/{id}")
public Mono<Student> getStudent(@PathVariable String id) {
return studentService.getStudent(id);
}
@PostMapping
public Mono<Student> addStudent(@RequestBody Student student) {
return studentService.addStudent(student);
}
@PutMapping("/{id}")
public Mono<Student> updateStudent(@PathVariable String id, @RequestBody Student student) {
return studentService.updateStudent(id, student);
}
@DeleteMapping("/{id}")
public Mono<Void> deleteStudent(@PathVariable String id) {
return studentService.deleteStudent(id);
}
}
Functional Endpoints
StudentHandler
@RequiredArgsConstructor
@Component
public class StudentHandler {
private final StudentService studentService;
public Mono<ServerResponse> getStudents(ServerRequest request) {
Flux<Student> students = studentService.getStudents();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(students, Student.class);
}
public Mono<ServerResponse> getStudent(ServerRequest request) {
String id = request.pathVariable("id");
Mono<Student> student = studentService.getStudent(id);
return ServerResponse.ok().contentType(APPLICATION_JSON).body(student, Student.class);
}
public Mono<ServerResponse> addStudent(ServerRequest request) {
return request.bodyToMono(Student.class)
.flatMap(student -> {
Mono<Student> newStudent = studentService.addStudent(student);
return ServerResponse.ok().contentType(APPLICATION_JSON).body(newStudent, Student.class);
});
}
public Mono<ServerResponse> updateStudent(ServerRequest request) {
String id = request.pathVariable("id");
return request.bodyToMono(Student.class)
.flatMap(student -> {
Mono<Student> newStudent = studentService.updateStudent(id, student);
return ServerResponse.ok().contentType(APPLICATION_JSON).body(newStudent, Student.class);
});
}
}
RouteConfig
@Slf4j
@Configuration
public class RouteConfig {
@Bean
public RouterFunction<ServerResponse> routes(StudentHandler studentHandler) {
return route()
.GET("/api/students", accept(APPLICATION_JSON), studentHandler::getStudents)
.GET("/api/students/{id}", accept(APPLICATION_JSON), studentHandler::getStudent)
.POST("/api/students", studentHandler::addStudent)
.PUT("/api/students/{id}", studentHandler::updateStudent)
.build();
}
// @Bean
// public RouterFunction<ServerResponse> routes(StudentHandler studentHandler) {
// return route()
// .path("/api/students", builder -> builder
// .GET("", accept(APPLICATION_JSON), studentHandler::getStudents)
// .GET("/{id}", accept(APPLICATION_JSON), studentHandler::getStudent)
// .POST("", studentHandler::addStudent)
// .PUT("/{id}", studentHandler::updateStudent)
// ).build();
// }
//
// @Bean
// public RouterFunction<ServerResponse> routes(StudentHandler studentHandler) {
// return route()
// .path("/api/students", builder1 -> builder1
// .nest(accept(APPLICATION_JSON), builder2 -> builder2
// .GET("", studentHandler::getStudents)
// .GET("/{id}", studentHandler::getStudent)
// )
// .POST("", studentHandler::addStudent)
// .PUT("/{id}", studentHandler::updateStudent)
// )
// .build();
// }
//
// @Bean
// public RouterFunction<ServerResponse> routes(StudentHandler studentHandler) {
// return route()
// .path("/api/students", builder1 -> builder1
// .nest(accept(APPLICATION_JSON), builder2 -> builder2
// .GET("", studentHandler::getStudents)
// .GET("/{id}", studentHandler::getStudent)
// .before(request -> ServerRequest.from(request)
// .header("X-RequestHeader", "Value")
// .build())
// )
// .POST("", studentHandler::addStudent)
// .PUT("/{id}", studentHandler::updateStudent)
// )
// .after((request, response) -> {
// log.info("response : {}", response);
// return response;
// })
// .build();
// }
//
// @Bean
// public RouterFunction<ServerResponse> routes(StudentHandler studentHandler) {
// return route()
// .path("/api/students", builder1 -> builder1
// .nest(accept(APPLICATION_JSON), builder2 -> builder2
// .GET("", studentHandler::getStudents)
// .GET("/{id}", studentHandler::getStudent)
// )
// .POST("", studentHandler::addStudent)
// .PUT("/{id}", studentHandler::updateStudent)
// )
// .filter((request, next) -> {
// String userId = request.headers().firstHeader("X-USER-ID");
//
// if (userId == null) {
// return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
// } else {
// return next.handle(request);
// }
// })
// .build();
// }
}
반응형
'Development > WebFlux' 카테고리의 다른 글
[WebFlux] Spring Data Reactive (0) | 2021.07.17 |
---|---|
[WebFlux] Stream API (0) | 2021.07.17 |
[WebFlux] WebClient (0) | 2021.07.17 |
[WebFlux] Test (0) | 2021.07.17 |
[WebFlux] Introduction (0) | 2021.07.13 |