반응형

들어가며

설명

  • 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

+ Recent posts