Вступ до GraphQL

1. Огляд

GraphQL - це мова запитів, створена Facebook з метою побудови клієнтських додатків на основі інтуїтивно зрозумілого та гнучкого синтаксису для опису їх вимог до даних та взаємодії.

Однією з основних проблем традиційних викликів REST є нездатність клієнта вимагати індивідуальний (обмежений або розширений) набір даних. У більшості випадків, коли клієнт запитує інформацію у сервера, він отримує всі або жодне з полів.

Ще одна складність - робота та підтримка декількох кінцевих точок. У міру зростання платформи, отже, кількість буде збільшуватися. Тому клієнтам часто потрібно запитувати дані з різних кінцевих точок.

При побудові сервера GraphQL потрібно мати лише одну URL-адресу для отримання та мутації даних. Таким чином, клієнт може запитувати набір даних, надсилаючи рядок запиту, описуючи те, що вони хочуть, на сервер.

2. Основна номенклатура GraphQL

Давайте подивимось на основну термінологію GraphQL.

  • Запит: це операція лише для читання, запитувана до сервера GraphQL
  • Мутація: це операція читання-запису, запитувана на сервер GraphQL
  • Resolver: У GraphQL Resolver відповідає за відображення операції та коду, що працює на серверній панелі, яка відповідає за обробку запиту. Це аналогічно серверній програмі MVC у додатку RESTFul
  • Тип: Тип визначає форму даних відгуку , які можуть бути повернуті з сервера GraphQL, в тому числі полів , які є ребром до інших типів
  • Вхідні дані: як тип, але визначає форму вхідних даних, які надсилаються на сервер GraphQL
  • Скаляр: це примітивний тип , такий як String , Int , Boolean , Float тощо
  • Інтерфейс: Інтерфейс буде зберігати імена полів та їх аргументи, тому об'єкти GraphQL можуть успадковувати від нього, забезпечуючи використання певних полів
  • Схема: У GraphQL схема керує запитами та мутаціями, визначаючи, що дозволено виконувати на сервері GraphQL

2.1. Завантаження схеми

Існує два способи завантаження схеми на сервер GraphQL:

  1. за допомогою мови визначення інтерфейсу GraphQL (IDL)
  2. за допомогою однієї з підтримуваних мов програмування

Давайте продемонструємо приклад використання IDL:

type User { firstName: String }

Тепер приклад визначення схеми з використанням коду Java:

GraphQLObjectType userType = newObject() .name("User") .field(newFieldDefinition() .name("firstName") .type(GraphQLString)) .build();

3. Мова визначення інтерфейсу

Мова визначення інтерфейсу (IDL) або Мова визначення схеми (SDL) - це найбільш стислий спосіб вказати схему GraphQL. Синтаксис чітко визначений і буде прийнятий в офіційній специфікації GraphQL.

Наприклад, давайте створимо схему GraphQL для користувача / адреси електронної пошти можуть бути вказані таким чином:

schema { query: QueryType } enum Gender { MALE FEMALE } type User { id: String! firstName: String! lastName: String! createdAt: DateTime! age: Int! @default(value: 0) gender: [Gender]! emails: [Email!]! @relation(name: "Emails") } type Email { id: String! email: String! default: Int! @default(value: 0) user: User @relation(name: "Emails") }

4. GraphQL-java

GraphQL-java - це реалізація, заснована на специфікації та посиланній реалізації JavaScript. Зверніть увагу, що для належної роботи потрібен принаймні Java 8.

4.1. Анотації GraphQL-Java

GraphQL також дозволяє використовувати анотації Java для генерації її визначення схеми без усього типового коду, створеного за допомогою традиційного підходу IDL.

4.2. Залежності

Щоб створити наш приклад, давайте спочатку почнемо імпортувати необхідну залежність, яка покладається на модуль Graphql-java-annotations:

 com.graphql-java graphql-java-annotations 3.0.3 

Ми також впроваджуємо бібліотеку HTTP, щоб полегшити налаштування в нашому додатку. Ми збираємось використовувати Ratpack (хоча він може бути реалізований також з Vert.x, Spark, Dropwizard, Spring Boot тощо).

Давайте також імпортуємо залежність Ratpack:

 io.ratpack ratpack-core 1.4.6 

4.3. Впровадження

Давайте створимо наш приклад: простий API, який забезпечує “CRUDL” (створення, отримання, оновлення, видалення та список) для користувачів. Спочатку створимо наш POJO для користувача :

@GraphQLName("user") public class User { @GraphQLField private Long id; @GraphQLField private String name; @GraphQLField private String email; // getters, setters, constructors, and helper methods omitted }

У цьому POJO ми можемо бачити анотацію @GraphQLName (“користувач”) , як ознаку того, що цей клас відображається GraphQL разом із кожним полем, анотованим @GraphQLField.

Далі ми створимо клас UserHandler . Цей клас успадковує від обраної бібліотеки з'єднувача HTTP (у нашому випадку Ratpack) метод обробника, який буде керувати та викликати функцію Resolver GraphQL . Таким чином, перенаправляючи запит (корисні навантаження JSON) на належну операцію запиту або мутації:

@Override public void handle(Context context) throws Exception { context.parse(Map.class) .then(payload -> { Map parameters = (Map) payload.get("parameters"); ExecutionResult executionResult = graphql .execute(payload.get(SchemaUtils.QUERY) .toString(), null, this, parameters); Map result = new LinkedHashMap(); if (executionResult.getErrors().isEmpty()) { result.put(SchemaUtils.DATA, executionResult.getData()); } else { result.put(SchemaUtils.ERRORS, executionResult.getErrors()); LOGGER.warning("Errors: " + executionResult.getErrors()); } context.render(json(result)); }); }

Тепер клас, який буде підтримувати операції запиту, тобто UserQuery. Як уже згадувалося, усі методи, які отримують дані з сервера на клієнт, управляються цим класом:

@GraphQLName("query") public class UserQuery { @GraphQLField public static User retrieveUser( DataFetchingEnvironment env, @NotNull @GraphQLName("id") String id) { // return user } @GraphQLField public static List listUsers(DataFetchingEnvironment env) { // return list of users } }

Similarly to UserQuery, now we create UserMutation, which will manage all the operations that intend to change some given data stored on the server side:

@GraphQLName("mutation") public class UserMutation { @GraphQLField public static User createUser( DataFetchingEnvironment env, @NotNull @GraphQLName("name") String name, @NotNull @GraphQLName("email") String email) { //create user information } }

It is worth notice the annotations in both UserQuery and UserMutation classes: @GraphQLName(“query”) and @GraphQLName(“mutation”). Those annotations are used to define the query and mutation operations respectively.

With the GraphQL-java server able to run the query and mutation operations, we can use the following JSON payloads to test the request of the client against the server:

  • For the CREATE operation:
{ "query": "mutation($name: String! $email: String!){ createUser (name: $name email: $email) { id name email age } }", "parameters": { "name": "John", "email": "[email protected]" } } 

As the response from the server for this operation:

{ "data": { "createUser": { "id": 1, "name": "John", "email": "[email protected]" } } }
  • For the RETRIEVE operation:
{ "query": "query($id: String!){ retrieveUser (id: $id) {name email} }", "parameters": { "id": 1 } }

As the response from the server for this operation:

{ "data": { "retrieveUser": { "name": "John", "email": "[email protected]" } } }

GraphQL надає функції, які клієнт може налаштувати на відповідь. Отже, в останній операції RETRIEVE, використаної як приклад, замість повернення імені та електронної пошти ми можемо, наприклад, повернути лише електронну адресу:

{ "query": "query($id: String!){ retrieveUser (id: $id) {email} }", "parameters": { "id": 1 } }

Отже, інформація, що повертається із сервера GraphQL, поверне лише запитувані дані:

{ "data": { "retrieveUser": { "email": "[email protected]" } } }

5. Висновок

GraphQL - це простий і досить привабливий спосіб мінімізації складності між клієнтом / сервером як альтернативний підхід до REST API.

Як завжди, приклад доступний у нашому сховищі GitHub.