Перейти к основному содержимому

Начало работы с GraphQL SPQR и Spring Boot

· 6 мин. чтения

1. Введение

GraphQL — это язык запросов и манипуляций для веб-API. Одной из библиотек, созданных для упрощения работы с GraphQL, является SPQR .

В этом руководстве мы изучим основы GraphQL SPQR и увидим его в действии в простом проекте Spring Boot.

2. Что такое GraphQL SPQR?

GraphQL — это известный язык запросов, созданный Facebook. В основе лежат схемы — файлы, в которых мы определяем пользовательские типы и функции.

При традиционном подходе, если бы мы хотели добавить GraphQL в наш проект, нам пришлось бы выполнить два шага. Во-первых, нам нужно добавить в проект файлы схемы GraphQL. Во-вторых, нам нужно написать соответствующие Java POJO, представляющие каждый тип из схемы. Это означает, что мы будем хранить одну и ту же информацию в двух местах: в файлах схемы и в классах Java. Такой подход подвержен ошибкам и требует больше усилий для поддержки проекта.

GraphQL Schema Publisher & Query Resolver, короче говоря, SPQR, был создан для того, чтобы уменьшить вышеперечисленные проблемы — он просто генерирует схемы GraphQL из аннотированных классов Java.

3. Представляем GraphQL SPQR с Spring Boot

Чтобы увидеть SPQR в действии, мы настроим простой сервис. Мы собираемся использовать Spring Boot GraphQL Starter и GraphQL SPQR.

3.1. Настраивать

Начнем с добавления зависимостей для SPQR и Spring Boot в наш POM:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.leangen.graphql</groupId>
<artifactId>spqr</artifactId>
<version>0.11.2</version>
</dependency>

3.2. Написание класса «Модель книги»

Теперь, когда мы добавили необходимые зависимости, давайте создадим простой класс Book :

public class Book {
private Integer id;
private String author;
private String title;
}

Как мы видим выше, он не содержит аннотаций SPQR. Это может быть очень полезно, если у нас нет исходного кода, но мы хотели бы извлечь выгоду из этой библиотеки.

3.3. Написание BookService

Для управления коллекцией книг создадим интерфейс IBookService :

public interface IBookService {
Book getBookWithTitle(String title);

List<Book> getAllBooks();

Book addBook(Book book);

Book updateBook(Book book);

boolean deleteBook(Book book);
}

Затем мы предоставим реализацию нашего интерфейса:

@Service
public class BookService implements IBookService {

Set<Book> books = new HashSet<>();

public Book getBookWithTitle(String title) {
return books.stream()
.filter(book -> book.getTitle()
.equals(title))
.findFirst()
.orElse(null);
}

public List<Book> getAllBooks() {
return books.stream()
.collect(Collectors.toList());
}

public Book addBook(Book book) {
books.add(book);
return book;
}

public Book updateBook(Book book) {
books.remove(book);
books.add(book);
return book;
}

public boolean deleteBook(Book book) {
return books.remove(book);
}
}

3.4. Предоставление службы с помощью graphql-spqr

Осталось только создать преобразователь, который будет выявлять мутации и запросы GraphQL. Для этого мы будем использовать две важные аннотации SPQR — @GraphQLMutation и @GraphQLQuery :

@Service
public class BookResolver {

@Autowired
IBookService bookService;

@GraphQLQuery(name = "getBookWithTitle")
public Book getBookWithTitle(@GraphQLArgument(name = "title") String title) {
return bookService.getBookWithTitle(title);
}

@GraphQLQuery(name = "getAllBooks", description = "Get all books")
public List<Book> getAllBooks() {
return bookService.getAllBooks();
}

@GraphQLMutation(name = "addBook")
public Book addBook(@GraphQLArgument(name = "newBook") Book book) {
return bookService.addBook(book);
}

@GraphQLMutation(name = "updateBook")
public Book updateBook(@GraphQLArgument(name = "modifiedBook") Book book) {
return bookService.updateBook(book);
}

@GraphQLMutation(name = "deleteBook")
public void deleteBook(@GraphQLArgument(name = "book") Book book) {
bookService.deleteBook(book);
}
}

Если мы не хотим писать @GraphQLArgument в каждом методе и довольны тем, что параметры GraphQL названы входными параметрами, мы можем скомпилировать код с аргументом -parameters .

3.5. Контроллер отдыха

Наконец, мы определим Spring @RestController. Чтобы предоставить сервис с помощью SPQR, мы настроим объекты GraphQLSchema и GraphQL :

@RestController
public class GraphqlController {

private final GraphQL graphQL;

@Autowired
public GraphqlController(BookResolver bookResolver) {
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withBasePackages("com.foreach")
.withOperationsFromSingleton(bookResolver)
.generate();
this.graphQL = new GraphQL.Builder(schema)
.build();
}

Важно отметить, что мы должны зарегистрировать наш BookResolver как синглтон .

Последняя задача в нашем путешествии с SPQR — создание конечной точки /graphql . Он будет служить единой точкой контакта с нашим сервисом и будет выполнять запрошенные запросы и мутации:

@PostMapping(value = "/graphql")
public Map<String, Object> execute(@RequestBody Map<String, String> request, HttpServletRequest raw)
throws GraphQLException {
ExecutionResult result = graphQL.execute(request.get("query"));
return result.getData();
}
}

3.6. Результат

Мы можем проверить результаты, проверив конечную точку /graphql . Например, давайте получим все записи книги , выполнив следующую команду cURL:

curl -g \
-X POST \
-H "Content-Type: application/json" \
-d '{"query":"{getAllBooks {id author title }}"}' \
http://localhost:8080/graphql

3.7. Тест

Как только мы закончим настройку, мы можем протестировать наш проект. Мы будем использовать MockMvc для тестирования нашей новой конечной точки и проверки ответов. Давайте определим тест JUnit и автоматически подключим необходимые службы:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class GraphqlControllerIntegrationTest {

@Autowired
private MockMvc mockMvc;

@Autowired
BookService bookService;

private static final String GRAPHQL_PATH = "/graphql";

@Test
public void givenNoBooks_whenReadAll_thenStatusIsOk() throws Exception {

String getAllBooksQuery = "{ getAllBooks {id author title } }";

this.mockMvc.perform(post(GRAPHQL_PATH).content(toJSON(getAllBooksQuery))
.contentType(
MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.getAllBooks").isEmpty());
}

@Test
public void whenAddBook_thenStatusIsOk() throws Exception {

String addBookMutation = "mutation { addBook(newBook: {id: 123, author: \"J.R.R. Tolkien\", "
+ "title: \"The Lord of the Rings\"}) { id author title } }";

this.mockMvc.perform(post(GRAPHQL_PATH).content(toJSON(addBookMutation))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.addBook.id").value("123"))
.andExpect(jsonPath("$.addBook.author").value("J.R.R. Tolkien"))
.andExpect(jsonPath("$.addBook.title").value("The Lord of the Rings"));
}

private String toJSON(String query) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("query", query);
return jsonObject.toString();
}
}

4. Использование GraphQL SPQR Spring Boot Starter

Команда, работающая над SPQR, создала стартер Spring Boot, который еще больше упрощает его использование. Давайте проверим это!

4.1. Настраивать

Мы начнем с добавления spqr-spring-boot-starter в наш POM:

<dependency>
<groupId>io.leangen.graphql</groupId>
<artifactId>graphql-spqr-spring-boot-starter</artifactId>
<version>0.0.6</version>
</dependency>

4.2. КнигаСервис

Затем нам нужно добавить две модификации в наш BookService . Прежде всего, он должен быть аннотирован аннотацией @GraphQLApi . Кроме того, каждый метод, который мы хотим представить в нашем API, должен иметь соответствующую аннотацию:

@Service
@GraphQLApi
public class BookService implements IBookService {

Set<Book> books = new HashSet<>();

@GraphQLQuery(name = "getBookWithTitle")
public Book getBookWithTitle(@GraphQLArgument(name = "title") String title) {
return books.stream()
.filter(book -> book.getTitle()
.equals(title))
.findFirst()
.orElse(null);
}

@GraphQLQuery(name = "getAllBooks", description = "Get all books")
public List<com.foreach.sprq.Book> getAllBooks() {
return books.stream()
.toList();
}

@GraphQLMutation(name = "addBook")
public Book addBook(@GraphQLArgument(name = "newBook") Book book) {
books.add(book);
return book;
}

@GraphQLMutation(name = "updateBook")
public Book updateBook(@GraphQLArgument(name = "modifiedBook") Book book) {
books.remove(book);
books.add(book);
return book;
}

@GraphQLMutation(name = "deleteBook")
public boolean deleteBook(@GraphQLArgument(name = "book") Book book) {
return books.remove(book);
}
}

Как мы видим, мы фактически переместили код из BookResolver в BookService . Кроме того, нам не нужен класс GraphqlController — конечная точка /graphql будет добавлена автоматически .

5. Резюме

GraphQL — это захватывающий фреймворк и альтернатива традиционным конечным точкам RESTful. Предлагая большую гибкость, он также может добавлять некоторые утомительные задачи, такие как обслуживание файлов схемы. SPQR стремится сделать работу с GraphQL проще и менее подверженной ошибкам.

В этой статье мы увидели, как добавить SPQR к существующим POJO и настроить его для обслуживания запросов и мутаций. Затем мы увидели новую конечную точку в действии в GraphiQL. Наконец, мы протестировали наш код, используя Spring MockMvc и JUnit.

Как всегда, пример кода, использованный здесь, доступен на GitHub . Кроме того, код стартового комплекта GraphQL Spring Boot доступен на GitHub .