Einführung in GraphQL

1. Überblick

GraphQL ist eine Abfragesprache, die von Facebook mit dem Zweck erstellt wurde, Clientanwendungen basierend auf intuitiver und flexibler Syntax zu erstellen, um deren Datenanforderungen und Interaktionen zu beschreiben.

Eine der Hauptherausforderungen bei herkömmlichen REST-Aufrufen ist die Unfähigkeit des Clients, einen benutzerdefinierten (begrenzten oder erweiterten) Datensatz anzufordern. Wenn der Client Informationen vom Server anfordert, erhält er in den meisten Fällen entweder alle oder keine Felder.

Eine weitere Schwierigkeit ist das Arbeiten und Verwalten mehrerer Endpunkte. Wenn eine Plattform wächst, wird folglich die Anzahl zunehmen. Daher müssen Kunden häufig Daten von verschiedenen Endpunkten abfragen.

Beim Erstellen eines GraphQL-Servers ist nur eine URL für das Abrufen und Mutieren von Daten erforderlich. Auf diese Weise kann ein Client einen Datensatz anfordern, indem er eine Abfragezeichenfolge, die seine Anforderungen beschreibt, an einen Server sendet.

2. Grundlegende GraphQL-Nomenklatur

Schauen wir uns die grundlegende Terminologie von GraphQL an.

  • Abfrage: ist eine schreibgeschützte Operation, die von einem GraphQL-Server angefordert wird

  • Mutation: ist eine Lese- und Schreiboperation, die von einem GraphQL-Server angefordert wird

  • Resolver: In GraphQL ist der Resolver für das Mapping des verantwortlich

Operation und den Code, der auf dem Backend läuft, für den verantwortlich ist die Anfrage bearbeiten. Es ist analog zum MVC-Backend in einem RESTFul Anwendung Typ: ** Ein Type definiert die Form der Antwortdaten, die sein können

Vom GraphQL-Server zurückgegeben werden, einschließlich der Felder, an denen die Kanten liegen andere Arten Eingabe: ** Wie ein Type, __ definiert aber die Form der Eingabedaten

an einen GraphQL-Server gesendet Scalar: ** ist ein primitiver Type , wie ein String , Int , Boolean ,

Float usw Schnittstelle: ** Eine Schnittstelle speichert die Namen der Felder und ihre

Argumente, damit GraphQL-Objekte davon erben können, wodurch die Verwendung von sichergestellt wird bestimmte Felder Schema: ** In GraphQL verwaltet das Schema Abfragen und Mutationen.

Definieren, was auf dem GraphQL-Server ausgeführt werden darf

2.1. Schema laden

Es gibt zwei Möglichkeiten, ein Schema in den GraphQL-Server zu laden:

  1. mit der Interface Definition Language (IDL) von GraphQL

  2. mit einer der unterstützten Programmiersprachen

Lassen Sie uns ein Beispiel mit IDL demonstrieren:

type User {
    firstName: String
}

Nun ein Beispiel für die Schemadefinition mit Java-Code:

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

3. Schnittstellendefinitionssprache

Interface Definition Language (IDL) oder Schema Definition Language (SDL) ist die präziseste Art, ein GraphQL-Schema anzugeben. Die Syntax ist klar definiert und wird in der offiziellen GraphQL-Spezifikation übernommen.

Lassen Sie uns zum Beispiel ein GraphQL-Schema für einen Benutzer erstellen. E-Mails könnten folgendermaßen angegeben werden:

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 ist eine Implementierung, die auf der Spezifikation und der Referenzimplementierung von JavaScript basiert. Beachten Sie, dass für die ordnungsgemäße Ausführung mindestens Java 8 erforderlich ist.

4.1. GraphQL-Java-Anmerkungen

GraphQL ermöglicht auch die Verwendung von Java-Annotationen , um seine Schemadefinition zu generieren, ohne den gesamten Boilerplate-Code, der mit dem traditionellen IDL-Ansatz erstellt wurde.

4.2. Abhängigkeiten

Um unser Beispiel zu erstellen, beginnen wir zuerst mit dem Import der erforderlichen Abhängigkeit, die auf https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.graphql-java%22%20AND%20a beruht % 3A% 22graphql-java-annotations% 22 Modul[Graphql-java-annotations]:

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-annotations</artifactId>
    <version>3.0.3</version>
</dependency>

Wir implementieren auch eine HTTP-Bibliothek, um das Setup in unserer Anwendung zu vereinfachen. Wir verwenden Ratpack (obwohl es kann auch mit Vert.x, Spark, Dropwizard, Spring Boot usw. implementiert werden.

Lassen Sie uns auch die Ratpack-Abhängigkeit importieren:

<dependency>
    <groupId>io.ratpack</groupId>
    <artifactId>ratpack-core</artifactId>
    <version>1.4.6</version>
</dependency>

4.3. Implementierung

Erstellen wir unser Beispiel: eine einfache API, die Benutzern eine „CRUDL“ (Erstellen, Abrufen, Aktualisieren, Löschen und Auflisten) zur Verfügung stellt. Zuerst erstellen wir unseren User 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
}

In diesem POJO sehen wir die Annotation @ GraphQLName (“user”) als Hinweis darauf, dass diese Klasse von GraphQL zusammen mit jedem mit @GraphQLField. annotierten Feld zugeordnet wird.

Als Nächstes erstellen wir die UserHandler -Klasse. Diese Klasse erbt von der ausgewählten HTTP-Connector-Bibliothek (in unserem Fall Ratpack) eine Handler-Methode, die die Resolver -Funktion von GraphQL verwaltet und aufruft. Umleiten der Anforderung (JSON-Payloads) zur richtigen Abfrage oder Mutation:

@Override
public void handle(Context context) throws Exception {
    context.parse(Map.class)
      .then(payload -> {
          Map<String, Object> parameters = (Map<String, Object>)
            payload.get("parameters");
          ExecutionResult executionResult = graphql
            .execute(payload.get(SchemaUtils.QUERY)
              .toString(), null, this, parameters);
          Map<String, Object> 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));
      });
}

Nun, die Klasse, die die Abfrageoperationen unterstützt, d. H. UserQuery. Wie bereits erwähnt, werden alle Methoden, die Daten vom Server zum Client abrufen, von dieser Klasse verwaltet:

@GraphQLName("query")
public class UserQuery {

    @GraphQLField
    public static User retrieveUser(
     DataFetchingEnvironment env,
      @NotNull @GraphQLName("id") String id) {
       //return user
    }

    @GraphQLField
    public static List<User> listUsers(DataFetchingEnvironment env) {
       //return list of users
    }
}

Ähnlich wie UserQuery erstellen wir jetzt UserMutation, __, die alle Vorgänge verwaltet, die bestimmte auf der Serverseite gespeicherte Daten ändern sollen:

@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
    }
}

Beachten Sie die Annotationen in den Klassen UserQuery und UserMutation : @ GraphQLName ("query") und __ @ GraphQLName ("mutation"). Diese Anmerkungen werden zur Definition der Abfrage- bzw. Mutationsoperationen verwendet.

Da der GraphQL-java-Server die Abfrage- und Mutationsoperationen ausführen kann, können wir die folgenden JSON-Nutzdaten verwenden, um die Anforderung des Clients an den Server zu testen:

  • Für die CREATE-Operation:

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

Als Antwort vom Server für diesen Vorgang:

{
    "data": {
        "createUser": {
            "id": 1,
            "name": "John",
            "email": "[email protected]"
        }
    }
}
  • Für den RETRIEVE-Vorgang:

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

Als Antwort vom Server für diesen Vorgang:

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

GraphQL bietet Funktionen, mit denen der Client die Antwort anpassen kann. Im letzten RETRIEVE-Vorgang, der als Beispiel verwendet wurde, können wir anstelle von Name und E-Mail beispielsweise nur die E-Mail zurückgeben:

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

Die zurückgegebenen Informationen vom GraphQL-Server geben also nur die angeforderten Daten zurück:

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

5. Fazit

GraphQL ist eine einfache und ziemlich attraktive Möglichkeit, die Komplexität zwischen Client/Server als alternativen Ansatz für REST-APIs zu minimieren.

Wie immer ist das Beispiel in unserem https://github.com/eugenp/tutorials/tree/master/graphql/graphql-java [GitHub-Repository verfügbar.