Se chiediamo a ChatGPT cos’è GraphQL, la risposta è la seguente: “GraphQL è un linguaggio di query per API sviluppato da Facebook”. Viene descritto come un modo rivoluzionario di gestire la comunicazione client-server, consentendo al client di richiedere solo i dati necessari e ricevere risposte strutturate in modo efficiente.
Non a caso, GraphQL è stato adottato non solo da Facebook ma anche da colossi come Airbnb, Atlassian, Shopify, Netflix e PayPal. L’idea di base è che le chiamate GraphQL possano offrire migliori performance rispetto alle chiamate REST, soprattutto quando occorre combinare più dati strutturati.
Ma come si sviluppa GraphQL in ambito iOS? È davvero così efficiente come descritto?
Apollo CLI su iOS
Apollo CLI facilita l’integrazione di GraphQL nelle app iOS, offrendo strumenti per la gestione delle query e dei dati. La forte tipizzazione e la gestione della cache rendono Apollo un alleato prezioso nello sviluppo.
Apollo CLI permette di generare automaticamente classi, mutazioni e query direttamente dallo schema GraphQL. È disponibile tramite CocoaPods o Swift Package Manager (SPM). Tuttavia, la documentazione ufficiale non menziona alcuni problemi che possono sorgere in fase di configurazione.
Problemi di configurazione
Abbiamo seguito attentamente la documentazione di Apollo CLI per installarlo sia tramite SPM che tramite il plugin Xcode (v15.3), ma l’eseguibile di Apollo CLI non veniva incluso nel progetto. Questo ha impedito la generazione automatica dei modelli GraphQL.
Utilizzando CocoaPods, l’installazione andava invece a buon fine. Ciononostante, la scelta tecnica era di utilizzare esclusivamente SPM.
Dai forum, le soluzioni proposte erano:
- Effettuare il downgrade di Xcode
- Importare manualmente l’eseguibile di Apollo CLI (soluzione adottata)
Configurazione del JSON
La creazione del file di configurazione JSON non ha invece generato problemi.
Comando di generazione:
apollo-ios-cli init --schema-namespace <namespace>
--module-type <type> [--target-name <target name>]
Esempio di configurazione JSON generato:
{
"schemaNamespace" : "GraphQLProject",
"input" : {
"operationSearchPaths" : ["**/*.graphql"],
"schemaSearchPaths" : ["**/*.graphqls"]
},
"output" : {
"testMocks" : { "none" : {} },
"schemaTypes" : {
"moduleType": { "swiftPackageManager": {} },
"path": "./GraphQLSchemas"
},
"operations" : { "inSchemaModule" : {} }
}
}
Questo file permette di configurare le proprietà che gestiranno il codice generato da Apollo CLI. Lo schema GraphQL può essere scaricato direttamente da un URL remoto o definito in file locali.
Differenza tra file .graphqls e .graphql
- .graphqls: Contiene la definizione della struttura dati lato server (entità, enum, interfacce, query, mutation)
- .graphql: Contiene le query e mutation definite dal client
GraphQL consente al client di richiedere solo i dati necessari, evitando il recupero di informazioni inutili come accade spesso con API REST.
Gestione dell’output
La configurazione dell’output permette di decidere come generare il pacchetto:
- Embedded in Target
- Swift Package Manager (SPM)
- Other
La scelta dipende dall’architettura del progetto. In un’app whitelabel con più target, la soluzione adottata è stata un pacchetto SPM importabile con il supporto di Tuist.ple targets, the solution adopted by us was an SPM package that could be imported with Tuist support.
Query management
Gestione delle query
Ogni query definita nel .graphql deve avere un corrispettivo nello schema. In un progetto in evoluzione, è essenziale mantenere il controllo sulle query richieste e sullo schema. Se un campo cambia nome o viene rimosso, Apollo CLI blocca la generazione dei modelli, segnalando l’errore con il numero di riga.
Come ridurre gli errori? Utilizzando Fragments.
Esempio senza fragment:
query GetBookById($getBookById: ID!) {
getBookById(bookId: $bookId) {
id
title
}
}
query GetBookByCode($code: Int) {
getBookByCode(code: $code) {
id
title
type
code
author
}
}
Se il backend cambia il campo code
in codeId
, dovremo aggiornare entrambe le query.
Soluzione con fragment:
fragment BookFragment on Book {
id
title
type
code
author
}
query GetBookById($getBookById: ID!) {
getBookById(bookId: $bookId) {
...BookFragment
}
}
query GetBookByCode($code: Int) {
getBookByCode(code: $code) {
…BookFragment
}
}
Ora, se code
cambia in codeId
, basta aggiornare solo BookFragment, evitando errori ripetuti.
Tuttavia, un uso eccessivo dei fragment può portare a query troppo generiche, compromettendo l’efficienza delle richieste.
Problemi di Apollo CLI con file multipli
Se le definizioni sono divise in più file .graphql, Apollo CLI potrebbe non riconoscere i fragment definiti in un file separato, generando errori.
ChatGPT suggerisce di usare:
apollo codegen:generate --target=swift
--includes=GraphQL/**/*.graphql
--localSchemaFile=schema.graphql --output=GraphQLAPI.swift
Di seguito il test per verificare se questa soluzione funzioni davvero.
Il codice Swift generato
import Apollo
class BookService {
private let client = ApolloClient(url: URL(string: "[BASEURL]")!)
func fetchBooks(completion: @escaping (Result<[Book], Error>) -> Void) {
client.fetch(query: GetBooksQuery()) { result in
switch result {
case .success(let graphQLResult):
if let books = graphQLResult.data?.books {
completion(.success(books.map { $0.fragments.bookFragment }))
} else if let errors = graphQLResult.errors {
completion(.failure(errors.first!))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
Apollo genera una classe separata per ogni query, anche se gli oggetti sono gli stessi. Per mantenere il codice più modulare, l’unico modo è utilizzare i fragment.
Gestione della cache ed errori
Apollo offre una gestione avanzata della cache, permettendo di definire il comportamento per ogni chiamata. È utile tenere presente che la cache è attiva di default, il che potrebbe impedire di visualizzare le richieste nei log di debug se si usano librerie di monitoraggio. Tuttavia, all’interno della fetch
o mutation
, si può verificare se il dato è stato recuperato dalla cache.
La gestione degli errori è robusta e personalizzabile, consentendo di individuare facilmente discrepanze tra schema e query.
Conclusioni
L’integrazione di GraphQL con Apollo su iOS offre uno strumento potente per la comunicazione client-server, combinando efficienza e tipizzazione. Tuttavia, per ottenere un codice pulito e scalabile, è essenziale strutturare correttamente le query e sfruttare al meglio i fragment.
Una volta compreso il suo funzionamento, Apollo CLI può diventare una valida alternativa alle REST API.
Autore: Samantha Giro, Technical Mobile Lead