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 .