Ten post jest trzecim z serii o GraphQL:
Spis Treści
- Wprowadzenie
- Zarządzanie cache w Apollo Client
- Dwukierunkowa komunikacja z serwerem za pomocą Subscription i WebSocket
- Debugowanie za pomocą Apollo Developer Tools
- Podsumowanie
Wprowadzenie
Poprzednie dwa posty omówiły podstawy zastosowania GraphQL zarówno na stronie serwera jak i klienta. Możliwości Apollo Client wykraczają jednak daleko poza wysyłanie zapytań na serwer.
Czym więc zajmiemy się w tym wpisie?
- Cache’owaniem danych przez Apollo Client
- Dwukierunkową komunikacją pomiędzy serwerem a klientem
- Debugowaniem za pomocą Apollo Developer Tools
Zmęczenie wadami podejścia REST oraz rosnąca popularność GraphQL powodują, że warto przeczytać ten post i zgłębić wiedzę na temat Apollo Client.
Kod tego posta będzie rozszerzeniem kodu 2 poprzednich: GraphQL w React #1 – poznaj podstawy GraphQL! oraz GraphQL w React #2 – połącz się z serwerem dzięki Apollo Client.
Zarządzanie cache w Apollo Client
W poprzednim poście zdefiniowaliśmy aktualizowane cache poprzez odświeżenie rezultatu zapytania dla komponentu Owners
:
const {data, refetch} = useQuery(gql` query { owners { name } } `);
const [createOwner] = useMutation(gql` mutation ($name: String!){ createOwner(owner: {name: $name}) { id, name, } } `, {onCompleted: refetch});
Oznaczało to, że po każdym wywołaniu funkcji createOwner
, które tworzyło na serwerze nowy obiekt owner
, cała lista była pobierana ponownie. Nie są to jednak jedyne możliwości aktualizowania cache
Cykliczne powtarzanie zapytania do serwera
Jeżeli zrezygnujemy z użycia funkcji refetch
i zastąpimy ją ustawieniem pollInterval
na wysyłanym na serwer query:
const {data} = useQuery(gql` query { owners { name } } `, {pollInterval: 30000});
Zauważymy, że co 30 sekund na serwer wysyłane będzie żądanie zwracające obiekty owners
. Jeżeli w międzyczasie dodamy jakiś obiekt, nie doda się on do listy od razu – a dopiero po tym, jak zapytanie pobierające listę elementów załaduje nowe dane.
Ręczna aktualizacja cache
Ponieważ w pierwszym poście zdefiniowaliśmy, że żądanie utworzenia obiektu Owner
na serwerze zwraca nowo utworzony obiekt:
Mutation: { createOwner: (parent, args, context, info) => { let owner = {...args.owner, id: owners.length + 1}; owners.push(owner); return owner; } }
Możemy dzięki tej zwrotce zaktualizować cache bez konieczności odpytywania serwera o całą listę owners
:
const loadOwnersQuery = gql` query { owners { name } } `; const {data} = useQuery(loadOwnersQuery); const [createOwner] = useMutation(gql` mutation ($name: String!){ createOwner(owner: {name: $name}) { id, name, } } `, {update: (store, request) => { const data = store.readQuery({query: loadOwnersQuery}) const createdOwner = request.data.createOwner const updatedOwners = [...data.owners, createdOwner] store.writeQuery({ query: loadOwnersQuery, data: {owners: updatedOwners}}) }});
Przekazujemy w opcjach fukcję update
do naszej mutacji. Wewnątrz mamy dostęp do stanu, skąd za pomocą metody readQuery
pobieramy aktualne dane. Później za pomocą metody writeQuery
wpisujemy dane zaktualizowane o obiekt utworzony na serwerze.
Dwukierunkowa komunikacja z serwerem za pomocą Subscription i WebSocket
Ponieważ w poście dotyczącym części serwerowej zajmowaliśmy się tylko obsługą żądań HTTP, konieczne będą zmiany nie tylko na kliencie, ale również na serwerze.
Obsługa Subscriptions i WebSocket na serwerze
Aby umożliwić publikowanie i subskrybowanie należy zaimportować klasę PubSub i utworzyć jej instancję:
const { ApolloServer, gql, PubSub } = require("apollo-server"); const pubsub = new PubSub();
Należy zmienić również definicję typów:
// inne typy, Mutation dla lepszej orientacji type Mutation { createOwner(owner: OwnerInput!): Owner! } type Subscription { ownerAdded: Owner! }
Oraz dodać resolver dla typu Subscription
:
const resolvers = { //inne resolvery Subscription: { ownerAdded: { subscribe: () => pubsub.asyncIterator(['OWNER_ADDED']), } }, }
Zmodyfikujmy również resolver odpowiadający za dodawanie nowego obiektu typu Owner
:
const resolvers = { Mutation: { createOwner: (parent, args, context, info) => { let id = owners.length + 1; let owner = {...args.owner, id}; owners.push(owner); pubsub.publish('OWNER_ADDED', {ownerAdded: owner}) return owner; } }, }
Podczas dodawania obiektu publikowany jest event, który zawiera świeżo dodany obiekt.
Zmodyfikować powinniśmy również start serwera jak, by móc wyświetlić URL pod którym subskrypcja będzie możliwa:
server.listen() .then(({url, subscriptionsUrl}) => { console.log(`Sub url: ${subscriptionsUrl}`); console.log(`Server url: ${url}`); })
Obsługa Subscriptions i WebSocket na kliencie
Zainstalujmy bibliotekę która obsłuży komunikację WebSocket, wymaganą przez Apollo Client:
npm install subscriptions-transport-ws
Następnie, aby móc używać równocześnie komunikacji WebSocket i HTTP, konieczne będzie decydowanie którego adresu należy użyć do realizacji Query które otrzyma instancja klienta Apollo Client:
const httpLink = new HttpLink({ uri: 'http://localhost:4000/' }); const wsLink = new WebSocketLink({ uri: `ws://localhost:4000/graphql`, options: { reconnect: true } }); const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ); }, wsLink, httpLink, ); const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache() });
Natomiast do zasubskrybowania do zdefiniowanego na serwerze eventu ownerAdded
użyjemy hooka useSubscription
:
const { data: subData } = useSubscription(gql` subscription onOwnerAdded { ownerAdded { id } } `) useEffect(() => { if (subData?.ownerAdded?.id) { alert(`Owner with id: ${subData.ownerAdded.id} added`); } }, [subData])
Kod obu aplikacji na Github
Kod aplikacji serwera:
https://github.com/radek-anuszewski/graphql-server-example-2
Kod aplikacji klienta:
https://github.com/radek-anuszewski/graphql-client-example-2
Debugowanie za pomocą Apollo Developer Tools
Zainstalujmy oficjalne rozszerzenie do przeglądarki Chrome. Po zainstalowaniu pojawił się dodatkowy tab “Apollo”.
Zakładka GraphQL
W zakładce GraphQL mamy możliwość wysyłania zapytań na serwer, co ważne – po kliknięciu przycisku Explorer możemy wyklikać sobie co serwer ma nam zwrócić:
Zakładka Queries
W zakładce Queries znajduje się historia wszystkich zapytań wysłanych na serwer które pobrały dane. Mamy możliwość bezpośredniego uruchomienia tych zapytań dzięki przyciskowi “Run in GraphQL”:
Zakładka Mutations
W zakładce Mutations znajdują się wszystkie zapytania modyfikujące dane na serwerze:
Zakładka Cache
W zakłace Cache znajdziemy zapisane dane z wysłanych już zapytań:
Podsumowanie
Apollo pozwala nam w prosty obsłużyć zaawansowane przypadki wymagające użycia dwukierunkowej komunikacji między serwerem a klientem czy też obsługę cache. Udostępnia też narzędzia mocno wspomagające debugowanie. Platforma ta będzie doskonałym wyborem implementacji GraphQL w projekcie.