Saturday, July 1, 2023

Spring Data Reactive With MongoDB and GrahpQL

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>


application.yml:


# graphql Configuration
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]
}


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);
}
}


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;
}

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;
}


Repository


package com.henry.repository;

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> {
}


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();
}

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);
}

}



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();
}
}



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:

mutation {
  createCategory(
    input: {
      title"Java"
      posts: [
        "Spring Data Reactive With MongoDB and GrahpQL"
        "Angular 16  Spring Boot 3 Spring Data GraphQL CRUD"
        "Spring Boot 3 Spring Data GraphQL CRUD"
        "Spring Boot 3 Spring Data  Elasticsearch "
      ]
    }
  ) {
    id
    title
    posts
  }
}









updateCategory query:

mutation {
  updateCategory(
    id2
    input: {
      title"Java"
      posts: [
        "Spring Data Reactive With MongoDB and GrahpQL"
        "Spring Security 6 Custom Login, OAuth2 Login with Google and Basic Auth"
        "Spring Boot 3 Spring Data GraphQL CRUD"
        "Spring Boot 3 Spring Data  Elasticsearch "
      ]
    }
  ) {
    id
    title
    posts
  }
}





deleteCategory query:

mutation {
  deleteCategory
    id2
   ) 
}





deleteAllCategories query:

mutation {
  deleteAllCategories
}





getCategoryById query:

query  {
  getCategoryById(id3) {
      title
    posts
  }
}





getAllCategories query:

query {
  getAllCategories
  {
    id
    title
    posts
  }
}









Source Code


Here on GitHub.


















            

No comments:

Post a Comment

Creating REST APIs with OpenAPI, Spring Boot 3.3.3, Java 21, and Jakarta

 Introduction In today's software landscape, designing robust and scalable REST APIs is a crucial aspect of application development. Wit...