Spring Boot Reactive is a framework that makes it easy to build reactive applications using Spring Boot. It provides a number of features that make it easier to develop, test, and deploy reactive applications, including:
- Reactive web support: Spring Boot Reactive provides support for building reactive web applications using Spring WebFlux.
- Reactive messaging support: Spring Boot Reactive provides support for building reactive messaging applications using Spring Cloud Stream.
- Reactive testing support: Spring Boot Reactive provides support for testing reactive applications using Spring Boot Test.
- Reactive deployment support: Spring Boot Reactive provides support for deploying reactive applications to a variety of environments, including cloud environments.
- Flux and Mono are two reactive types that are used in Spring Boot Reactive applications. They are both implementations of the Publisher interface, which means that they can emit a sequence of elements to subscribers. The main difference between Flux and Mono is that Flux can emit zero or more elements, while Mono can only emit zero or one element. This means that Flux is more general-purpose than Mono, but it can also be more complex to use.
GraphQL is an open-source query language and runtime for APIs (Application Programming Interfaces). It was developed by Facebook and released in 2015. GraphQL provides a flexible and efficient way to define, query, and manipulate data in APIs.
Technology
- Spring Boot 3.1.1
- GraphQL
- MongoDB
- Docker
- Maven
- IntelliJ IDEA
Configuration Spring Boot project :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>spring-data-mongodb-graphql-reactive</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-reactive-mongodb</name>
<description>Demo project for Spring Boot with MongoDB</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/de.flapdoodle.embed/de.flapdoodle.embed.mongo
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.7.0</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-java-tools -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>spring-data-mongodb-graphql-reactive</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-reactive-mongodb</name>
<description>Demo project for Spring Boot with MongoDB</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/de.flapdoodle.embed/de.flapdoodle.embed.mongo
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
<version>4.7.0</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-java-tools -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml:
# graphql Configuration
graphql:
graphiql:
enabled: true
# mongodb Configuration
spring:
data:
mongodb:
uri: mongodb://test:test@localhost:27017/tech_notes?authSource=admin
graphql:
graphiql:
enabled: true
# mongodb Configuration
spring:
data:
mongodb:
uri: mongodb://test:test@localhost:27017/tech_notes?authSource=admin
Schema
Spring for GraphQL application, create a directory src/main/resources/graphql. Add a new file schema.graphqls to this folder with the following content:
type Query {
getAllCategories: [Category]
getCategoryById(id: ID): Category
}
type Mutation {
createCategory(input: CategoryInput): Category
updateCategory(id: ID, input: CategoryInput): Category
deleteCategory(id: ID): Boolean
deleteAllCategories: Boolean
}
type Category {
id: ID
title: String
posts: [String]
}
input CategoryInput {
id: Int
title: String
posts: [String]
}
getAllCategories: [Category]
getCategoryById(id: ID): Category
}
type Mutation {
createCategory(input: CategoryInput): Category
updateCategory(id: ID, input: CategoryInput): Category
deleteCategory(id: ID): Boolean
deleteAllCategories: Boolean
}
type Category {
id: ID
title: String
posts: [String]
}
input CategoryInput {
id: Int
title: String
posts: [String]
}
Spring Data Reactive GraphQL Application
Config
package com.henry.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins("*")
//.allowedMethods("POST")
.maxAge(3600);
}
}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins("*")
//.allowedMethods("POST")
.maxAge(3600);
}
}
Model
package com.henry.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.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "categories")
public class Category {
@Id
private long id;
private String title;
private List<String> posts;
}
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.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "categories")
public class Category {
@Id
private long id;
private String title;
private List<String> posts;
}
package com.henry.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "cat_sequences")
public class Sequence {
@Id
private String id;
private long value;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "cat_sequences")
public class Sequence {
@Id
private String id;
private long value;
}
Repository
package com.henry.repository;
import com.henry.model.Category;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
public interface CategoryRepository extends ReactiveMongoRepository<Category, Long> {
}
import com.henry.model.Category;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
public interface CategoryRepository extends ReactiveMongoRepository<Category, Long> {
}
package com.henry.repository;
import com.henry.model.Sequence;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface SequenceRepository extends ReactiveCrudRepository<Sequence, String> {
}
import com.henry.model.Sequence;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface SequenceRepository extends ReactiveCrudRepository<Sequence, String> {
}
Service
package com.henry.service;
import com.henry.model.Category;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface CategoryService {
public Flux<Category> getAllCategories();
public Mono<Category> getCategoryById(Long id);
public Mono<Category> createCategory(Category category);
public Mono<Category> updateCategory(Long id, Category updatedCategory);
public Mono<Void> deleteCategory(Long id);
public Mono<Void> deleteAllCategories();
}
import com.henry.model.Category;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface CategoryService {
public Flux<Category> getAllCategories();
public Mono<Category> getCategoryById(Long id);
public Mono<Category> createCategory(Category category);
public Mono<Category> updateCategory(Long id, Category updatedCategory);
public Mono<Void> deleteCategory(Long id);
public Mono<Void> deleteAllCategories();
}
Resolver
package com.henry.resolver;
import com.coxautodev.graphql.tools.GraphQLMutationResolver;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import com.henry.model.Category;
import com.henry.model.Sequence;
import com.henry.repository.CategoryRepository;
import com.henry.repository.SequenceRepository;
import com.henry.service.CategoryService;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Component
public class CategoryResolver implements CategoryService, GraphQLQueryResolver, GraphQLMutationResolver {
private final SequenceRepository sequenceRepository;
private final CategoryRepository categoryRepository;
public CategoryResolver(SequenceRepository sequenceRepository, CategoryRepository categoryRepository) {
this.sequenceRepository = sequenceRepository;
this.categoryRepository = categoryRepository;
}
@Override
public Flux<Category> getAllCategories() {
return categoryRepository.findAll();
}
@Override
public Mono<Category> getCategoryById(Long id) {
return categoryRepository.findById(id);
}
@Override
public Mono<Category> createCategory(Category category) {
return getNextSequenceId().map(id -> {
category.setId(id);
return category;
}).flatMap(categoryRepository::save);
}
@Override
public Mono<Category> updateCategory(Long id, Category updatedCategory) {
return categoryRepository.findById(id)
.flatMap(category -> {
category.setTitle(updatedCategory.getTitle());
category.setPosts(updatedCategory.getPosts());
return categoryRepository.save(category);
});
}
@Override
public Mono<Void> deleteCategory(Long id) {
return categoryRepository.deleteById(id);
}
@Override
public Mono<Void> deleteAllCategories() {
return categoryRepository.deleteAll();
}
private Mono<Long> getNextSequenceId() {
return sequenceRepository.findById("categoryId")
.map(sequence -> {
long nextValue = sequence.getValue() + 1;
sequence.setValue(nextValue);
return sequence;
})
.defaultIfEmpty(new Sequence("categoryId", 1))
.flatMap(sequenceRepository::save)
.map(Sequence::getValue);
}
}
import com.coxautodev.graphql.tools.GraphQLMutationResolver;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import com.henry.model.Category;
import com.henry.model.Sequence;
import com.henry.repository.CategoryRepository;
import com.henry.repository.SequenceRepository;
import com.henry.service.CategoryService;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Component
public class CategoryResolver implements CategoryService, GraphQLQueryResolver, GraphQLMutationResolver {
private final SequenceRepository sequenceRepository;
private final CategoryRepository categoryRepository;
public CategoryResolver(SequenceRepository sequenceRepository, CategoryRepository categoryRepository) {
this.sequenceRepository = sequenceRepository;
this.categoryRepository = categoryRepository;
}
@Override
public Flux<Category> getAllCategories() {
return categoryRepository.findAll();
}
@Override
public Mono<Category> getCategoryById(Long id) {
return categoryRepository.findById(id);
}
@Override
public Mono<Category> createCategory(Category category) {
return getNextSequenceId().map(id -> {
category.setId(id);
return category;
}).flatMap(categoryRepository::save);
}
@Override
public Mono<Category> updateCategory(Long id, Category updatedCategory) {
return categoryRepository.findById(id)
.flatMap(category -> {
category.setTitle(updatedCategory.getTitle());
category.setPosts(updatedCategory.getPosts());
return categoryRepository.save(category);
});
}
@Override
public Mono<Void> deleteCategory(Long id) {
return categoryRepository.deleteById(id);
}
@Override
public Mono<Void> deleteAllCategories() {
return categoryRepository.deleteAll();
}
private Mono<Long> getNextSequenceId() {
return sequenceRepository.findById("categoryId")
.map(sequence -> {
long nextValue = sequence.getValue() + 1;
sequence.setValue(nextValue);
return sequence;
})
.defaultIfEmpty(new Sequence("categoryId", 1))
.flatMap(sequenceRepository::save)
.map(Sequence::getValue);
}
}
Controller
package com.henry.controller;
import com.henry.model.Category;
import com.henry.resolver.CategoryResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Controller
public class CategoryController {
private final CategoryResolver categoryResolver;
@Autowired
public CategoryController(CategoryResolver categoryResolver) {
this.categoryResolver = categoryResolver;
}
@QueryMapping
public Flux<Category> getAllCategories() {
return categoryResolver.getAllCategories();
}
@QueryMapping
public Mono<Category> getCategoryById(@Argument Long id) {
return categoryResolver.getCategoryById(id);
}
@SchemaMapping(typeName = "Mutation", field = "createCategory")
public Mono<Category> createCategory(@Argument(name = "input") Category category) {
return categoryResolver.createCategory(category);
}
@SchemaMapping(typeName = "Mutation", field = "updateCategory")
public Mono<Category> updateCategory(@Argument Long id, @Argument(name = "input") Category updatedCategory) {
return categoryResolver.updateCategory(id, updatedCategory);
}
@SchemaMapping(typeName = "Mutation", field = "deleteCategory")
public Mono<Void> deleteCategory(@Argument Long id) {
return categoryResolver.deleteCategory(id);
}
@SchemaMapping(typeName = "Mutation", field = "deleteAllCategories")
public Mono<Void> deleteAllCategories() {
return categoryResolver.deleteAllCategories();
}
}
import com.henry.model.Category;
import com.henry.resolver.CategoryResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Controller
public class CategoryController {
private final CategoryResolver categoryResolver;
@Autowired
public CategoryController(CategoryResolver categoryResolver) {
this.categoryResolver = categoryResolver;
}
@QueryMapping
public Flux<Category> getAllCategories() {
return categoryResolver.getAllCategories();
}
@QueryMapping
public Mono<Category> getCategoryById(@Argument Long id) {
return categoryResolver.getCategoryById(id);
}
@SchemaMapping(typeName = "Mutation", field = "createCategory")
public Mono<Category> createCategory(@Argument(name = "input") Category category) {
return categoryResolver.createCategory(category);
}
@SchemaMapping(typeName = "Mutation", field = "updateCategory")
public Mono<Category> updateCategory(@Argument Long id, @Argument(name = "input") Category updatedCategory) {
return categoryResolver.updateCategory(id, updatedCategory);
}
@SchemaMapping(typeName = "Mutation", field = "deleteCategory")
public Mono<Void> deleteCategory(@Argument Long id) {
return categoryResolver.deleteCategory(id);
}
@SchemaMapping(typeName = "Mutation", field = "deleteAllCategories")
public Mono<Void> deleteAllCategories() {
return categoryResolver.deleteAllCategories();
}
}
Downloading an installing MongoDB in Ubuntu
Create a docker-compose-mongodb.yml file with the following contents:
version: "2.0"
services:
mongodb_container:
image: mongo:latest
environment:
MONGO_INITDB_ROOT_USERNAME: test
MONGO_INITDB_ROOT_PASSWORD: test
MONGO_INITDB_DATABASE: tech_notes
ports:
- 27017:27017
volumes:
- mongodb_data_container:/data/db
volumes:
mongodb_data_container:
Run the following command to start the MongoDB container:
docker-compose -f docker-compose-mongodb.yml up -d
Run & Test
Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.
Postman
POST
http://localhost:8080/graphql
createCategory query:
updateCategory query:
deleteCategory query:
deleteAllCategories query:
getCategoryById query:
getAllCategories query:
query {
getAllCategories
{
id
title
posts
}
}
No comments:
Post a Comment