1. 序章

GraphQL は、WebAPIのクエリおよび操作言語です。 GraphQLの操作をよりシームレスにするために生まれたライブラリの1つは、SPQRです。

このチュートリアルでは、GraphQL SPQRの基本を学び、単純なSpring Bootプロジェクトで実際に動作することを確認します。

2. GraphQL SPQRとは何ですか?

GraphQLは、Facebookによって作成された有名なクエリ言語です。 その中核となるのはスキーマです。これは、カスタムタイプと関数を定義するファイルです。

従来のアプローチでは、プロジェクトにGraphQLを追加する場合は、2つの手順を実行する必要があります。 まず、GraphQLスキーマファイルをプロジェクトに追加する必要があります。 次に、スキーマの各タイプを表すそれぞれのJavaPOJOを作成する必要があります。 これは、スキーマファイルとJavaクラスの2か所で同じ情報を維持することを意味します。このようなアプローチはエラーが発生しやすく、プロジェクトの維持にさらに労力が必要です。

GraphQL Schema Publisher&Query Resolver、 要するに、SPQRは、上記の問題を減らすために作成されました –注釈付きのJavaクラスからGraphQLスキーマを生成するだけです。

3. SpringBootを使用したGraphQLSPQRの紹介

SPQRの動作を確認するために、簡単なサービスを設定します。 Spring Boot GraphQLStarterとGraphQLSPQRを使用します。

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クラス

必要な依存関係を追加したので、簡単な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のミューテーションとクエリを公開するリゾルバーを作成することだけです。 これを行うには、2つの重要な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.baeldung")
          .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コマンドを実行して、すべてのBookレコードを取得してみましょう。

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 SpringBootStarterの使用

SPQRに取り組んでいるチームは、Spring Bootスターターを作成しました。これにより、SPQRの使用がさらに簡単になります。 それをチェックしよう!

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

次に、BookServiceに2つの変更を追加する必要があります。 まず、@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.baeldung.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の操作をより簡単にし、エラーが発生しにくいようにすることを目指しています。

この記事では、既存のPOJOにSPQRを追加し、クエリとミューテーションを提供するように構成する方法を説明しました。 次に、GraphiQLで新しいエンドポイントが動作しているのを確認しました。 最後に、SpringのMockMvcとJUnitを使用してコードをテストしました。

いつものように、ここで使用されているサンプルコードは、GitHubから入手できます。 さらに、GraphQL Spring Bootスターターキットのコードは、GitHubからで入手できます。