codecentric

GraphQL mit Spotify – Teil 2: React Native Client mit Apollo

Keine Kommentare

GraphQL im Client: Introduction

Sobald wir mit einen GraphQL-Client, hier mit Apollo, Anfragen an unserem GraphQL-Server aus dem 1. Teil schicken, werden die Vorteile von GraphQL noch besser sichtbar.

Wir werden sehen, wie

  • leicht GraphQL im Client hinzugefügt werden kann,
  • die React-UI-Komponenten mit nur geringem Aufwand erweitert werden können,
  • GraphQL zum deklarativem Ansatz von React passt,
  • GraphiQL die schnelle Anknüpfung an Schema und Server unterstützt,
  • durch den Einsatz einer Bibliothek Aspekte wie Fetching, Error Handling und Caching von der Business-Logik isoliert bleiben und
  • sogenannte Co-Location hilft, Wartbarkeit zu erhöhen.

In unserem Beispiel werden wir Spotify-Daten auf einem mobilen Gerät in einer React-Native-App vom eigenen GraphQL-Server laden.

teaser: Spotify-Daten in einer React-Native-App via GraphQL client laden

Für eine schnelle Umsetzung – in wenigen Minuten – können wir Expo, also React Native, benutzen (mehr dazu im Block weiter unten) – Eine Demoversion wird einfach auf expo.io veröffentlicht.

Bibliothek/Integration

Wir werden die Apollo-2-Client-Bibliothek verwenden, die leichter einzusetzen ist als Facebooks eigene Bibliothek Relay. Relay kann zwar noch leistungsfähiger mit schlechten Netzverbindungen umgehen, würde aber mehr Komplexität und eine höhere Lernkurve mitbringen, und auch das Schema müsste daraufhin erweitert und angepasst werden.

GraphQL im Client mit Apollo und React

Apollo-Grundlagen

Mit Apollo werden die Daten, ähnlich wie bei flux/redux, in einem lokalen State oder Cache gehalten. Die UI-Komponenten werden dann per HOC(Higher Order Component)-Ansatz gekapselt, an diesen State gekoppelt und mit den vom Server geladenen Werten gefüllt. Dadurch kann die Verbindung zum Server an zentraler Stelle zu konfiguriert werden. Apollo Version 1 baute noch direkt auf redux auf, aber mit Version 2 wurde es unabhängig von einem redux-store, modelliert den Cache selbst und bringt eine verbesserte Modularisierung mit sich.

Diese Komponenten stehen zur Verfügung:

  • ApolloClient: Verbindung von Cache und Netzwerk Stack/Link
  • ApolloInMemoryCache: clientseitiger GraphQL-Cache mit Normalisierung (ist Default-Implementierung)
  • ApolloLink: Netzwerk-Verbindung und Schnittstelle zum GraphQL-Server
  • ApolloProvider: Verbindung zwischen ApolloClient und den Komponenten in der Komponenten-Hierarchie

Installation der Bibliotheken und Benutzung in React

  1. Apollo Boost erlaubt durch Rückgriff auf Standardwerte mit wenigster Konfiguration starten zu können.
  2. Seit Apollo React 2.1 können sog. renderProps genutzt werden.
  3. Mit Apollo Client 2 wurden HOC (Higher Order Components) als Standard eingeführt, die reine UI-Komponenten von dem GraphQL-Glue-Code trennt. Der letzte Ansatz erlaubt leichtere Wiederverwendung und vor allem Testen und soll deshalb hier benutzt werden.

Nach Installation folgender Bibliotheken mit

npm install apollo-client-preset react-apollo graphql-tag graphql --save

können wir auf oberster Ebene den Kontext mit der Verbindung zum GraphQL-Server definieren.

// app.js
import { ApolloClient, InMemoryCache } from 'apollo-client-preset';
import { HttpLink } from 'apollo-link-http';

const client = new ApolloClient({
    link: new HttpLink({
        uri: 'http://localhost:3000' // GraphQL endpoint  
    }),
    cache: new InMemoryCache()
});

import { ApolloProvider } from 'react-apollo';

import Hauptkomponente from './Hauptkomponente';

// rendering in a react web app:
ReactDOM.render(
    <ApolloProvider client={client}>
        <Hauptkomponente />
    </ApolloProvider>,
    document.getElementById('root')
);

Die Komponenten werden nun einfach per Apollo gekapselt und dadurch um die GraphQL-Abfrage erweitert.

// Hauptkomponente.js
import gql from 'graphql-tag';

class ReactKomponente extends React.Component {
    //...
}

// Anreicherung um GraphQL Abfrage
export default graphql(
    gql`{ ... }`,  // ES6 template string mit GraphQL query
    config,        // optional
)(ReactKomponente);

Dabei werden die Daten per React-Mechanismen als „props“ in die Komponente übergeben, dazu gleich mehr.

Abfrage-Parametrisierung mit Variablen

Um Variablen in GraphQL-Abfragen zu verwenden, kann das optionale `config-Object benutzt werden:

let config = {
    options: (props) => ({
        variables: {
            byName: props.name
        }
    })
};

In diesem Beispiel wird die Variable byName aus den props der React-Komponente mit dem Wert des name-Feldes gefüllt, also zum Beispiel mit Xul Zolar, wenn die gekapselte Komponente folgendermaßen verwendet wird:

    <GekapselteReactKomponente name='Xul Zolar' />

Durch diesen deklarativen Ansatz lässt sich GraphQL mit React-Mechanismen ohne viel Aufwand in das UI integrieren.

Mobiler Client: Spotify Music Info App

Schritte für die Umsetzung in unserem Spotify-Beispiel:

  • Tool-Installation: Expo, Android/iOS-Simulator oder Expo-App auf Smartphone (siehe Absatz “Expo…“)
  • Projekt initialisieren mit Expo
  • Einfaches UI und einfaches Styling
  • Modellierung der Datenabfrage mit GraphiQL
  • Verbindung zum Server und Verknüpfung der Komponente mit GraphQL-Abfrage

Projekt initialisieren mit Expo

Wir installieren das Projekt mit Expo:

exp init spotify-graphql-client-react-native-expo --projectType blank

Einfaches UI und Styling

Die generierte Seite muss noch abgeändert werden. Der Rückgabewert der render-Method

// app.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default class App extends React.Component {
    render() {
        return (
            <View style={styles.container}>
                <Text>Initial UI, more to come!</Text>
            </View>
        );
    }
};

wird mit

    <View style={styles.container}>
         <Query />
         <Artists name={this.state.artistName} />
    </View>

ersetzt und die folgenden Zeilen hinzugefügt:

const Query = ({search}) => <SearchBar
    onChangeText={search}
    placeholder="artist name..." />;
        
const Artists = ({artists}) => (
    <View>
    {
        artists.map(artist => (
            <Text>
                { artist.name }
            </Text>
        ))
    }
    </View>);

Die Liste ist anfangs noch leer.

Verbindung zum Server

Wir nennen die Datei app.js in main.js um, damit wir sie in folgender, neuer app.js neu inkludieren können, und um sie in folgender Apollo-HOC-Komponente zu kapseln:

//app.js
import { ApolloClient, InMemoryCache } from 'apollo-client-preset';
import { HttpLink } from 'apollo-link-http';
import React from 'react';
import { ApolloProvider } from 'react-apollo';

import Main from './main';

const client = new ApolloClient({
    link: new HttpLink({
        uri: 'http://spotify-graphql-server.herokuapp.com/graphql'
    }),
    cache: new InMemoryCache()
});

export default class App extends React.Component {
    render() {
        return (
            <ApolloProvider client={client}>
                <Main />
            </ApolloProvider>
        );
    }
}

Modellierung der Datenabfrage mit GraphiQL

online GraphIQL-Abfrage

prepare query of artists in graphiql

Verknüpfung der Komponente mit GraphQL-Abfrage

In unserem Beispiel fragen wir nach der Liste der Künstler mit einem bestimmten Namen.

Dazu ergänzen wir in main.js diese Suche, in einem gql template string gekapselt (aus dem graphql-tag module), mit Suchparameter:

const ArtistsQuery = gql`
    query artists($byName: String!) {
        artists: queryArtists(byName: $byName) {
            name
        }
    }`;

const queryConfig = {
    options: ({ byName }) => ({
        variables: {
            byName
        }
    })
};

const ArtistsWithData = graphql(ArtistsQuery, queryConfig)(Artists);

und verwenden weiter oben ArtistsWithData anstatt Artists, also: <ArtistsWithData name={/*...*/} />

 

Expo als Entwicklungsumgebung

Ein kurzer Überblick über das Setup und die Tools für die schnelle Entwicklung mit Android/iOS-Simulator oder Client auf Smartphone für unser Beispiel:

Tools und Projekt initialisieren

npm install exp --global
exp register
exp login

exp init spotify-graphql-client-react-native-expo --projectType blank
cd spotify-graphql-client-react-native-expo
exp start

[exp] Your URL is: exp://xxxx.spot-cli.exp.direct:80
[exp] Logs for your project will appear below. Press Ctrl+C to exit.
in simulator, open expo, + button, add url and ...

exp start

Auf dem Smartphone kann der erzeugte QR-Code oder der URL in der Expo-App eingescannt werden, so dass auch auf einem echten Gerät getestet werden kann.

Diese kann auch im Simulator genutzt werden. Am einfachsten funktioniert es mit dem Expo Cli Tool per

#  in neuer shell, iOS Simulator oder Android Simulator
exp ios
exp android

 

Einige weiterführende Anwendungsfelder konnten nicht betrachtet werden, sind aber wesentlich und nutzen weitere Möglichkeiten von GraphQL:

  • Verschachtelung der Komponenten und deren Abfragen, also zusätzlich Liste der Alben pro Künstler
  • Änderungen per GraphQL-mutation: z. B. einen Künstler „liken“
  • Optimistic UI: sofortige Aktualisierung der Anzeige, ohne auf Rückgabe des Servers zu warten
  • Caching: Der Cache im Client kann sehr flexibel benutzt werden und z. B. schon bei Start mit alten Werten aus dem Local-Cache gefüllt werden etc.
  • GraphQL-Subscription: Das interessanteste Feature ist die Möglichkeit, vom Server per Websockets (oder auch MQTT etc.) aktualisiert zu werden, wobei das gleiche Typsystem des Schemas benutzt wird.

Zusammenfassung

In diesem Artikel haben wir anhand eines Beispiels sehen können, wie einfach die Integration von GraphQL im React Client ist.
Gegenüber reinem REST-Ansatz kann exakt spezifiziert werden,
welche Daten angefordert werden sollen.
Apollo bringt als client framework bereits Grundfunktionen mit, die
sonst immer zusätzlich implementiert werden müssten, wie zum Beispiel Caching, Aktualisierung des Clients…
Vorteile:

  • Exakte Definition, welche Daten benötigt und übermittelt werden sollen
    (kein „over-fetching“ mehr): ideal für mobile Datenverbindung
  • Abfrage durch Aggregation auf Server-Seite mit nur einem Request (kein typisches n+1 Muster): optimal bei mobiler Datenverbindung
  • API-Entwicklung aus Business Sicht, oder den Anforderungen des Clients ist zielorienter und bleibt eventuell „leichter verständlich“
  • ein „Vertrag“ durch Typisierung im Schema, der immer erlaubt, jede Abfrage
    gegen die aktuelle Schema-Definition zu überprüfen: keine „unnötigen“ Fehler durch fehlerhafte Abfragen,
    weil zum beispiel die JSON-Antwort unterschiedliche Feldnamen enthält (wie „Name“ statt „name“)
  • Nur ein einziger Endpoint für Aggregation von verschiedenen Datenquellen notwendig.

Durch die Typisierung erhält man bereits einen sehr guten Tool-Support in einer IDE, die durch Syntaxprüfung und Code Completion Arbeit sparen. Auch ein Linter, wie z. B. eslint kann die GraphQL-Abfragen im Sourcecode parsen und Fehler finden, noch bevor manuell getestet werden muss – was viel Zeit spart und mühevolle Fehlersuche vermeidet.

Durch den deklarativen Ansatz und die Möglichkeit, GraphQL und Komponenten im Sourcecode zusammen zu speichern, fallen Anpassungen und Orientierung im Code leicht. Erweiterungen um einfache Felder sind z. B. völlig unkompliziert.

Aus meiner Sicht ist auch der Austausch über die Schnittstelle zwischen Front- und Backend- Teams durch den technischen Kontrakt erleichtert. Der Dokumentations- und Kommunikations-Overhead wird reduziert.

Das Repository findet sich hier auf Github

Viel Spaß beim Ausprobieren!

Robert Hostlowsky

In den letzten 19 Jahren sammelte Robert Erfahrungen in verschiedenen Rollen in der Softwareentwicklung. Derzeit arbeitet er bei der codecentric AG als Entwickler und technischer Coach für agile Entwicklungspraktiken mit dem Ziel hoher Qualität und Effizienz.
Seit 2012 ist Robert begeisteter Anhänger der Software-Crafts-Bewegung.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.