Reactive Spring WebFlux R2DBC H2 for Todo Rest API

Reactive Spring WebFlux R2DBC H2 for Todo Rest API

Hi everyone, I'll continue writing samples of  Todo Rest APIs, on various technology stacks.

Today we are going to see how to make our simple CRUD application more reactive. Nowadays the topic is more like a buzzword, like microservices.

We all know that there is a reactive manifesto, which define the main concepts of reactive: Responsive, Resilient, Elastic, Message-Driven.

Also, you could heard a word reactive: reactive systems, reactive architecture, reactive streams or reactive programming. Be careful do not mix it, see following article and white - paper about those variety provided by Lightbend Co.

Here we going to use following stack for our API:
Spring WebFlux - reactive non blocking framework provides us ability to use Reactive Programming and the approach uses Project Reactor under the hood - Netty web server, with non-blocking threads and an event-loop at core.

Spring Data - one of easiest ways, to get a ready solution for Repository layer in your application

R2DBC - is a reactive implementation of standard jdbc drivers - vendor companies of RDBMS like PostgresSql, H2, MSSQL, already done adoption of reactive concept for their DBs. Earlier, without it you would need to create an execution context and set a number of threads for those contexts.

Prerequisites

Before we begin, we must ensure that the following pre-requisites are installed on the local machine:

Steps to build:

  1. Initialize Spring Boot Web Flux project
  2. Ensure dependency in pom.xml
  3. Create model class Todo
  4. Create Repository interface
  5. Initialize connection factory for "r2dbc-h2"
  6. Define a Rest Controller API
  7. Initialize DB for our application
  8. Verify Rest API of Todo Application through a console calls

Let us start:

  1. To bootstrap our project, let's visit start.spring.io site and generate it
https://start.spring.io/

2. Ensure that in a pom.xml we have following dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
    <scope>runtime</scope>
</dependency>

3. Create a model class Todo like here 👇
By the way you can use a Lombok library to short following code;

@Table
class Todo {
    @Id
    private Long id;
    private String text;
    private boolean completed;

    public Todo(Long id, String text, boolean completed) {
        this.id = id;
        this.text = text;
        this.completed = completed;
    }

    public Todo() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public boolean isCompleted() {
        return completed;
    }

    public void setCompleted(boolean completed) {
        this.completed = completed;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Todo)) return false;

        Todo todo = (Todo) o;

        if (isCompleted() != todo.isCompleted()) return false;
        if (getId() != null ? !getId().equals(todo.getId()) : todo.getId() != null) return false;
        return getText() != null ? getText().equals(todo.getText()) : todo.getText() == null;
    }

    @Override
    public int hashCode() {
        int result = getId() != null ? getId().hashCode() : 0;
        result = 31 * result + (getText() != null ? getText().hashCode() : 0);
        result = 31 * result + (isCompleted() ? 1 : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Todo{" +
                "id=" + id +
                ", text='" + text + '\'' +
                ", completed=" + completed +
                '}';
    }
}

4. Define a reactive repository interface TodoRepository interface extends ReactiveCrudRepository which provides, basic CRUD operations on you model. Spring Boot automatically plugs in an implementation of this interface at runtime.


interface TodoRepository extends ReactiveCrudRepository<Todo, Long> {

}

5. Initialize Connection Factory for H2 database - by default it is not necessary, so I'd like to omit following code in git repository. !You can skip this configuration if using H2 database!

@Configuration
@EnableR2dbcRepositories
class R2DBCConfiguration extends AbstractR2dbcConfiguration {
    @Bean
    public H2ConnectionFactory connectionFactory() {
        return new H2ConnectionFactory(
                H2ConnectionConfiguration.builder()
                        .url("r2dbc:h2:mem:default;DB_CLOSE_DELAY=-1;")
                        .username("sa")
                        .build()
        );
    }
}

6. Defining a Rest Controller to expose APIs to our clients, what is important is that as a response to request we return a Publisher interface as  Flux or Mono.

@RestController
@RequestMapping("/api")
class TodoController {
    private TodoRepository repository;

    @Autowired
    public TodoController(TodoRepository repository) {
        this.repository = repository;
    }

    @GetMapping("/todo")
    Flux<Todo> getAll() {
        return repository.findAll();
    }

    @PostMapping("/todo")
    Mono<Todo> addTodo(@RequestBody Todo todo) {
        return repository.save(todo);
    }

    @PutMapping("/todo")
    Mono<Todo> updateTodo(@RequestBody Todo todo) {
        return repository.save(todo);
    }

    @DeleteMapping("/todo/{id}")
    Mono<Void> deleteById(@PathVariable("id") Long id) {
        return repository.deleteById(id);
    }
}

7. Initialize DB, create a table Todo - without init method, running our application will throw an exception, that you don't have a table Todo.

@SpringBootApplication
public class ReactiveTodoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReactiveTodoApplication.class, args);
    }

    @Bean
    ApplicationRunner init(TodoRepository repository, DatabaseClient client) {
        return args -> {
            client.execute("create table IF NOT EXISTS TODO" +
                    "(id SERIAL PRIMARY KEY, text varchar (255) not null, completed boolean default false);").fetch().first().subscribe();
            client.execute("DELETE FROM TODO;").fetch().first().subscribe();

            Stream<Todo> stream = Stream.of(new Todo(null, "Hi this is my first todo!", false),
                    new Todo(null, "This one I have acomplished!", true),
                    new Todo(null, "And this is secret", false));

            // initialize the database

            repository.saveAll(Flux.fromStream(stream))
                    .then()
                    .subscribe(); // execute

        };
    }

}
We must insert this method in ReactiveTodoApplication.class

8. To verify you can browse following link and type into console following commands to query our API:

//GET
await (await fetch("/api/todo", {
  method: "get",
  headers: {
    'Accept': 'application/json'
   }
})).json()
//POST
await (await fetch("/api/todo", {
  method: "post",
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },

  //make sure to serialize your JSON body
  body: JSON.stringify({
    text: "Todo1",
    completed: false
  })
})).json()

//PUT
await (await fetch("/api/todo", {
  method: "put",
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },

  //make sure to serialize your JSON body
  body: JSON.stringify({
    id: 1, 
    text: "Blablahblah",
    completed: true
  })
})).json()
//DELETE
await (await fetch("/api/todo/1", {
  method: "delete",
  headers: {
    'Accept': 'application/json'
   }
})).json()

Summary:

What have we learned - the basics of reactive programming with Spring and built a simple Restful service of Todo APIs using the Spring WebFlux framework and Spring Data r2dbc that supports reactive web components. The reactive-stack web framework, Spring WebFlux is not a replacement for the Spring MVC module.


P.S. If you'd like to add Swagger documentation and use it we need to add extra dependencies, as following example, add repository tags for snapshots, because swagger which is written in a reactive style, not yet released and current version is snapshot - 3.0.0-SNAPSHOT

To use it add following code into pom.xml

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-spring-webflux</artifactId>
    <version>3.0.0-SNAPSHOT</version>
</dependency>

...

<repositories>
    <repository>
        <id>jcenter-snapshots</id>
        <name>jcenter</name>
        <url>http://oss.jfrog.org/artifactory/oss-snapshot-local/</url>
    </repository>
</repositories>

and Java configuration class

@Configuration
@EnableSwagger2WebFlux
class SwaggerConfiguration implements WebFluxConfigurer {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(true)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder().title("Reactive Todo API Swagger")
                .description("Reactive Todo API Swagger")
                .version("1.0").build();
    }
}

Thanks for reading it's all and especially till this line, appreciate it! Comment it if you like!