Erste Schritte mit GraphQL und Spring Boot

1. Einleitung

GraphQL ist ein relativ neues Konzept von Facebook, das als Alternative zu REST für Web-APIs in Rechnung gestellt wird.

Dieser Artikel enthält eine Einführung in die Einrichtung eines GraphQL-Servers mithilfe von Spring Boot, damit er vorhandenen Anwendungen hinzugefügt oder in neuen verwendet werden kann.

2. Was ist GraphQL ?

Herkömmliche REST-APIs arbeiten mit dem vom Server verwalteten Ressourcenkonzept. Diese Ressourcen können auf einige Standardmethoden gemäß den verschiedenen HTTP-Verben bearbeitet werden. Dies funktioniert sehr gut, solange unsere API zum Ressourcenkonzept passt, fällt jedoch schnell auseinander, wenn wir davon abweichen müssen.

Dies leidet auch, wenn der Client Daten aus mehreren Ressourcen gleichzeitig benötigt. Zum Beispiel das Anfordern eines Blogposts und der Kommentare. In der Regel wird dies dadurch gelöst, dass der Client entweder mehrere Anforderungen stellt oder dass der Server zusätzliche Daten bereitstellt, die möglicherweise nicht immer erforderlich sind, was zu größeren Antwortgrößen führt.

GraphQL bietet eine Lösung für beide Probleme . Der Client kann genau angeben, welche Daten gewünscht werden, einschließlich der Navigation untergeordneter Ressourcen in einer einzelnen Anforderung, und mehrere Abfragen in einer einzelnen Anforderung.

Es funktioniert auch viel mehr RPC-artig und verwendet benannte Abfragen und Mutationen anstelle eines obligatorischen Standardsatzes von Aktionen. Auf diese Weise wird das Steuerelement dort platziert, wo es hingehört, wobei der API-Entwickler angibt, was möglich ist, und der API-Verbraucher, was gewünscht wird.

Ein Blog kann beispielsweise die folgende Abfrage zulassen:

query { recentPosts(count: 10, offset: 0) { id title category author { id name thumbnail } } }

Diese Abfrage wird:

  • Fordern Sie die zehn neuesten Beiträge an
  • Fordern Sie für jeden Beitrag die ID, den Titel und die Kategorie an
  • Für jede Beitragsanfrage gibt der Autor die ID, den Namen und die Miniaturansicht zurück

In einer herkömmlichen REST-API sind entweder 11 Anforderungen erforderlich - 1 für die Beiträge und 10 für die Autoren - oder die Autorendetails müssen in den Beitragsdetails enthalten sein.

2.1. GraphQL-Schemata

Der GraphQL-Server stellt ein Schema bereit, das die API beschreibt. Dieses Schema besteht aus Typdefinitionen. Jeder Typ hat ein oder mehrere Felder, die jeweils null oder mehr Argumente annehmen und einen bestimmten Typ zurückgeben.

Das Diagramm besteht aus der Art und Weise, wie diese Felder miteinander verschachtelt sind. Beachten Sie, dass der Graph nicht azyklisch sein muss - Zyklen sind durchaus akzeptabel -, aber er ist gerichtet. Das heißt, der Client kann von einem Feld zu seinen untergeordneten Feldern gelangen, aber nicht automatisch zum übergeordneten Feld zurückkehren, es sei denn, das Schema definiert dies explizit.

Ein Beispiel für ein GraphQL-Schema für ein Blog kann die folgenden Definitionen enthalten, die einen Beitrag, einen Autor des Beitrags und eine Stammabfrage beschreiben, um die neuesten Beiträge im Blog abzurufen.

type Post { id: ID! title: String! text: String! category: String author: Author! } type Author { id: ID! name: String! thumbnail: String posts: [Post]! } # The Root Query for the application type Query { recentPosts(count: Int, offset: Int): [Post]! } # The Root Mutation for the application type Mutation { writePost(title: String!, text: String!, category: String) : Post! }

Das "!" am Ende einiger Namen gibt an, dass dies ein nicht nullbarer Typ ist. Jeder Typ, der dies nicht hat, kann in der Antwort vom Server null sein. Der GraphQL-Dienst behandelt diese korrekt, sodass wir untergeordnete Felder nullter Typen sicher anfordern können.

Der GraphQL-Dienst macht das Schema selbst auch mithilfe eines Standardsatzes von Feldern verfügbar, sodass jeder Client die Schemadefinition vorab abfragen kann.

Auf diese Weise kann der Client automatisch erkennen, wenn sich das Schema ändert, und Clients können sich dynamisch an die Funktionsweise des Schemas anpassen. Ein unglaublich nützliches Beispiel hierfür ist das später beschriebene GraphiQL-Tool, mit dem wir mit jeder GraphQL-API interagieren können.

3. Einführung in GraphQL Spring Boot Starter

Der Spring Boot GraphQL Starter bietet eine fantastische Möglichkeit, einen GraphQL-Server in sehr kurzer Zeit zum Laufen zu bringen . In Kombination mit der GraphQL Java Tools-Bibliothek müssen wir nur den für unseren Service erforderlichen Code schreiben.

3.1. Einrichten des Dienstes

Damit dies funktioniert, müssen wir nur die richtigen Abhängigkeiten verwenden:

 com.graphql-java graphql-spring-boot-starter 5.0.2   com.graphql-java graphql-java-tools 5.2.4 

Spring Boot nimmt diese automatisch auf und richtet die entsprechenden Handler so ein, dass sie automatisch funktionieren.

Standardmäßig wird dadurch der GraphQL-Dienst auf dem Endpunkt / graphql unserer Anwendung verfügbar gemacht und POST-Anforderungen akzeptiert, die die GraphQL-Nutzlast enthalten. Dieser Endpunkt kann bei Bedarf in unserer Datei application.properties angepasst werden .

3.2. Schema schreiben

Die GraphQL Tools-Bibliothek verarbeitet GraphQL-Schemadateien, um die richtige Struktur zu erstellen, und verdrahtet dann spezielle Beans mit dieser Struktur. Der Spring Boot GraphQL-Starter findet diese Schemadateien automatisch .

Diese Dateien müssen mit der Erweiterung “gespeichert werden. graphqls “und kann überall im Klassenpfad vorhanden sein. Wir können auch so viele dieser Dateien wie gewünscht haben, so dass wir das Schema nach Wunsch in Module aufteilen können.

Die einzige Voraussetzung ist, dass es genau eine Root-Abfrage und bis zu einer Root-Mutation geben muss. Dies kann im Gegensatz zum Rest des Schemas nicht auf Dateien aufgeteilt werden. Dies ist eine Einschränkung der GraphQL-Schemadefinition selbst und nicht der Java-Implementierung.

3.3. Root Query Resolver

Für die Stammabfrage müssen im Spring-Kontext spezielle Beans definiert sein, damit die verschiedenen Felder in dieser Stammabfrage verarbeitet werden können . Im Gegensatz zur Schemadefinition gibt es keine Einschränkung, dass nur eine einzige Spring Bean für die Stammabfragefelder vorhanden ist.

Die einzigen Anforderungen sind, dass die Beans GraphQLQueryResolver implementieren und dass jedes Feld in der Stammabfrage aus dem Schema eine Methode in einer dieser Klassen mit demselben Namen hat.

public class Query implements GraphQLQueryResolver { private PostDao postDao; public List getRecentPosts(int count, int offset) { return postsDao.getRecentPosts(count, offset); } }

Die Namen der Methode müssen in dieser Reihenfolge wie folgt lauten:

  1. ist - nur wenn das Feld vom Typ Boolean ist
  2. bekommen

Die Methode muss Parameter haben, die beliebigen Parametern im GraphQL-Schema entsprechen, und kann optional einen endgültigen Parameter vom Typ DataFetchingEnvironment verwenden.

The method must also return the correct return type for the type in the GraphQL scheme, as we are about to see. Any simple types – String, Int, List, etc. – can be used with the equivalent Java types, and the system just maps them automatically.

The above defined the method getRecentPosts which will be used to handle any GraphQL queries for the recentPosts field in the schema defined earlier.

3.4. Using Beans to Represent Types

Every complex type in the GraphQL server is represented by a Java bean – whether loaded from the root query or from anywhere else in the structure. The same Java class must always represent the same GraphQL type, but the name of the class is not necessary.

Fields inside the Java bean will directly map onto fields in the GraphQL response based on the name of the field.

public class Post { private String id; private String title; private String category; private String authorId; }

Any fields or methods on the Java bean that do not map on to the GraphQL schema will be ignored, but will not cause problems. This is important for field resolvers to work.

For example, the field authorId here does not correspond to anything in our schema we defined earlier, but it will be available to use for the next step.

3.5. Field Resolvers for Complex Values

Sometimes, the value of a field is non-trivial to load. This might involve database lookups, complex calculations, or anything else. GraphQL Tools has a concept of a field resolver that is used for this purpose. These are Spring beans that can provide values in place of the data bean.

The field resolver is any bean in the Spring Context that has the same name as the data bean, with the suffix Resolver, and that implements the GraphQLResolver interface. Methods on the field resolver bean follow all of the same rules as on the data bean but are also provided the data bean itself as a first parameter.

If a field resolver and the data bean both have methods for the same GraphQL field then the field resolver will take precedence.

public class PostResolver implements GraphQLResolver { private AuthorDao authorDao; public Author getAuthor(Post post) { return authorDao.getAuthorById(post.getAuthorId()); } }

The fact that these field resolvers are loaded from the Spring context is important. This allows them to work with any other Spring managed beans – e.g., DAOs.

Importantly, if the client does not request a field, then the GraphQL Server will never do the work to retrieve it. This means that if a client retrieves a Post and does not ask for the Author, then the getAuthor() method above will never be executed, and the DAO call will never be made.

3.6. Nullable Values

The GraphQL Schema has the concept that some types are nullable and others are not.

This can be handled in the Java code by directly using null values, but equally, the new Optional type from Java 8 can be used directly here for nullable types, and the system will do the correct thing with the values.

This is very useful as it means that our Java code is more obviously the same as the GraphQL schema from the method definitions.

3.7. Mutations

So far, everything that we have done has been about retrieving data from the server. GraphQL also has the ability to update the data stored on the server, by means of mutations.

From the point of view of the code, there is no reason that a Query can't change data on the server. We could easily write query resolvers that accept arguments, save new data and return those changes. Doing this will cause surprising side effects for the API clients, and is considered bad practice.

Instead, Mutations should be used to inform the client that this will cause a change to the data being stored.

Mutations are defined in the Java code by using classes that implement GraphQLMutationResolver instead of GraphQLQueryResolver.

Otherwise, all of the same rules apply as for queries. The return value from a Mutation field is then treated exactly the same as from a Query field, allowing nested values to be retrieved as well.

public class Mutation implements GraphQLMutationResolver { private PostDao postDao; public Post writePost(String title, String text, String category) { return postDao.savePost(title, text, category); } }

4. Introducing GraphiQL

GraphQL also has a companion tool called GraphiQL. This is a UI that is able to communicate with any GraphQL Server and execute queries and mutations against it. A downloadable version of it exists as an Electron app and can be retrieved from here.

It is also possible to include the web-based version of GraphiQL in our application automatically, by adding the GraphiQL Spring Boot Starter dependency:

 com.graphql-java graphiql-spring-boot-starter 5.0.2  

This will only work if we are hosting our GraphQL API on the default endpoint of /graphql though, so the standalone application will be needed if that is not the case.

5. Summary

GraphQL ist eine sehr aufregende neue Technologie, die möglicherweise die Art und Weise, wie Web-APIs entwickelt werden, revolutionieren kann.

Die Kombination der Spring Boot GraphQL Starter- und der GraphQL Java Tools-Bibliotheken macht es unglaublich einfach, diese Technologie neuen oder vorhandenen Spring Boot-Anwendungen hinzuzufügen.

Code-Schnipsel finden Sie auf GitHub.