MicroProfile GraphQL Support Now Available in Helidon MP

原文はこちら。
The original article was written by Tim Middleton (Solution Architect with Oracle Coherence Development).
https://medium.com/helidon/microprofile-graphql-support-now-available-in-helidon-mp-dbc7bc0b4af

We are pleased to announce that Helidon MP version 2.2.0 がいよいよEclipse FoundationのMicroProfile GraphQL仕様をサポートすることを発表できうれしく思っています。

Microprofile GraphQL Specification
https://github.com/eclipse/microprofile-graphql

仕様には以下のような文言があります。

GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL interprets strings from the client, and returns data in an understandable, predictable, pre-defined manner. GraphQL is an alternative, though not necessarily a replacement for REST.(GraphQLはAPIのためのオープンソースのデータクエリおよびデータ操作言語で、既存のデータを使ってクエリを実行するためのランタイムです。GraphQLはクライアントからの文字列を解釈し、わかりやすく、予測可能で、事前に定義された方法でデータを返します。GraphQLは、RESTの代替品ではありますが、必ずしもRESTを置き換えるものではありません。)

https://github.com/eclipse/microprofile-graphql#about

GraphQLは、クエリ(query)、ミューテーション(mutation)、サブスクリプション(subscription)の3種類のデータ操作を提供します。スキーマは GraphQL の中核となるもので、API がサポートする操作を明確に定義します。

スキーマの例を以下に示します。これは架空のタスクAPIを定義しており、後ほど説明いたします。

type Mutation {
  "Create a task with the given description"
  createTask(description: String): Task
  "Remove all completed tasks and return the tasks left"
  deleteCompletedTasks: [Task]
  "Delete a task and return the deleted task details"
  deleteTask(id: String): Task
  "Update a task"
  updateTask(completed: Boolean, 
             description: String, 
             id: String): Task
}

type Query {
  "Return a given task"
  findTask(id: String): Task
  "Query tasks and optionally specified only completed"
  tasks(completed: Boolean): [Task]
}

type Task {
  completed: Boolean!
  createdAt: BigInteger!
  description: String!
  id: String!
}

"Built-in java.math.BigInteger"
scalar BigInteger

スキーマを見ると、多くの変異とクエリが定義されていることがわかります。例えば、findTaskクエリは、タスクidとしてStringの入力型を定義し、Taskオブジェクトを返します。tasksクエリは、オプションのcompletedという引数を定義し、Taskオブジェクトの配列を返します。

ではこのサンプルAPIをより詳細に説明していきます。

注意: GraphQLの初心者であれば、先に進む前に以下を読んでおくと良いでしょう。

GraphQL – A query language for your API
https://graphql.org/

The MicroProfile GraphQL Specification

MicroProfile GraphQL仕様 (1.0.3) からの引用です。Taken from https://github.com/eclipse/microprofile-graphql (1.0.3) からの引用です。

The intent of the MicroProfile GraphQL specification is to provide a “code-first” set of APIs that will enable users to quickly develop portable GraphQL-based applications in Java.(MicroProfile GraphQL仕様の意図は、ユーザがJavaでポータブルなGraphQLベースのアプリケーションを迅速に開発できるようにするための、 code-firstな一連のAPIを提供することである)

There are 2 main requirements for all implementations of this specification, namely:(この仕様のすべての実装には、主に2つの要件がある)
1. Generate and make the GraphQL Schema available. This is done by looking at the annotations in the users code and must include all GraphQL Queries and Mutations as well as all entities as defined implicitly via the response type or argument(s) of Queries and Mutations.(GraphQLスキーマを生成して利用可能にする。これは、ユーザーコードのアノテーションを見て行われる。すべてのGraphQLクエリとミューテーション、およびクエリとミューテーションのレスポンスの型または引数を使って暗黙的に定義されたすべてのエンティティを含まなければならない)

2. Execute GraphQL requests. This will be in the form of either a Query or a Mutation. As a minimum the specification must support executing these requests via HTTP.(GraphQLリクエストを実行する。これは、クエリまたはミューテーションのいずれかの形式である。最低限、仕様はHTTP経由でのリクエストの実行をサポートしなければならない。)

Note: The spec does not yet contain support for subscriptions.(注意:この仕様ではまだサブスクリプションをサポートしていない)

MicroProfile GraphQL
https://download.eclipse.org/microprofile/microprofile-graphql-1.0.3/microprofile-graphql.html#microprofile_graphql

ハイレベルには、この仕様ではcode firstアプローチでAPIを開発するために利用可能な多くのアノテーションを定義しています。

  • GraphQLApi — CDI BeanをGraphQLエンドポイントとして識別するマーカーアノテーション
  • Query — オブジェクトまたはオブジェクトのコレクションのすべてのフィールドまたは特定のフィールドを問い合わせることができるパブリック・メソッドに適用される。これらはGraphQLApiアノテーションが付いたクラスに存在する必要がある。
  • Mutation — ユーザーがエントリを作成したり、変異させたりすることができるパブリック・メソッドに適用される。これも、GraphQLApiアノテーションが付いたクラス内に存在する必要がある。

その他の関連するアノテーションには以下のようなものがあります。

  • Type — 複合型(POJO)を出力型として定義
  • Input — 複合他がを入力型として定義
  • Interface — 型をインターフェースとして定義

Helidon Support for the MP GraphQL Spec

Helidonバージョン2.2.0は、MicroProfile GraphQL仕様のバージョン1.0.3をサポートし、合格しています。

Include Dependencies

まず、HelidonドキュメントのManaging Dependenciesのセクションで説明されているように、Helidon BOMを含める必要があります。

Managing Dependencies
https://helidon.io/docs/v2/#/about/04_managing-dependencies

次に、実装を使用するためには、以下のGraphQLの依存関係もプロジェクトに含める必要があります。

<dependency>
    <groupId>io.helidon.microprofile.graphql</groupId>
    <artifactId>helidon-microprofile-graphql-server</artifactId>
</dependency>

注意: Helidon SEでもGraphQLをサポートしますが、GraphQLスキーマとバインディングを手作業で作成しなければなりません。詳細はHelidon SEのドキュメントをご覧ください。

SE — Helidon SE
https://helidon.io/docs/v2/#/se/introduction/01_introduction

上記の依存関係を含めたら、GraphQLエンドポイントのコーディングを開始できます。

Coding the Example

注意: 以下の例はHelidon MP GraphQLのサンプルからの引用です。サンプルは以下のURLから利用できます。

Microprofile GraphQL Example
https://github.com/oracle/helidon/tree/master/examples/microprofile/graphql

Create Your API Class

クラスを作成し、@GraphQLAPI@ApplicationScoped を使って注釈を付けます。この例では、TaskConcurrentHashMap に格納しますが、Oracle Coherence をバックエンドとして使用する方法については、次回の記事でご紹介する予定です。

Coherence Community
https://coherence.community/

@GraphQLApi
@ApplicationScoped
public class TaskApi {
   // store the Tasks in a Map
   private Map<String, Task> tasks = new ConcurrentHashMap<>();
   ...}

Create an endpoint to create a new Task

以下のメソッドではdescriptionを受け入れ、新たなTaskを作成し、作成済みのTaskを返すミューテーションを定義しています

@Mutation
@Description("Create a task with the given description")
public Task createTask(@Name("description") String description) {
    if (description == null) {
        throw new IllegalArgumentException(
                                   "Description must be provided");
    }
    Task task = new Task(description);
    tasks.put(task.getId(), task);
    return task;
}

上記メソッドでいくつか注意すべきポイントがあります。

  1. @Description により、エンドポイントをドキュメント化し、スキーマで利用可能にします。
  2. @Name でパラメータに名前を付けることを保証します。
  3. 仕様では、チェック済み例外がスローされた場合、例外メッセージが表示されます。未チェックの例外の場合(例:IllegalArgumentException)はメッセージ出力が抑止されていますが、この挙動を変更できます。サンプルの META-INF/microprofile-config.properties を参照ください。

これを元に自動生成されたGraphQL Schemaは以下の通りです。

type Mutation {
  "Create a task with the given description"
  createTask(description: String): Task
}

Create an endpoint to display Tasks

以下のメソッドでは、完了済みタスクを表示するか否かを判断するため、オプションのBoolean値を指定して全てのTaskを表示するクエリを定義しています。

@Query
@Description("Query tasks and optionally specified only completed")
public Collection<Task> getTasks(@Name("completed") 
                                 Boolean completed) {
    return tasks.values().stream()
            .filter(task -> completed == null 
                    || task.isCompleted() == completed)
            .collect(Collectors.toList());
}

Create an endpoint to return an individual Task

以下のメソッドでは、idで指定された個別のTaskを返すクエリを定義しています。

@Query
@Description("Return a given task")
public Task findTask(@Name("id") String id) 
        throws TaskNotFoundException {
    return Optional.ofNullable(tasks.get(id))
       .orElseThrow(() -> 
         new TaskNotFoundException("Task not found " + id));
}

上記メソッドがTaskNotFoundExceptionをスローする場合、例外メッセージの表示がデフォルトの挙動です。

上記の2個のクエリから自動生成されたGraphQLスキーマは以下のようです。

type Query {
  "Return a given task"
  findTask(id: String!): Task
  "Query tasks and optionally specify only completed"
  tasks(completed: Boolean): [Task]
}

サンプルの全てのメソッドを説明しませんが、このサンプルを見れば一般的なアイデアが得られるはずです。

Running the Example

Taskサンプルを実行するには以下の手順で実行します。

注意 : JDK 11以上である必要があります。

  1. HelidonのリポジトリをクローンしてHelidonをビルド
git clone https://github.com/oracle/helidon.git
mvn clean install -DskipTests

2. サンプルをビルドし実行

cd examples/microprofile/graphql
mvn clean install
java -jar target/helidon-examples-microprofile-graphql.jar

起動後、ログにいくつか出力されていることに気づくはず(便宜上短縮している)。

... Discovered 1 annotated GraphQL API class... Server started on http://localhost:7001... You are using experimental features. These APIs may change, please follow changelog!
...  Experimental feature: GraphQL (GraphQL)

上記の例では、Helidon MP GraphQLがGraphQLApiアノテーションが付いた1個のクラスを発見し、サーバーがポート番号7001で開始し、現在のGraphQLのバージョンがexperimental(実験的)であることを示している。

3. 続いて、スキーマを取得する。GraphQL仕様によると、スキーマは http://host:port/graphql/schema.graphql で利用可能でなければならない。

$ curl http://127.0.0.1:7001/graphql/schema.graphqltype Mutation {
   "Create a task with the given description"
   createTask(description: String): Task
   "Remove all completed tasks and return the tasks left"
   deleteCompletedTasks: [Task]
   "Delete a task and return the deleted task details"
   deleteTask(id: String): Task
   "Update a task"
   updateTask(completed: Boolean, description: 
              String, id: String): Task 
}type Query {
   "Return a given task"
   findTask(id: String): Task
   "Query tasks and optionally specify only completed"
   tasks(completed: Boolean): [Task]
}type Task {
   completed: Boolean!
   createdAt: BigInteger!
   description: String!
   id: String!
}"Custom: Built-in java.math.BigInteger"
scalar BigInteger

4. curlでcreateTaskミューテーションを呼び出すと、新規作成されたTaskオブジェクトが返る

curl -X POST http://127.0.0.1:7001/graphql -d '{"query":"mutation createTask { createTask(description: \"Task Description 1\") { id description createdAt completed }}"}'{ 
"data":{"createTask":
   {
     "id":"0d4a8d",
     "description":"Task Description 1",
     "createdAt":1605501774877,
     "completed":false
   }
}

Using the GraphiQL UI

GraphiQL UIは、GraphQLコマンドを実行するためのUIを提供します。

GraphQL IDE Monorepo
https://github.com/graphql/graphiql

このツールはHelidonのMicroProfile実装にはデフォルトで含まれてはいません。以下のガイドに従い、UIをGraphQLアプリケーションに組み込むことができます。

注意: 以下はGraphQL MicroProfileのサンプルからの引用です。サンプルは以下のURLから利用できます。

Microprofile GraphQL Example
https://github.com/oracle/helidon/tree/master/examples/microprofile/graphql

  1. ここからサンプルのindex.htmlファイルの内容を examples/microprofile/graphql/src/main/resources/web/index.html にコピー
  2. fetch('https://my/graphql', { の行のURL(この例ではhttps://my/graphql)を http://127.0.0.1:7001/graphql に変更
  3. 上記指示に従い、サンプルを再ビルドして実行
  4. http://127.0.0.1:7001/ui でGraphiQL UIにアクセス
GraphiQL UI
GraphiQL UI

最後に、以下のコマンドを左側のエディターにコピーし、Playボタンを押して個々のミューテーションやクエリを実行してください。

# Fragment to allow shorcut to display all fields for a task
fragment task on Task {
  id
  description
  createdAt
  completed
}

# Create a task
mutation createTask {
  createTask(description: "Task Description 1") {
    ...task
  }
}

mutation createTaskWithoutDescription {
  createTask {
    ...task
  }
}

# Find all the tasks
query findAllTasks {
  tasks {
    ...task
  }
}

# Find a task
query findTask {
  findTask(id: "251474") {
    ...task
  }
}

# Find completed Tasks
query findCompletedTasks {
  tasks(completed: true) {
    ...task
  }
}

# Find outstanding Tasks
query findOutstandingTasks {
  tasks(completed: false) {
    ...task
  }
}

mutation updateTask {
  updateTask(id: "251474" description:"New Description") {
    ...task
  }
}

mutation completeTask {
  updateTask(id: "251474" completed:true) {
    ...task
  } 
}

# Delete a task
mutation deleteTask {
  deleteTask(id: "1f6ae5") {
    ...task
  }
}

# Delete completed
mutation deleteCompleted {
  deleteCompletedTasks {
    ...task
  }
}

META-INF/microprofile-config.properties には以下のGraphiQLを実行支援するものが含まれています。

  1. server.static.classpath.context=/ui
  2. server.static.classpath.location=/web
  3. graphql.cors=Access-Control-Allow-Origin
  4. mp.graphql.exceptionsWhiteList=java.lang.IllegalArgumentException

最初の2個のプロパティは /uiresources/web にマッピングすることで、index.htmlが表示されます。

3番目のプロパティでCORSを許可するため、GraphiQLインターフェースが動作します。

通常未チェックの例外は表示されませんが、4個目のプロパティでオーバーライドしています。

Where to From Here?

GraphQLとHelidonについて詳細を知りたい方は以下のリソースをどうぞ。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中