Reactive programming with Spring Boot 2. Part 2


    In the first part, we learned what reactivity is and how to work with it at a basic level. If you want to continue exploring reactive programming with the new Spring framework, then welcome!

    Now we will analyze what to do with the knowledge gained from the previous article, and also build a small web service that will work in asynchronous mode.

    For this we need:

    • MongoDB database . The fact is that not all DBMSs support asynchronous operation. MongoDB in this sense is pleasantly different and fully supports the functionality we need.
    • Driver for asynchronous operation with the database . A regular driver does not know how.
    • Spring Boot 2 . Spring Boot is currently under development, but should be released December 18th . It will greatly facilitate our work and save a lot of time.
    • Gradle. Will be used as a build system.
    • Java 8 . Yes, this is not the latest version at the moment, but for our purposes it is quite suitable.
    • Lombok . Helps reduce code.

    All settings and the final project can be viewed on github .

    Begin

    If you have not connected all the dependencies, you can do it right now. In gradle they will look like this:

    	compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
    	compile('org.springframework.boot:spring-boot-starter-webflux')
    	compileOnly('org.projectlombok:lombok')
    	testCompile('org.springframework.boot:spring-boot-starter-test')
    	testCompile(‘io.projectreactor:reactor-test')


    So, we are all set. To begin, create a user of our application:
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Document
    public class User {
        @Id
        private String id;
        private String firstName;
        private String lastName;
    }

    I remind you that for successful compilation and work in IntelliJ Idea you need to check the enable annotation processing box or write all the setters, getters and constructors yourself.

    Next, create a repository with our users:

    import org.faoxis.habrreactivemongo.domain.User;
    import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
    public interface UserRepository extends ReactiveMongoRepository {
    }

    It should be noted here that we inherit from a special interface for working in reactive mode. If you look at the ReactiveMongoRepository interface , you can see that we are returned with objects wrapped in the Mono and Flux classes that we already know . This means that with any appeal to the database, we do not immediately get the result. Instead, we get a stream of data from which data can be obtained as soon as ready.

    At the moment, layered architecture is the most common solution when working with microservice architecture. It is really very convenient. Let's create a service layer. To do this, we will make the appropriate interface:

    public interface UserService {
        Flux get();
        Mono save(User user);
    }
    

    Our service is quite simple, so we immediately create several useful methods. And then we realize them:

    @Service
    @AllArgsConstructor
    public class UserServiceImpl implements UserService {
        private UserRepository userRepository;
        @Override
        public Flux get() {
            return userRepository.findAll();
        }
        @Override
        public Mono save(User user) {
            return userRepository.save(user);
        }
    }

    It is worth noting here that the implementation of the UserRepository dependency occurs through the constructor using the AllArgsConstructor annotation. Just in case, let me remind you that with some version of Spring 4 you can automatically implement dependencies through the constructor without the Autowire annotation .

    And finally, make the controller:

    import lombok.AllArgsConstructor;
    import org.faoxis.habrreactivemongo.domain.User;
    import org.faoxis.habrreactivemongo.service.UserService;
    import org.springframework.web.bind.annotation.*;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    @RestController
    @RequestMapping("/users")
    @AllArgsConstructor
    public class UserController {
        private UserService userService;
        @PostMapping
        public Mono post(@RequestBody User user) {
            return userService.save(user);
        }
        @GetMapping
        public Flux get() {
            return userService.get();
        }
    }

    Launch our application. Everything should work. Now we will make a POST request to localhost : 8080 / users with the following contents:

    {
    	"firstName": "Peter",
    	"lastName": "Griffin"
    }

    In response, we get the same object, but with the id assigned to it:
    {
        "id": "5a0bf0fdc48fd53478638c9e",
        "firstName": "Peter",
        "lastName": "Griffin"
    }

    Excellent! Let's save a couple more users and try to see what we already have in the database. I have this result of a GET request to localhost : 8080 / users:

    [
        {
            "id": "5a0bf0fdc48fd53478638c9e",
            "firstName": "Peter",
            "lastName": "Griffin"
        },
        {
            "id": "5a0bf192c48fd53478638c9f",
            "firstName": "Lois",
            "lastName": "Griffin"
        },
        {
            "id": "5a0bf19ac48fd53478638ca0",
            "firstName": "Mag",
            "lastName": "Griffin"
        }
    ]

    Excellent! We have a whole service that works in asynchronous mode! But there is one more thing to keep in mind. Working with asynchronous repositories can greatly change the type of service that receives data from it.

    To demonstrate this, create another method URL handler in our controller:

        @GetMapping("{lastName}")
        public Flux getByLastName(@PathVariable(name = "lastName") String lastName) {
            return userService.getByLastName(lastName);
        }

    Everything is simple here. We get the user's last name and display all people with that last name.

    Of course, this logic can be assigned to the database, but here I would like to pay attention to how it will look in the service:

        @Override
        public Flux getByLastName(final String lastName) {
            return userRepository
                    .findAll()
                    .filter(user -> user.getLastName().equals(lastName));
        }

    Please note that there is a difference in working with data and data streams. Usually we carry out some actions on the data directly. Here the situation is different. We say what should be done in the data stream.

    Let's try to make a GET request at the localhost URL : 8080 / users / Griffin. I have this result:

    [
        {
            "id": "5a0bf0fdc48fd53478638c9e",
            "firstName": "Peter",
            "lastName": "Griffin"
        },
        {
            "id": "5a0bf192c48fd53478638c9f",
            "firstName": "Lois",
            "lastName": "Griffin"
        },
        {
            "id": "5a0bf19ac48fd53478638ca0",
            "firstName": "Mag",
            "lastName": "Griffin"
        }
    ]

    In this article, we looked at how to build an asynchronous service with the new WebFlux framework and the Netty server (it comes out of the box by default). We also saw how easy it is to achieve this with Spring Boot 2 . If you have a microservice architecture on your project, then most likely you can easily transfer your applications to WebFlux with the release of Spring Boot 2 if you wish (unless, of course, there is a need for this).

    PS There are several ideas to continue. If you are interested in the material, and you are not against my presentation, then in the following parts we can consider, for example, asynchronous interaction between applications. Thanks for attention!

    Also popular now: